Compare commits
5 Commits
f98905f6e6
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
| b20411fce1 | |||
| b14eeff0b3 | |||
| a487804c7c | |||
| ea83e3e1ac | |||
| 3cdcb239c3 |
@@ -1,3 +1,2 @@
|
|||||||
|
|
||||||
__version__ = '0.0.1'
|
__version__ = '14.38.0'
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ required_apps = ["erpnext"]
|
|||||||
|
|
||||||
# include js, css files in header of desk.html
|
# include js, css files in header of desk.html
|
||||||
# app_include_css = "/assets/manufacturing_overview/css/manufacturing_overview.css"
|
# 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
|
# include js, css files in header of web template
|
||||||
# web_include_css = "/assets/manufacturing_overview/css/manufacturing_overview.css"
|
# web_include_css = "/assets/manufacturing_overview/css/manufacturing_overview.css"
|
||||||
|
|||||||
@@ -2,9 +2,12 @@ import frappe
|
|||||||
from functools import reduce
|
from functools import reduce
|
||||||
from datetime import date
|
from datetime import date
|
||||||
|
|
||||||
|
# frappe.utils.logger.set_log_level("DEBUG")
|
||||||
|
# logger = frappe.logger("manufacturing_overview",
|
||||||
|
# allow_site=True, file_count=100000)
|
||||||
|
|
||||||
|
# logger.debug(f"String")
|
||||||
|
|
||||||
def clearCache(doc, event):
|
|
||||||
frappe.cache().delete_value("production_overview")
|
|
||||||
|
|
||||||
def getDueInDays(d):
|
def getDueInDays(d):
|
||||||
delta = d - date.today()
|
delta = d - date.today()
|
||||||
@@ -31,7 +34,8 @@ def shortenCustomerName(customer):
|
|||||||
else:
|
else:
|
||||||
return customer
|
return customer
|
||||||
|
|
||||||
def generateProductionOverviewCache():
|
|
||||||
|
def generateProductionOverviewCacheData():
|
||||||
salesOrderItems = frappe.db.sql(
|
salesOrderItems = frappe.db.sql(
|
||||||
"""
|
"""
|
||||||
SELECT
|
SELECT
|
||||||
@@ -45,6 +49,7 @@ def generateProductionOverviewCache():
|
|||||||
`tabSales Order Item`.item_code,
|
`tabSales Order Item`.item_code,
|
||||||
`tabSales Order Item`.work_order_qty,
|
`tabSales Order Item`.work_order_qty,
|
||||||
`tabSales Order Item`.parent,
|
`tabSales Order Item`.parent,
|
||||||
|
`tabSales Order Item`.production_plan_qty,
|
||||||
`tabSales Order`.status as parentStatus
|
`tabSales Order`.status as parentStatus
|
||||||
FROM
|
FROM
|
||||||
`tabSales Order Item`
|
`tabSales Order Item`
|
||||||
@@ -59,38 +64,67 @@ def generateProductionOverviewCache():
|
|||||||
delivery_date, item_code, name
|
delivery_date, item_code, name
|
||||||
""", as_dict=1)
|
""", as_dict=1)
|
||||||
|
|
||||||
|
currentWarehouseQtyList = []
|
||||||
|
|
||||||
for soItem in salesOrderItems:
|
for soItem in salesOrderItems:
|
||||||
|
soItem.currentWarehouseQty = calculateCurrentWarehouseQty(
|
||||||
|
soItem.item_code, soItem.qty, currentWarehouseQtyList)
|
||||||
|
|
||||||
|
soItem.totalWarehouseQty = getAmountInWarehouses(soItem.item_code)
|
||||||
|
|
||||||
soItem.due_in = getDueInDays(soItem.delivery_date)
|
soItem.due_in = getDueInDays(soItem.delivery_date)
|
||||||
|
|
||||||
soItem.customer = shortenCustomerName(frappe.get_value(
|
soItem.customer = shortenCustomerName(frappe.get_value(
|
||||||
'Sales Order', soItem.parent, 'customer'))
|
'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 = frappe.utils.formatdate(soItem.delivery_date, 'dd.MM.yyyy')
|
|
||||||
soItem.warehouseamount = getAmountInWarehouses(soItem.item_code)
|
|
||||||
|
|
||||||
if soItem.delivered_qty > 0 and soItem.delivered_qty < soItem.qty:
|
|
||||||
soItem.status = 'Partially Delivered'
|
|
||||||
elif soItem.delivered_qty >= soItem.qty:
|
|
||||||
soItem.status = 'Fully Delivered'
|
|
||||||
elif soItem.warehouseamount >= soItem.qty:
|
|
||||||
soItem.status = 'In Warehouse'
|
|
||||||
elif soItem.work_order_qty > 0:
|
|
||||||
soItem.status = 'To Produce'
|
|
||||||
else:
|
|
||||||
soItem.status = 'No Work Order'
|
|
||||||
|
|
||||||
|
soItem = formatDate(soItem)
|
||||||
|
soItem = calculateStatus(soItem)
|
||||||
soItem.qty = soItem.qty - soItem.delivered_qty
|
soItem.qty = soItem.qty - soItem.delivered_qty
|
||||||
|
|
||||||
soItem.link = '/app/sales-order/' + soItem.parent
|
soItem.link = '/app/sales-order/' + soItem.parent
|
||||||
|
|
||||||
frappe.cache().set_value("production_overview", salesOrderItems)
|
return salesOrderItems
|
||||||
|
|
||||||
|
|
||||||
|
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.delivery_date = frappe.utils.formatdate(
|
||||||
|
item.delivery_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()
|
@frappe.whitelist()
|
||||||
def getSalesorderOverviewList():
|
def getSalesorderOverviewList():
|
||||||
salesOrderItems = frappe.cache().get_value(
|
salesOrderItems = frappe.cache().get_value('production_overview', expires=True)
|
||||||
"production_overview", generateProductionOverviewCache())
|
|
||||||
|
if salesOrderItems is None:
|
||||||
|
salesOrderItems = generateProductionOverviewCacheData()
|
||||||
|
frappe.cache().set_value('production_overview', salesOrderItems, expires_in_sec=60)
|
||||||
|
|
||||||
return salesOrderItems
|
return salesOrderItems
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import frappe
|
||||||
|
from erpnext.stock.get_item_details import get_default_bom
|
||||||
|
|
||||||
|
# frappe.utils.logger.set_log_level("DEBUG")
|
||||||
|
# logger = frappe.logger("manufacturing_overview",
|
||||||
|
# allow_site=True, file_count=50)
|
||||||
|
|
||||||
|
# logger.debug(f"{current_value} + {value} = {updated_value}")
|
||||||
|
|
||||||
|
|
||||||
|
@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]}
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"manufacturing_overview/js/manufacturing_overview.min.js": [
|
|
||||||
"public/js/manufacturing_overview_page.js"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import './manufacturing_overview_desk.vue';
|
||||||
|
import './manufacturing_overview_page.js';
|
||||||
|
import './manufacturing_overview_row.vue';
|
||||||
|
import './production_plan.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"}
|
|
||||||
@@ -3,25 +3,24 @@
|
|||||||
<div class="layout-main-section">
|
<div class="layout-main-section">
|
||||||
<div class="widget links-widget-box" style="height: auto">
|
<div class="widget links-widget-box" style="height: auto">
|
||||||
<div class="widget-head">
|
<div class="widget-head">
|
||||||
<div>
|
<div class="widget-label">
|
||||||
|
<div class="widget-title"><svg class="icon icon-lg" style="">
|
||||||
|
<!-- <use class="" href="#icon-file"></use> -->
|
||||||
|
</svg> <span class="ellipsis" title="Manufacturing OVerview">Manufacturing Overview</span>
|
||||||
|
<!-- <span>
|
||||||
|
<button class="btn btn-secondary btn-default btn-sm" data-label="Mark sent">Mark
|
||||||
|
sent</button>
|
||||||
|
</span> -->
|
||||||
|
</div>
|
||||||
<div class="widget-subtitle"></div>
|
<div class="widget-subtitle"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="widget-control"></div>
|
<div class="widget-control"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="widget-body">
|
<div class="widget-body">
|
||||||
<manufacturing-overview-row
|
<manufacturing-overview-row v-for="so in salesorderData" :key="so.name" v-bind:qty="so.qty"
|
||||||
v-for="so in salesorderData"
|
v-bind:item_name="so.item_name" v-bind:item_code="so.item_code" v-bind:customer="so.customer"
|
||||||
:key="so.name"
|
v-bind:delivery_date="so.delivery_date" v-bind:status="so.status" v-bind:link="so.link"
|
||||||
v-bind:qty="so.qty"
|
v-bind:reference="so.parent" v-bind:due_in="so.due_in">
|
||||||
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:status="so.status"
|
|
||||||
v-bind:link="so.link"
|
|
||||||
v-bind:reference="so.parent"
|
|
||||||
v-bind:due_in="so.due_in"
|
|
||||||
>
|
|
||||||
</manufacturing-overview-row>
|
</manufacturing-overview-row>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -59,7 +58,7 @@ export default {
|
|||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.fetchEventsList();
|
this.fetchEventsList();
|
||||||
this.timer = setInterval(this.fetchEventsList, 30000);
|
this.timer = setInterval(this.fetchEventsList, 10000);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
fetchEventsList() {
|
fetchEventsList() {
|
||||||
@@ -86,5 +85,4 @@ export default {
|
|||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style></style>
|
||||||
</style>
|
|
||||||
31
manufacturing_overview/public/js/production_plan.js
Normal file
31
manufacturing_overview/public/js/production_plan.js
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
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>'])
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log(r.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user