Compare commits
17 Commits
version-13
...
version-15
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
78e1831314 | ||
| b1935a3520 | |||
| cd98dffbab | |||
| 43d9b0dc0f | |||
| 2670bb83c7 | |||
| cccf121910 | |||
| b20411fce1 | |||
| f8b9e28258 | |||
| b14eeff0b3 | |||
| 5161c00efc | |||
| a487804c7c | |||
| 6f1a9d1219 | |||
| ea83e3e1ac | |||
| 3cdcb239c3 | |||
|
|
f98905f6e6 | ||
|
|
e62195815f | ||
|
|
0619cae23c |
19
.gitignore
vendored
19
.gitignore
vendored
@@ -1,6 +1,19 @@
|
||||
.DS_Store
|
||||
*.pyc
|
||||
*.py~
|
||||
.DS_Store
|
||||
conf.py
|
||||
locale
|
||||
latest_updates.json
|
||||
.wnf-lang-status
|
||||
*.egg-info
|
||||
dist/
|
||||
manufacturing_overview/public/dist
|
||||
manufacturing_overview/docs/current
|
||||
*.swp
|
||||
tags
|
||||
manufacturing_overview/docs/current
|
||||
*.swo
|
||||
__pycache__
|
||||
*~
|
||||
.idea/
|
||||
.vscode/
|
||||
node_modules/
|
||||
.backportrc.json
|
||||
@@ -1,3 +1,2 @@
|
||||
|
||||
__version__ = '0.0.1'
|
||||
|
||||
__version__ = '14.38.0'
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
from . import __version__ as app_version
|
||||
from frappe import _
|
||||
|
||||
app_name = "manufacturing_overview"
|
||||
app_title = "Manufacturing Overview"
|
||||
app_publisher = "CAnetzberger Design"
|
||||
app_description = "Dashboard Overview for Sales Orders Items to be manufactured"
|
||||
app_icon = "octicon octicon-file-directory"
|
||||
app_icon = "fa fa-th"
|
||||
app_color = "grey"
|
||||
app_email = "admin@canetzberger.design"
|
||||
app_license = "MIT"
|
||||
required_apps = ["erpnext"]
|
||||
|
||||
# Includes in <head>
|
||||
# ------------------
|
||||
|
||||
# include js, css files in header of desk.html
|
||||
# app_include_css = "/assets/manufacturing_overview/css/manufacturing_overview.css"
|
||||
app_include_js = "/assets/manufacturing_overview/js/manufacturing_overview.min.js"
|
||||
app_include_js = "manufacturing_overview.bundle.js"
|
||||
|
||||
# include js, css files in header of web template
|
||||
# web_include_css = "/assets/manufacturing_overview/css/manufacturing_overview.css"
|
||||
@@ -44,7 +45,7 @@ app_include_js = "/assets/manufacturing_overview/js/manufacturing_overview.min.j
|
||||
|
||||
# website user home page (by Role)
|
||||
# role_home_page = {
|
||||
# "Role": "home_page"
|
||||
# "Role": "home_page"
|
||||
# }
|
||||
|
||||
# Generators
|
||||
@@ -100,7 +101,13 @@ app_include_js = "/assets/manufacturing_overview/js/manufacturing_overview.min.j
|
||||
# "on_update": "method",
|
||||
# "on_cancel": "method",
|
||||
# "on_trash": "method"
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
|
||||
# doc_events = {
|
||||
# "Subcontracting Receipt": {
|
||||
# "on_submit": "manufacturing_overview.manufacturing_overview.productionplan.update_productionplan_status",
|
||||
# }
|
||||
# }
|
||||
|
||||
# Scheduled Tasks
|
||||
@@ -148,30 +155,6 @@ app_include_js = "/assets/manufacturing_overview/js/manufacturing_overview.min.j
|
||||
# auto_cancel_exempted_doctypes = ["Auto Repeat"]
|
||||
|
||||
|
||||
# User Data Protection
|
||||
# --------------------
|
||||
|
||||
user_data_fields = [
|
||||
{
|
||||
"doctype": "{doctype_1}",
|
||||
"filter_by": "{filter_by}",
|
||||
"redact_fields": ["{field_1}", "{field_2}"],
|
||||
"partial": 1,
|
||||
},
|
||||
{
|
||||
"doctype": "{doctype_2}",
|
||||
"filter_by": "{filter_by}",
|
||||
"partial": 1,
|
||||
},
|
||||
{
|
||||
"doctype": "{doctype_3}",
|
||||
"strict": False,
|
||||
},
|
||||
{
|
||||
"doctype": "{doctype_4}"
|
||||
}
|
||||
]
|
||||
|
||||
# Authentication and authorization
|
||||
# --------------------------------
|
||||
|
||||
|
||||
@@ -2,19 +2,10 @@ import frappe
|
||||
from functools import reduce
|
||||
from datetime import date
|
||||
|
||||
# frappe.utils.logger.set_log_level("DEBUG")
|
||||
# logger = frappe.logger("manufacturing_overview", allow_site=True)
|
||||
|
||||
def clearCache(doc, event):
|
||||
frappe.cache().delete_value("production_overview")
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def getUserPermissions():
|
||||
return frappe.get_doc('Production Overview User Mapping', frappe.session.user)
|
||||
|
||||
|
||||
def formatDate(d):
|
||||
return frappe.utils.formatdate(
|
||||
d, 'dd.MM.yyyy')
|
||||
#logger.debug(f"String")
|
||||
|
||||
|
||||
def getDueInDays(d):
|
||||
@@ -22,9 +13,12 @@ def getDueInDays(d):
|
||||
return delta.days
|
||||
|
||||
|
||||
def getWorkOrders(soname):
|
||||
return frappe.get_all("Work Order", filters={
|
||||
'sales_order': soname}, fields=['name'])
|
||||
def getWorkOrder(soItemName):
|
||||
return frappe.get_all("Work Order", filters={"sales_order_item": soItemName})
|
||||
|
||||
def getPurchaseOrder(soItemName):
|
||||
|
||||
return frappe.get_all("Purchase Order Item", filters={"sales_order_item": soItemName}, fields=['parent'])
|
||||
|
||||
|
||||
def getAmountInWarehouses(item_code):
|
||||
@@ -43,20 +37,7 @@ def shortenCustomerName(customer):
|
||||
return customer
|
||||
|
||||
|
||||
def calculateSalesOrderStatus(item, warehouseamount, workorders):
|
||||
if item.delivered_qty < item.qty and item.delivered_qty > 0:
|
||||
return "Partially Delivered"
|
||||
elif item.qty <= warehouseamount:
|
||||
return "In Warehouse"
|
||||
elif len(workorders) <= 0:
|
||||
return "No Work Order"
|
||||
elif len(workorders) > 0 and item.qty > warehouseamount:
|
||||
return "To Produce"
|
||||
else:
|
||||
return "Unknown"
|
||||
|
||||
|
||||
def generateProductionOverviewCache():
|
||||
def generateProductionOverviewCacheData():
|
||||
salesOrderItems = frappe.db.sql(
|
||||
"""
|
||||
SELECT
|
||||
@@ -66,9 +47,11 @@ def generateProductionOverviewCache():
|
||||
`tabSales Order Item`.qty,
|
||||
`tabSales Order Item`.actual_qty,
|
||||
`tabSales Order Item`.delivered_qty,
|
||||
`tabSales Order Item`.delivery_date,
|
||||
`tabSales Order Item`.delivery_date AS date,
|
||||
`tabSales Order Item`.item_code,
|
||||
`tabSales Order Item`.work_order_qty,
|
||||
`tabSales Order Item`.parent,
|
||||
`tabSales Order Item`.production_plan_qty,
|
||||
`tabSales Order`.status as parentStatus
|
||||
FROM
|
||||
`tabSales Order Item`
|
||||
@@ -80,37 +63,132 @@ def generateProductionOverviewCache():
|
||||
status = 'To Deliver and Bill' and
|
||||
item_group = "Produkte"
|
||||
ORDER BY
|
||||
delivery_date
|
||||
""", as_dict=1)
|
||||
date, item_code, name
|
||||
""", as_dict=1)
|
||||
|
||||
for soItem in salesOrderItems:
|
||||
soItem.due_in = getDueInDays(soItem.delivery_date)
|
||||
|
||||
currentWarehouseQtyList = []
|
||||
|
||||
for soItem in salesOrderItems:
|
||||
soItem.currentWarehouseQty = calculateCurrentWarehouseQty(
|
||||
soItem.item_code, soItem.qty, currentWarehouseQtyList)
|
||||
|
||||
soItem.totalWarehouseQty = getAmountInWarehouses(soItem.item_code)
|
||||
|
||||
soItem.due_in = getDueInDays(soItem.date)
|
||||
|
||||
soItem.customer = shortenCustomerName(frappe.get_value(
|
||||
'Sales Order', soItem.parent, 'customer'))
|
||||
soItem.wos = frappe.get_all('Work Order', filters=[
|
||||
['sales_order', '=', soItem.parent], ['production_item', '=', soItem.item_code], ['status', '!=', "Cancelled"]])
|
||||
|
||||
soItem.delivery_date = formatDate(soItem.delivery_date)
|
||||
soItem.warehouseamount = getAmountInWarehouses(soItem.item_code)
|
||||
|
||||
if soItem.warehouseamount >= soItem.qty:
|
||||
soItem.status = 'In Warehouse'
|
||||
elif len(soItem.wos) != 0:
|
||||
soItem.status = 'To Produce'
|
||||
|
||||
soItem.direct_po = getPurchaseOrder(soItem.name)
|
||||
if len(soItem.direct_po) >= 1:
|
||||
soItem.direct_po = soItem.direct_po[0]['parent']
|
||||
else:
|
||||
soItem.status = 'No Work Order'
|
||||
soItem.direct_po = None
|
||||
soItem.direct_wo = getWorkOrder(soItem.name)
|
||||
if len(soItem.direct_wo) >= 1:
|
||||
soItem.direct_wo = soItem.direct_wo[0]['name']
|
||||
else:
|
||||
soItem.direct_wo = None
|
||||
|
||||
|
||||
|
||||
soItem = formatDate(soItem)
|
||||
soItem = calculateStatus(soItem)
|
||||
soItem.qty = soItem.qty - soItem.delivered_qty
|
||||
soItem.link = '/app/sales-order/' + soItem.parent
|
||||
|
||||
soItem.link = '/app#Form/Sales%20Order/' + soItem.parent
|
||||
return salesOrderItems
|
||||
|
||||
frappe.cache().set_value("production_overview", salesOrderItems)
|
||||
|
||||
def generatePurchaseOrderOverviewCacheData():
|
||||
purchaseOrderItems = frappe.db.sql(
|
||||
"""
|
||||
SELECT
|
||||
`tabPurchase Order Item`.name,
|
||||
`tabPurchase Order Item`.item_name,
|
||||
`tabPurchase Order Item`.item_group,
|
||||
`tabPurchase Order Item`.qty,
|
||||
`tabPurchase Order Item`.schedule_date AS date,
|
||||
`tabPurchase Order Item`.item_code,
|
||||
`tabPurchase Order Item`.parent,
|
||||
`tabPurchase Order Item`.fg_item,
|
||||
`tabPurchase Order`.status as parentStatus
|
||||
FROM
|
||||
`tabPurchase Order Item`
|
||||
INNER JOIN `tabPurchase Order`
|
||||
ON
|
||||
`tabPurchase Order Item`.parent = `tabPurchase Order`.name
|
||||
WHERE
|
||||
status = 'To Receive and Bill'
|
||||
ORDER BY
|
||||
date, item_code, name
|
||||
""", as_dict=1)
|
||||
|
||||
for poItem in purchaseOrderItems:
|
||||
poItem = formatDate(poItem)
|
||||
poItem.customer = frappe.get_value("Purchase Order", poItem.parent, 'supplier_name')
|
||||
|
||||
poItem.fg_item_name = frappe.get_value("Item", poItem.fg_item, 'item_name')
|
||||
poItem.direct_po = poItem.parent
|
||||
|
||||
poItem.link = '/app/purchase-order/' + poItem.parent
|
||||
|
||||
poItem.status = 'Unknown'
|
||||
|
||||
return purchaseOrderItems
|
||||
|
||||
|
||||
def calculateCurrentWarehouseQty(item_code, qty, currentWarehouseQtyList):
|
||||
for warehouseQty in currentWarehouseQtyList:
|
||||
|
||||
if warehouseQty['item_code'] == item_code:
|
||||
returnQty = warehouseQty['qty']
|
||||
warehouseQty['qty'] = warehouseQty['qty'] - qty
|
||||
return returnQty
|
||||
|
||||
fetchedWarehouseQty = getAmountInWarehouses(item_code)
|
||||
currentWarehouseQtyList.append(
|
||||
{'item_code': item_code, 'qty': fetchedWarehouseQty - qty})
|
||||
return fetchedWarehouseQty
|
||||
|
||||
|
||||
def formatDate(item):
|
||||
item.date = frappe.utils.formatdate(
|
||||
item.date, 'dd.MM.yyyy')
|
||||
return item
|
||||
|
||||
|
||||
def calculateStatus(item):
|
||||
if item.delivered_qty > 0 and item.delivered_qty < item.qty:
|
||||
item.status = 'Partially Delivered'
|
||||
elif item.delivered_qty >= item.qty:
|
||||
item.status = 'Fully Delivered'
|
||||
elif item.currentWarehouseQty >= item.qty:
|
||||
item.status = 'In Warehouse'
|
||||
elif item.work_order_qty > 0:
|
||||
item.status = 'To Produce'
|
||||
else:
|
||||
item.status = 'No Work Order'
|
||||
return item
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def getSalesorderOverviewList():
|
||||
salesOrderItems = frappe.cache().get_value(
|
||||
"production_overview", generateProductionOverviewCache())
|
||||
salesOrderItems = frappe.cache().get_value('production_overview', expires=True)
|
||||
|
||||
if salesOrderItems is None:
|
||||
salesOrderItems = generateProductionOverviewCacheData()
|
||||
frappe.cache().set_value('production_overview', salesOrderItems,expires_in_sec=600)
|
||||
|
||||
return salesOrderItems
|
||||
|
||||
@frappe.whitelist()
|
||||
def getPurchaseOrderOverviewList():
|
||||
purchaseOrderItems = frappe.cache().get_value('purchaseOrder_items', expires=True)
|
||||
|
||||
if purchaseOrderItems is None:
|
||||
purchaseOrderItems = generatePurchaseOrderOverviewCacheData()
|
||||
frappe.cache().set_value('purchaseOrder_items', purchaseOrderItems, expires_in_sec=600)
|
||||
|
||||
return purchaseOrderItems
|
||||
109
manufacturing_overview/manufacturing_overview/productionplan.py
Normal file
109
manufacturing_overview/manufacturing_overview/productionplan.py
Normal file
@@ -0,0 +1,109 @@
|
||||
import frappe
|
||||
from erpnext.stock.get_item_details import get_default_bom
|
||||
from frappe.utils import (
|
||||
nowdate
|
||||
)
|
||||
|
||||
frappe.utils.logger.set_log_level("DEBUG")
|
||||
logger = frappe.logger("manufacturing_overview",
|
||||
allow_site=True, file_count=50)
|
||||
# logger.debug(f"{wo_id}")
|
||||
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def makepp(sales_order):
|
||||
"""
|
||||
Check if Production Plan already exists and break when it does.
|
||||
"""
|
||||
so = frappe.get_doc("Sales Order", sales_order)
|
||||
|
||||
checkpp = frappe.get_all('Production Plan Item', filters={'sales_order': so.name}, fields=[
|
||||
'name', 'parent'])
|
||||
|
||||
if checkpp:
|
||||
return {'status': 400,
|
||||
'docname': [checkpp[0].parent]}
|
||||
|
||||
"""
|
||||
Create Production Plan
|
||||
"""
|
||||
sales_orders = [{
|
||||
"sales_order": so.name,
|
||||
"customer": so.customer,
|
||||
"sales_order_date": so.transaction_date,
|
||||
"grand_total": so.grand_total,
|
||||
}]
|
||||
|
||||
po_items = []
|
||||
for item in so.items:
|
||||
i = {
|
||||
"item_code": item.item_code,
|
||||
"bom_no": get_default_bom(item.item_code),
|
||||
"planned_qty": item.qty,
|
||||
"planned_start_date": frappe.utils.nowdate(),
|
||||
"stock_uom": "Stk",
|
||||
"sales_order": so.name,
|
||||
"sales_order_item": item.name
|
||||
}
|
||||
|
||||
po_items.append(i)
|
||||
|
||||
pp = frappe.get_doc({
|
||||
'doctype': 'Production Plan',
|
||||
'get_items_from': 'Sales Order',
|
||||
'sales_orders': sales_orders,
|
||||
'po_items': po_items,
|
||||
'for_warehouse': "Lagerräume - HP"
|
||||
})
|
||||
pp.insert()
|
||||
|
||||
return {'status': 201,
|
||||
'docname': [pp.name]}
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def fixfinisheditemwopo(production_plan):
|
||||
pp = frappe.get_doc("Production Plan", production_plan)
|
||||
wo_id = frappe.get_all('Work Order', filters={'production_plan_item': pp.po_items[0].name})
|
||||
wo_id = wo_id[0]["name"]
|
||||
|
||||
frappe.delete_doc("Work Order", wo_id)
|
||||
|
||||
|
||||
po = frappe.new_doc("Purchase Order")
|
||||
po.supplier = pp.po_items[0].custom_supplier
|
||||
po.schedule_date = nowdate()
|
||||
po.is_subcontracted = 1
|
||||
|
||||
for row in pp.po_items:
|
||||
po_data = {
|
||||
"fg_item": row.item_code,
|
||||
"warehouse": row.warehouse,
|
||||
"bom": row.bom_no,
|
||||
"production_plan": pp.name,
|
||||
"fg_item_qty": row.planned_qty,
|
||||
"supplier_warehouse": "Externe Arbeiten - HP"
|
||||
}
|
||||
|
||||
for field in [
|
||||
"schedule_date",
|
||||
"qty",
|
||||
"description",
|
||||
"production_plan_item",
|
||||
]:
|
||||
po_data[field] = row.get(field)
|
||||
|
||||
po.append("items", po_data)
|
||||
|
||||
po.set_service_items_for_finished_goods()
|
||||
po.set_missing_values()
|
||||
# po.flags.ignore_mandatory = True
|
||||
# po.flags.ignore_validate = True
|
||||
po.insert()
|
||||
|
||||
# pp.db_set("total_produced_qty", pp.total_planned_qty)
|
||||
# pp.db_set("status", "Completed")
|
||||
|
||||
return {'status': 201,
|
||||
'docname': po.name}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"manufacturing_overview/js/manufacturing_overview.min.js": [
|
||||
"public/js/manufacturing_overview_page.js"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import './manufacturing_overview_desk.vue';
|
||||
import './manufacturing_overview_page.js';
|
||||
import './manufacturing_overview_row.vue';
|
||||
import './sales_order_addition.js';
|
||||
import './production_plan_addition.js';
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"manufacturing_overview.min.js","sources":["../../../../apps/manufacturing_overview/manufacturing_overview/public/js/manufacturing_overview_row.vue?rollup-plugin-vue=script.js","../../../../apps/manufacturing_overview/manufacturing_overview/public/js/manufacturing_overview_desk.vue?rollup-plugin-vue=script.js","../../../../apps/manufacturing_overview/manufacturing_overview/public/js/manufacturing_overview_page.js"],"sourcesContent":["//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n\nexport default {\n name: \"ManufacturingOverviewRow\",\n props: [\n \"qty\",\n \"item_name\",\n \"item_code\",\n \"customer\",\n \"delivery_date\",\n \"status\",\n \"link\",\n \"reference\",\n \"due_in\",\n ],\n methods: {\n pushRoute(link) {\n frappe.router.push_state(link);\n },\n },\n};\n","//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n\nimport ManufacturingOverviewRow from \"./manufacturing_overview_row.vue\";\n\nexport default {\n components: {\n ManufacturingOverviewRow,\n },\n data() {\n return {\n salesorderData: [\n {\n customer: \"\",\n delivery_date: \"\",\n link: \"\",\n name: \"\",\n item_name: \"Loading...\",\n item_code: \"\",\n qty: \"\",\n sales_order: \"\",\n status: \"Unknown\",\n due_in: 0,\n },\n ],\n timer: \"\",\n origin: window.location.origin,\n userPermissions: {},\n };\n },\n created() {\n this.fetchEventsList();\n this.timer = setInterval(this.fetchEventsList, 30000);\n },\n methods: {\n fetchEventsList() {\n let self = this;\n frappe.call({\n method:\n \"manufacturing_overview.manufacturing_overview.api.getSalesorderOverviewList\",\n async: true,\n args: {},\n callback: function (r) {\n if (r.message) {\n self.salesorderData = r.message;\n }\n },\n });\n },\n cancelAutoUpdate() {\n clearInterval(this.timer);\n },\n beforeDestroy() {\n clearInterval(this.timer);\n },\n },\n};\n","import ManufacturingOverviewDesk from \"./manufacturing_overview_desk.vue\";\n\n$(document).ready(function () {\n $(\".layout-main-section-wrapper\").after('<div class=\"col-12 col-lg-4 layout-main-section-wrapper\" id=\"manufacturing-overview-body\"></div>');\n var pod = new Vue({\n el: \"#manufacturing-overview-body\",\n render(h) {\n return h(ManufacturingOverviewDesk, {});\n }\n });\n});"],"names":["name","props","methods","pushRoute","link","frappe","router","push_state","components","data","salesorderData","customer","delivery_date","item_name","item_code","qty","sales_order","status","due_in","timer","origin","window","location","userPermissions","created","this","fetchEventsList","setInterval","let","self","call","method","async","args","callback","r","message","cancelAutoUpdate","clearInterval","beforeDestroy","$","document","ready","after","Vue","el","render","h","ManufacturingOverviewDesk"],"mappings":"+BA4De,CACbA,KAAM,2BACNC,MAAO,CACL,MACA,YACA,YACA,WACA,gBACA,SACA,OACA,YACA,UAEFC,QAAS,CACPC,mBAAUC,GACRC,OAAOC,OAAOC,WAAWH,0mDCzChB,CACbI,WAAY,o8DAGZC,gBACE,MAAO,CACLC,eAAgB,CACd,CACEC,SAAU,GACVC,cAAe,GACfR,KAAM,GACNJ,KAAM,GACNa,UAAW,aACXC,UAAW,GACXC,IAAK,GACLC,YAAa,GACbC,OAAQ,UACRC,OAAQ,IAGZC,MAAO,GACPC,OAAQC,OAAOC,SAASF,OACxBG,gBAAiB,KAGrBC,mBACEC,KAAKC,kBACLD,KAAKN,MAAQQ,YAAYF,KAAKC,gBAAiB,MAEjDxB,QAAS,CACPwB,2BACEE,IAAIC,EAAOJ,KACXpB,OAAOyB,KAAK,CACVC,OACE,8EACFC,OAAO,EACPC,KAAM,GACNC,SAAU,SAAUC,GACdA,EAAEC,UACJP,EAAKnB,eAAiByB,EAAEC,aAKhCC,4BACEC,cAAcb,KAAKN,QAErBoB,yBACED,cAAcb,KAAKN,8vFChFzBqB,EAAEC,UAAUC,MAAM,WACdF,EAAE,gCAAgCG,MAAM,oGAC9B,IAAIC,IAAI,CACdC,GAAI,+BACJC,gBAAOC,GACH,OAAOA,EAAEC,EAA2B"}
|
||||
@@ -1,10 +1,40 @@
|
||||
<template>
|
||||
<div class="col-12 col-lg-4 layout-main-section-wrapper">
|
||||
<div class="form-tabs-list">
|
||||
<ul class="nav form-tabs" id="form-tabs" role="tablist">
|
||||
<li class="nav-item show">
|
||||
<a
|
||||
class="nav-link"
|
||||
:class="{ active: currentTab === 0 }"
|
||||
role="tab"
|
||||
@click="changeTab(0)"
|
||||
>
|
||||
Production Overview
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item show">
|
||||
<a
|
||||
class="nav-link"
|
||||
:class="{ active: currentTab === 1 }"
|
||||
role="tab"
|
||||
@click="changeTab(1)"
|
||||
>
|
||||
Purchase Overview
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="layout-main-section">
|
||||
<div class="widget links-widget-box" style="height: auto">
|
||||
<div
|
||||
class="widget quick-list-widget-box"
|
||||
style="height: auto"
|
||||
v-if="currentTab === 0"
|
||||
>
|
||||
<div class="widget-head">
|
||||
<div>
|
||||
<div class="widget-subtitle"></div>
|
||||
<div class="widget-label">
|
||||
<div class="widget-title">
|
||||
<span class="ellipsis" title="Sales Order"
|
||||
>Delivery Overview</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="widget-control"></div>
|
||||
</div>
|
||||
@@ -16,11 +46,48 @@
|
||||
v-bind:item_name="so.item_name"
|
||||
v-bind:item_code="so.item_code"
|
||||
v-bind:customer="so.customer"
|
||||
v-bind:delivery_date="so.delivery_date"
|
||||
v-bind:date="so.date"
|
||||
v-bind:status="so.status"
|
||||
v-bind:link="so.link"
|
||||
v-bind:reference="so.parent"
|
||||
v-bind:due_in="so.due_in"
|
||||
v-bind:direct_wo="so.direct_wo"
|
||||
v-bind:direct_po="so.direct_po"
|
||||
v-bind:rowtype="so"
|
||||
>
|
||||
</manufacturing-overview-row>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="widget quick-list-widget-box"
|
||||
style="height: auto"
|
||||
v-if="currentTab === 1"
|
||||
>
|
||||
<div class="widget-head">
|
||||
<div class="widget-label">
|
||||
<div class="widget-title">
|
||||
<span class="ellipsis" title="Sales Order"
|
||||
>Purchase Overview</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="widget-control"></div>
|
||||
</div>
|
||||
<div class="widget-body">
|
||||
<manufacturing-overview-row
|
||||
v-for="sc in purchaseOrderData"
|
||||
:key="sc.name"
|
||||
v-bind:qty="sc.qty"
|
||||
v-bind:item_name="sc.fg_item_name"
|
||||
v-bind:item_code="sc.fg_item"
|
||||
v-bind:fg_item="sc.fg_item"
|
||||
v-bind:customer="sc.customer"
|
||||
v-bind:date="sc.date"
|
||||
v-bind:status="sc.status"
|
||||
v-bind:direct_po="sc.direct_po"
|
||||
v-bind:link="sc.link"
|
||||
v-bind:rowtype="po"
|
||||
>
|
||||
</manufacturing-overview-row>
|
||||
</div>
|
||||
@@ -38,10 +105,11 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentTab: 0,
|
||||
salesorderData: [
|
||||
{
|
||||
customer: "",
|
||||
delivery_date: "",
|
||||
date: "",
|
||||
link: "",
|
||||
name: "",
|
||||
item_name: "Loading...",
|
||||
@@ -50,6 +118,22 @@ export default {
|
||||
sales_order: "",
|
||||
status: "Unknown",
|
||||
due_in: 0,
|
||||
direct_wo: "",
|
||||
direct_po: "",
|
||||
},
|
||||
],
|
||||
purchaseOrderData: [
|
||||
{
|
||||
name: "",
|
||||
customer: "",
|
||||
link: "",
|
||||
item_name: "Loading...",
|
||||
item_code: "",
|
||||
qty: "",
|
||||
date: "",
|
||||
fg_item: "",
|
||||
status: "Unknown",
|
||||
direct_po: "",
|
||||
},
|
||||
],
|
||||
timer: "",
|
||||
@@ -62,6 +146,10 @@ export default {
|
||||
this.timer = setInterval(this.fetchEventsList, 30000);
|
||||
},
|
||||
methods: {
|
||||
changeTab(id) {
|
||||
let self = this;
|
||||
self.currentTab = id;
|
||||
},
|
||||
fetchEventsList() {
|
||||
let self = this;
|
||||
frappe.call({
|
||||
@@ -75,6 +163,18 @@ export default {
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
frappe.call({
|
||||
method:
|
||||
"manufacturing_overview.manufacturing_overview.api.getPurchaseOrderOverviewList",
|
||||
async: true,
|
||||
args: {},
|
||||
callback: function (r) {
|
||||
if (r.message) {
|
||||
self.purchaseOrderData = r.message;
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
cancelAutoUpdate() {
|
||||
clearInterval(this.timer);
|
||||
@@ -86,5 +186,4 @@ export default {
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
<style></style>
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { createApp } from "vue";
|
||||
|
||||
import ManufacturingOverviewDesk from "./manufacturing_overview_desk.vue";
|
||||
|
||||
$(document).ready(function () {
|
||||
$(".layout-main-section-wrapper").after('<div class="col-12 col-lg-4 layout-main-section-wrapper" id="manufacturing-overview-body"></div>');
|
||||
var pod = new Vue({
|
||||
el: "#manufacturing-overview-body",
|
||||
render(h) {
|
||||
return h(ManufacturingOverviewDesk, {});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const moapp = createApp(ManufacturingOverviewDesk)
|
||||
|
||||
moapp.mount("#manufacturing-overview-body");
|
||||
});
|
||||
|
||||
|
||||
@@ -1,29 +1,45 @@
|
||||
<template>
|
||||
<a
|
||||
<div
|
||||
@click="pushRoute(link)"
|
||||
class="row link-item text-wrap ellipsis onbpoard-spotlight"
|
||||
type="Link"
|
||||
@mouseenter="hover = true"
|
||||
@mouseleave="(hover = false), (detailsOpen = false)"
|
||||
class="quick-list-item align-items-start"
|
||||
>
|
||||
<div class="col col-xs-8">
|
||||
<div class="col-1">
|
||||
<span
|
||||
class="indicator-pill no-margin"
|
||||
v-bind:class="{
|
||||
red: status === 'No Work Order',
|
||||
blue: status === 'Partially Delivered',
|
||||
gray: status === 'Fully Delivered',
|
||||
green: status === 'In Warehouse',
|
||||
yellow: status === 'To Produce',
|
||||
grey: status === 'Unknown',
|
||||
}"
|
||||
></span>
|
||||
<span class="widget-subtitle">{{ qty }}</span> -
|
||||
<span class="widget-title">{{ item_name }}</span>
|
||||
</div>
|
||||
<div class="col col-xs-6 col-lg-8 overflow-hidden">
|
||||
<span class="ellipsis title"
|
||||
><b>{{ qty }}</b> - {{ item_name }}</span
|
||||
>
|
||||
<div>
|
||||
<small v-if="customer && item_code" class="color-secondary"
|
||||
<small v-if="fg_item" class="color-secondary">
|
||||
<a
|
||||
class="underline-hover"
|
||||
@click="pushRoute('/app/item/' + fg_item)"
|
||||
v-on:click.stop
|
||||
>{{ fg_item }}</a
|
||||
>
|
||||
</small>
|
||||
<small v-else-if="customer && item_code" class="color-secondary"
|
||||
>{{ customer }} -
|
||||
<a @click="pushRoute('/app#Form/Item/' + item_code)" type="Link">{{
|
||||
item_code
|
||||
}}</a></small
|
||||
>
|
||||
<a
|
||||
class="underline-hover"
|
||||
@click="pushRoute('/app/item/' + item_code)"
|
||||
v-on:click.stop
|
||||
>{{ item_code }}</a
|
||||
>
|
||||
</small>
|
||||
<small v-else-if="customer" class="color-secondary">{{
|
||||
customer
|
||||
}}</small>
|
||||
@@ -32,52 +48,105 @@
|
||||
}}</small>
|
||||
</div>
|
||||
<div>
|
||||
<small class="color-secondary">{{ reference }}</small>
|
||||
<small>
|
||||
<span>
|
||||
<a
|
||||
class="underline-hover"
|
||||
@click="pushRoute('/app/sales-order/' + reference)"
|
||||
v-on:click.stop
|
||||
>{{ reference }}</a
|
||||
>
|
||||
</span>
|
||||
<span v-if="direct_wo">
|
||||
-
|
||||
<a
|
||||
class="underline-hover"
|
||||
@click="pushRoute('/app/work-order/' + direct_wo)"
|
||||
v-on:click.stop
|
||||
>{{ direct_wo }}</a
|
||||
>
|
||||
</span>
|
||||
<span v-if="direct_po && direct_wo">-</span>
|
||||
<span v-if="direct_po">
|
||||
<a
|
||||
class="underline-hover"
|
||||
@click="pushRoute('/app/purchase-order/' + direct_po)"
|
||||
v-on:click.stop
|
||||
>{{ direct_po }}</a
|
||||
>
|
||||
</span>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- v-if="hover" -->
|
||||
|
||||
<div
|
||||
v-if="due_in < 0"
|
||||
class="text-muted ellipsis color-secondary col col-xs-4 text-right"
|
||||
class="text-muted ellipsis color-secondary col col-xs-5 col-lg-3 text-right"
|
||||
>
|
||||
<b style="color: red">{{ delivery_date }}</b>
|
||||
<div class="d-flex justify-content-end flex-column align-items-end">
|
||||
<div class="mb-auto">
|
||||
<b v-if="due_in < 0" style="color: red">{{ date }}</b>
|
||||
<b v-else-if="due_in === 0" style="color: black">{{ date }}</b>
|
||||
<span v-else>{{ date }}</span>
|
||||
</div>
|
||||
|
||||
<!-- <div @click="getItemDetails(item_code)" v-if="!detailsOpen" v-on:click.stop>
|
||||
Details
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="due_in === 0"
|
||||
class="text-muted ellipsis color-secondary col col-xs-4 text-right"
|
||||
>
|
||||
<b style="color: black">{{ delivery_date }}</b>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="text-muted ellipsis color-secondary col col-xs-4 text-right"
|
||||
>
|
||||
{{ delivery_date }}
|
||||
</div>
|
||||
</a>
|
||||
<!-- <div v-if="detailsOpen" :style="{ height: '500px' }"></div> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "ManufacturingOverviewRow",
|
||||
data() {
|
||||
return {
|
||||
hover: false,
|
||||
detailsOpen: false,
|
||||
currentDetails: ["1", "2"],
|
||||
};
|
||||
},
|
||||
props: [
|
||||
"qty",
|
||||
"item_name",
|
||||
"item_code",
|
||||
"customer",
|
||||
"delivery_date",
|
||||
"customershort",
|
||||
"date",
|
||||
"status",
|
||||
"link",
|
||||
"reference",
|
||||
"due_in",
|
||||
"direct_wo",
|
||||
"direct_po",
|
||||
"fg_item",
|
||||
],
|
||||
methods: {
|
||||
pushRoute(link) {
|
||||
frappe.router.push_state(link);
|
||||
},
|
||||
// getItemDetails(item) {
|
||||
// this.detailsOpen = !this.detailsOpen;
|
||||
// frappe.call({
|
||||
// method:
|
||||
// "manufacturing_overview.manufacturing_overview.api.get_bom_tree",
|
||||
// async: true,
|
||||
// args: { item_code: item },
|
||||
// callback: function (r) {
|
||||
// console.log(r.message);
|
||||
// },
|
||||
// });
|
||||
// },
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
.underline-hover:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
|
||||
28
manufacturing_overview/public/js/production_plan_addition.js
Normal file
28
manufacturing_overview/public/js/production_plan_addition.js
Normal file
@@ -0,0 +1,28 @@
|
||||
frappe.ui.form.on("Production Plan", {
|
||||
refresh: function (frm) {
|
||||
frm.add_custom_button(__('Fix WO/PO'), function () {
|
||||
frappe.call({
|
||||
method: 'manufacturing_overview.manufacturing_overview.productionplan.fixfinisheditemwopo',
|
||||
args: {
|
||||
production_plan: frm.docname
|
||||
},
|
||||
callback: function (r) {
|
||||
if (r.message.status === 201) {
|
||||
frappe.msgprint({
|
||||
title: __('Created'),
|
||||
indicator: 'green',
|
||||
message: __('Purchase Order {0} created.',
|
||||
['<a href="/app/purchase-order/' + r.message.docname + '">' + r.message.docname + '</a>'])
|
||||
});
|
||||
} else if (r.message.status === 400) {
|
||||
frappe.msgprint({
|
||||
title: __('Already exists'),
|
||||
indicator: 'yellow',
|
||||
message: "Oooopsie"
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
});
|
||||
29
manufacturing_overview/public/js/sales_order_addition.js
Normal file
29
manufacturing_overview/public/js/sales_order_addition.js
Normal file
@@ -0,0 +1,29 @@
|
||||
frappe.ui.form.on("Sales Order", {
|
||||
refresh: function (frm) {
|
||||
frm.add_custom_button(__('Production Plan'), function () {
|
||||
frappe.call({
|
||||
method: 'manufacturing_overview.manufacturing_overview.productionplan.makepp',
|
||||
args: {
|
||||
sales_order: frm.docname
|
||||
},
|
||||
callback: function (r) {
|
||||
if (r.message.status === 201) {
|
||||
frappe.msgprint({
|
||||
title: __('Created'),
|
||||
indicator: 'green',
|
||||
message: __('Production Plan {0} created.',
|
||||
['<a href="/app/production-plan/' + r.message.docname + '">' + r.message.docname + '</a>'])
|
||||
});
|
||||
} else if (r.message.status === 400) {
|
||||
frappe.msgprint({
|
||||
title: __('Already exists'),
|
||||
indicator: 'yellow',
|
||||
message: __('Production Plan {0} already exists.',
|
||||
['<a href="/app/production-plan/' + r.message.docname + '">' + r.message.docname + '</a>'])
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user