feat: Production Planning Against Sales Order/Material Request/Work Order (#21763)
Co-authored-by: Marica <maricadsouza221197@gmail.com>
This commit is contained in:
parent
ddec05ee07
commit
aa85e511da
@ -13,7 +13,7 @@
|
||||
{
|
||||
"hidden": 0,
|
||||
"label": "Reports",
|
||||
"links": "[{\n\t\"dependencies\": [\"Work Order\"],\n\t\"name\": \"Work Order Summary\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"Work Order\",\n\t\"label\": \"Work Order Summary\"\n}, {\n\t\"dependencies\": [\"Work Order\"],\n\t\"name\": \"Production Analytics\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"Work Order\",\n\t\"label\": \"Production Analytics\"\n}, {\n\t\"dependencies\": [\"Quality Inspection\"],\n\t\"name\": \"Quality Inspection Summary\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"Quality Inspection\",\n\t\"label\": \"Quality Inspection Summary\"\n}, {\n\t\"dependencies\": [\"Downtime Entry\"],\n\t\"name\": \"Downtime Analysis\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"Downtime Entry\",\n\t\"label\": \"Downtime Analysis\"\n}, {\n\t\"dependencies\": [\"Job Card\"],\n\t\"name\": \"Job Card Summary\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"Job Card\",\n\t\"label\": \"Job Card Summary\"\n}, {\n\t\"dependencies\": [\"BOM\"],\n\t\"name\": \"BOM Search\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"BOM\",\n\t\"label\": \"BOM Search\"\n}, {\n\t\"dependencies\": [\"BOM\"],\n\t\"name\": \"BOM Stock Report\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"BOM\",\n\t\"label\": \"BOM Stock Report\"\n}]"
|
||||
"links": "[{\n\t\"dependencies\": [\"Work Order\"],\n\t\"name\": \"Production Planning Report\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"Work Order\",\n\t\"label\": \"Production Planning Report\"\n}, {\n\t\"dependencies\": [\"Work Order\"],\n\t\"name\": \"Work Order Summary\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"Work Order\",\n\t\"label\": \"Work Order Summary\"\n}, {\n\t\"dependencies\": [\"Quality Inspection\"],\n\t\"name\": \"Quality Inspection Summary\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"Quality Inspection\",\n\t\"label\": \"Quality Inspection Summary\"\n}, {\n\t\"dependencies\": [\"Downtime Entry\"],\n\t\"name\": \"Downtime Analysis\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"Downtime Entry\",\n\t\"label\": \"Downtime Analysis\"\n}, {\n\t\"dependencies\": [\"Job Card\"],\n\t\"name\": \"Job Card Summary\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"Job Card\",\n\t\"label\": \"Job Card Summary\"\n}, {\n\t\"dependencies\": [\"BOM\"],\n\t\"name\": \"BOM Search\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"BOM\",\n\t\"label\": \"BOM Search\"\n}, {\n\t\"dependencies\": [\"BOM\"],\n\t\"name\": \"BOM Stock Report\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"BOM\",\n\t\"label\": \"BOM Stock Report\"\n}, {\n\t\"dependencies\": [\"Work Order\"],\n\t\"name\": \"Production Analytics\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"Work Order\",\n\t\"label\": \"Production Analytics\"\n}, {\n\t\"dependencies\": [\"BOM\"],\n\t\"name\": \"BOM Operations Time\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"BOM\",\n\t\"label\": \"BOM Operations Time\"\n}]"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
@ -46,7 +46,7 @@
|
||||
"idx": 0,
|
||||
"is_standard": 1,
|
||||
"label": "Manufacturing",
|
||||
"modified": "2020-05-19 12:54:04.104444",
|
||||
"modified": "2020-05-19 14:05:59.100891",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Manufacturing",
|
||||
|
@ -0,0 +1,9 @@
|
||||
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
/* eslint-disable */
|
||||
|
||||
frappe.query_reports["BOM Operations Time"] = {
|
||||
"filters": [
|
||||
|
||||
]
|
||||
};
|
@ -0,0 +1,28 @@
|
||||
{
|
||||
"add_total_row": 0,
|
||||
"creation": "2020-03-03 01:41:20.862521",
|
||||
"disable_prepared_report": 0,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"idx": 0,
|
||||
"is_standard": "Yes",
|
||||
"letter_head": "",
|
||||
"modified": "2020-03-03 01:41:20.862521",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "BOM Operations Time",
|
||||
"owner": "Administrator",
|
||||
"prepared_report": 0,
|
||||
"ref_doctype": "BOM",
|
||||
"report_name": "BOM Operations Time",
|
||||
"report_type": "Script Report",
|
||||
"roles": [
|
||||
{
|
||||
"role": "Manufacturing Manager"
|
||||
},
|
||||
{
|
||||
"role": "Manufacturing User"
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,112 @@
|
||||
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
|
||||
def execute(filters=None):
|
||||
data = get_data(filters)
|
||||
columns = get_columns(filters)
|
||||
return columns, data
|
||||
|
||||
def get_data(filters):
|
||||
data = []
|
||||
|
||||
bom_data = []
|
||||
for d in frappe.db.sql("""
|
||||
SELECT
|
||||
bom.name, bom.item, bom.item_name, bom.uom,
|
||||
bomps.operation, bomps.workstation, bomps.time_in_mins
|
||||
FROM `tabBOM` bom, `tabBOM Operation` bomps
|
||||
WHERE
|
||||
bom.docstatus = 1 and bom.is_active = 1 and bom.name = bomps.parent
|
||||
""", as_dict=1):
|
||||
row = get_args()
|
||||
if d.name not in bom_data:
|
||||
bom_data.append(d.name)
|
||||
row.update(d)
|
||||
else:
|
||||
row.update({
|
||||
"operation": d.operation,
|
||||
"workstation": d.workstation,
|
||||
"time_in_mins": d.time_in_mins
|
||||
})
|
||||
|
||||
data.append(row)
|
||||
|
||||
used_as_subassembly_items = get_bom_count(bom_data)
|
||||
|
||||
for d in data:
|
||||
d.used_as_subassembly_items = used_as_subassembly_items.get(d.name, 0)
|
||||
|
||||
return data
|
||||
|
||||
def get_bom_count(bom_data):
|
||||
data = frappe.get_all("BOM Item",
|
||||
fields=["count(name) as count", "bom_no"],
|
||||
filters= {"bom_no": ("in", bom_data)}, group_by = "bom_no")
|
||||
|
||||
bom_count = {}
|
||||
for d in data:
|
||||
bom_count.setdefault(d.bom_no, d.count)
|
||||
|
||||
return bom_count
|
||||
|
||||
def get_args():
|
||||
return frappe._dict({
|
||||
"name": "",
|
||||
"item": "",
|
||||
"item_name": "",
|
||||
"uom": ""
|
||||
})
|
||||
|
||||
def get_columns(filters):
|
||||
return [{
|
||||
"label": _("BOM ID"),
|
||||
"options": "BOM",
|
||||
"fieldname": "name",
|
||||
"fieldtype": "Link",
|
||||
"width": 140
|
||||
}, {
|
||||
"label": _("BOM Item Code"),
|
||||
"options": "Item",
|
||||
"fieldname": "item",
|
||||
"fieldtype": "Link",
|
||||
"width": 140
|
||||
}, {
|
||||
"label": _("Item Name"),
|
||||
"fieldname": "item_name",
|
||||
"fieldtype": "Data",
|
||||
"width": 110
|
||||
}, {
|
||||
"label": _("UOM"),
|
||||
"options": "UOM",
|
||||
"fieldname": "uom",
|
||||
"fieldtype": "Link",
|
||||
"width": 140
|
||||
}, {
|
||||
"label": _("Operation"),
|
||||
"options": "Operation",
|
||||
"fieldname": "operation",
|
||||
"fieldtype": "Link",
|
||||
"width": 120
|
||||
}, {
|
||||
"label": _("Workstation"),
|
||||
"options": "Workstation",
|
||||
"fieldname": "workstation",
|
||||
"fieldtype": "Link",
|
||||
"width": 110
|
||||
}, {
|
||||
"label": _("Time (In Mins)"),
|
||||
"fieldname": "time_in_mins",
|
||||
"fieldtype": "Int",
|
||||
"width": 140
|
||||
}, {
|
||||
"label": _("Subassembly BOM Count"),
|
||||
"fieldname": "used_as_subassembly_items",
|
||||
"fieldtype": "Int",
|
||||
"width": 180
|
||||
}]
|
||||
|
||||
|
@ -0,0 +1,111 @@
|
||||
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
/* eslint-disable */
|
||||
|
||||
frappe.query_reports["Production Planning Report"] = {
|
||||
"filters": [
|
||||
{
|
||||
"fieldname":"company",
|
||||
"label": __("Company"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Company",
|
||||
"reqd": 1,
|
||||
"default": frappe.defaults.get_user_default("Company")
|
||||
},
|
||||
{
|
||||
"fieldname":"based_on",
|
||||
"label": __("Based On"),
|
||||
"fieldtype": "Select",
|
||||
"options": ["Sales Order", "Material Request", "Work Order"],
|
||||
"default": "Sales Order",
|
||||
"reqd": 1,
|
||||
on_change: function() {
|
||||
let filters = frappe.query_report.filters;
|
||||
let based_on = frappe.query_report.get_filter_value('based_on');
|
||||
let options = {
|
||||
"Sales Order": ["Delivery Date", "Total Amount"],
|
||||
"Material Request": ["Required Date"],
|
||||
"Work Order": ["Planned Start Date"]
|
||||
}
|
||||
|
||||
filters.forEach(d => {
|
||||
if (d.fieldname == "order_by") {
|
||||
d.df.options = options[based_on];
|
||||
d.set_input(d.df.options)
|
||||
}
|
||||
});
|
||||
|
||||
frappe.query_report.refresh();
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname":"docnames",
|
||||
"label": __("Document Name"),
|
||||
"fieldtype": "MultiSelectList",
|
||||
"options": "Sales Order",
|
||||
"get_data": function(txt) {
|
||||
if (!frappe.query_report.filters) return;
|
||||
|
||||
let based_on = frappe.query_report.get_filter_value('based_on');
|
||||
if (!based_on) return;
|
||||
|
||||
return frappe.db.get_link_options(based_on, txt);
|
||||
},
|
||||
"get_query": function() {
|
||||
var company = frappe.query_report.get_filter_value('company');
|
||||
return {
|
||||
filters: {
|
||||
"docstatus": 1,
|
||||
"company": company
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname":"raw_material_warehouse",
|
||||
"label": __("Raw Material Warehouse"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Warehouse",
|
||||
"depends_on": "eval: doc.based_on != 'Work Order'",
|
||||
"get_query": function() {
|
||||
var company = frappe.query_report.get_filter_value('company');
|
||||
return {
|
||||
filters: {
|
||||
"company": company
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname":"order_by",
|
||||
"label": __("Order By"),
|
||||
"fieldtype": "Select",
|
||||
"options": ["Delivery Date", "Total Amount"],
|
||||
"default": "Delivery Date"
|
||||
},
|
||||
{
|
||||
"fieldname":"include_subassembly_raw_materials",
|
||||
"label": __("Include Sub-assembly Raw Materials"),
|
||||
"fieldtype": "Check",
|
||||
"depends_on": "eval: doc.based_on != 'Work Order'",
|
||||
"default": 0
|
||||
},
|
||||
],
|
||||
"formatter": function(value, row, column, data, default_formatter) {
|
||||
value = default_formatter(value, row, column, data);
|
||||
|
||||
if (column.fieldname == "production_item_name" && data && data.qty_to_manufacture > data.available_qty ) {
|
||||
value = `<div style="color:red">${value}</div>`;
|
||||
}
|
||||
|
||||
if (column.fieldname == "production_item" && !data.name ) {
|
||||
value = "";
|
||||
}
|
||||
|
||||
if (column.fieldname == "raw_material_name" && data && data.required_qty > data.allotted_qty ) {
|
||||
value = `<div style="color:red">${value}</div>`;
|
||||
}
|
||||
|
||||
return value;
|
||||
},
|
||||
};
|
@ -0,0 +1,31 @@
|
||||
{
|
||||
"add_total_row": 0,
|
||||
"creation": "2020-03-06 11:37:43.180095",
|
||||
"disable_prepared_report": 0,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"idx": 0,
|
||||
"is_standard": "Yes",
|
||||
"letter_head": "",
|
||||
"modified": "2020-03-06 11:38:05.789851",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Production Planning Report",
|
||||
"owner": "Administrator",
|
||||
"prepared_report": 0,
|
||||
"ref_doctype": "Work Order",
|
||||
"report_name": "Production Planning Report",
|
||||
"report_type": "Script Report",
|
||||
"roles": [
|
||||
{
|
||||
"role": "Manufacturing User"
|
||||
},
|
||||
{
|
||||
"role": "Stock User"
|
||||
},
|
||||
{
|
||||
"role": "Manufacturing Manager"
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,371 @@
|
||||
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses
|
||||
|
||||
# and bom_no is not null and bom_no !=''
|
||||
|
||||
mapper = {
|
||||
"Sales Order": {
|
||||
"fields": """ item_code as production_item, item_name as production_item_name, stock_uom,
|
||||
stock_qty as qty_to_manufacture, `tabSales Order Item`.parent as name, bom_no, warehouse,
|
||||
`tabSales Order Item`.delivery_date, `tabSales Order`.base_grand_total """,
|
||||
"filters": """`tabSales Order Item`.docstatus = 1 and stock_qty > produced_qty
|
||||
and `tabSales Order`.per_delivered < 100.0"""
|
||||
},
|
||||
"Material Request": {
|
||||
"fields": """ item_code as production_item, item_name as production_item_name, stock_uom,
|
||||
stock_qty as qty_to_manufacture, `tabMaterial Request Item`.parent as name, bom_no, warehouse,
|
||||
`tabMaterial Request Item`.schedule_date """,
|
||||
"filters": """`tabMaterial Request`.docstatus = 1 and `tabMaterial Request`.per_ordered < 100
|
||||
and `tabMaterial Request`.material_request_type = 'Manufacture' """
|
||||
},
|
||||
"Work Order": {
|
||||
"fields": """ production_item, item_name as production_item_name, planned_start_date,
|
||||
stock_uom, qty as qty_to_manufacture, name, bom_no, fg_warehouse as warehouse """,
|
||||
"filters": "docstatus = 1 and status not in ('Completed', 'Stopped')"
|
||||
},
|
||||
}
|
||||
|
||||
order_mapper = {
|
||||
"Sales Order": {
|
||||
"Delivery Date": "`tabSales Order Item`.delivery_date asc",
|
||||
"Total Amount": "`tabSales Order`.base_grand_total desc"
|
||||
},
|
||||
"Material Request": {
|
||||
"Required Date": "`tabMaterial Request Item`.schedule_date asc"
|
||||
},
|
||||
"Work Order": {
|
||||
"Planned Start Date": "planned_start_date asc"
|
||||
}
|
||||
}
|
||||
|
||||
def execute(filters=None):
|
||||
return ProductionPlanReport(filters).execute_report()
|
||||
|
||||
class ProductionPlanReport(object):
|
||||
def __init__(self, filters=None):
|
||||
self.filters = frappe._dict(filters or {})
|
||||
self.raw_materials_dict = {}
|
||||
self.data = []
|
||||
|
||||
def execute_report(self):
|
||||
self.get_open_orders()
|
||||
self.get_raw_materials()
|
||||
self.get_item_details()
|
||||
self.get_bin_details()
|
||||
self.get_purchase_details()
|
||||
self.prepare_data()
|
||||
self.get_columns()
|
||||
|
||||
return self.columns, self.data
|
||||
|
||||
def get_open_orders(self):
|
||||
doctype = ("`tabWork Order`" if self.filters.based_on == "Work Order"
|
||||
else "`tab{doc}`, `tab{doc} Item`".format(doc=self.filters.based_on))
|
||||
|
||||
filters = mapper.get(self.filters.based_on)["filters"]
|
||||
filters = self.prepare_other_conditions(filters, self.filters.based_on)
|
||||
order_by = " ORDER BY %s" % (order_mapper[self.filters.based_on][self.filters.order_by])
|
||||
|
||||
self.orders = frappe.db.sql(""" SELECT {fields} from {doctype}
|
||||
WHERE {filters} {order_by}""".format(
|
||||
doctype = doctype,
|
||||
filters = filters,
|
||||
order_by = order_by,
|
||||
fields = mapper.get(self.filters.based_on)["fields"]
|
||||
), tuple(self.filters.docnames), as_dict=1)
|
||||
|
||||
def prepare_other_conditions(self, filters, doctype):
|
||||
if self.filters.docnames:
|
||||
field = "name" if doctype == "Work Order" else "`tab{} Item`.parent".format(doctype)
|
||||
filters += " and %s in (%s)" % (field, ','.join(['%s'] * len(self.filters.docnames)))
|
||||
|
||||
if doctype != "Work Order":
|
||||
filters += " and `tab{doc}`.name = `tab{doc} Item`.parent".format(doc=doctype)
|
||||
|
||||
if self.filters.company:
|
||||
filters += " and `tab%s`.company = %s" %(doctype, frappe.db.escape(self.filters.company))
|
||||
|
||||
return filters
|
||||
|
||||
def get_raw_materials(self):
|
||||
if not self.orders: return
|
||||
self.warehouses = [d.warehouse for d in self.orders]
|
||||
self.item_codes = [d.production_item for d in self.orders]
|
||||
|
||||
if self.filters.based_on == "Work Order":
|
||||
work_orders = [d.name for d in self.orders]
|
||||
|
||||
raw_materials = frappe.get_all("Work Order Item",
|
||||
fields=["parent", "item_code", "item_name as raw_material_name",
|
||||
"source_warehouse as warehouse", "required_qty"],
|
||||
filters = {"docstatus": 1, "parent": ("in", work_orders), "source_warehouse": ("!=", "")}) or []
|
||||
self.warehouses.extend([d.source_warehouse for d in raw_materials])
|
||||
|
||||
else:
|
||||
bom_nos = []
|
||||
|
||||
for d in self.orders:
|
||||
bom_no = d.bom_no or frappe.get_cached_value("Item", d.production_item, "default_bom")
|
||||
|
||||
if not d.bom_no:
|
||||
d.bom_no = bom_no
|
||||
|
||||
bom_nos.append(bom_no)
|
||||
|
||||
bom_doctype = ("BOM Explosion Item"
|
||||
if self.filters.include_subassembly_raw_materials else "BOM Item")
|
||||
|
||||
qty_field = ("qty_consumed_per_unit"
|
||||
if self.filters.include_subassembly_raw_materials else "(bom_item.qty / bom.quantity)")
|
||||
|
||||
raw_materials = frappe.db.sql(""" SELECT bom_item.parent, bom_item.item_code,
|
||||
bom_item.item_name as raw_material_name, {0} as required_qty
|
||||
FROM
|
||||
`tabBOM` as bom, `tab{1}` as bom_item
|
||||
WHERE
|
||||
bom_item.parent in ({2}) and bom_item.parent = bom.name and bom.docstatus = 1
|
||||
""".format(qty_field, bom_doctype, ','.join(["%s"] * len(bom_nos))), tuple(bom_nos), as_dict=1)
|
||||
|
||||
if not raw_materials: return
|
||||
|
||||
self.item_codes.extend([d.item_code for d in raw_materials])
|
||||
|
||||
for d in raw_materials:
|
||||
if d.parent not in self.raw_materials_dict:
|
||||
self.raw_materials_dict.setdefault(d.parent, [])
|
||||
|
||||
rows = self.raw_materials_dict[d.parent]
|
||||
rows.append(d)
|
||||
|
||||
def get_item_details(self):
|
||||
if not (self.orders and self.item_codes): return
|
||||
|
||||
self.item_details = {}
|
||||
for d in frappe.get_all("Item Default", fields = ["parent", "default_warehouse"],
|
||||
filters = {"company": self.filters.company, "parent": ("in", self.item_codes)}):
|
||||
self.item_details[d.parent] = d
|
||||
|
||||
def get_bin_details(self):
|
||||
if not (self.orders and self.raw_materials_dict): return
|
||||
|
||||
self.bin_details = {}
|
||||
self.mrp_warehouses = []
|
||||
if self.filters.raw_material_warehouse:
|
||||
self.mrp_warehouses.extend(get_child_warehouses(self.filters.raw_material_warehouse))
|
||||
self.warehouses.extend(self.mrp_warehouses)
|
||||
|
||||
for d in frappe.get_all("Bin",
|
||||
fields=["warehouse", "item_code", "actual_qty", "ordered_qty", "projected_qty"],
|
||||
filters = {"item_code": ("in", self.item_codes), "warehouse": ("in", self.warehouses)}):
|
||||
key = (d.item_code, d.warehouse)
|
||||
if key not in self.bin_details:
|
||||
self.bin_details.setdefault(key, d)
|
||||
|
||||
def get_purchase_details(self):
|
||||
if not (self.orders and self.raw_materials_dict): return
|
||||
|
||||
self.purchase_details = {}
|
||||
|
||||
for d in frappe.get_all("Purchase Order Item",
|
||||
fields=["item_code", "min(schedule_date) as arrival_date", "qty as arrival_qty", "warehouse"],
|
||||
filters = {"item_code": ("in", self.item_codes), "warehouse": ("in", self.warehouses)},
|
||||
group_by = "item_code, warehouse"):
|
||||
key = (d.item_code, d.warehouse)
|
||||
if key not in self.purchase_details:
|
||||
self.purchase_details.setdefault(key, d)
|
||||
|
||||
def prepare_data(self):
|
||||
if not self.orders: return
|
||||
|
||||
for d in self.orders:
|
||||
key = d.name if self.filters.based_on == "Work Order" else d.bom_no
|
||||
|
||||
if not self.raw_materials_dict.get(key): continue
|
||||
|
||||
bin_data = self.bin_details.get((d.production_item, d.warehouse)) or {}
|
||||
d.update({
|
||||
"for_warehouse": d.warehouse,
|
||||
"available_qty": 0
|
||||
})
|
||||
|
||||
if bin_data and bin_data.get("actual_qty") > 0 and d.qty_to_manufacture:
|
||||
d.available_qty = (bin_data.get("actual_qty")
|
||||
if (d.qty_to_manufacture > bin_data.get("actual_qty")) else d.qty_to_manufacture)
|
||||
|
||||
bin_data["actual_qty"] -= d.available_qty
|
||||
|
||||
self.update_raw_materials(d, key)
|
||||
|
||||
def update_raw_materials(self, data, key):
|
||||
self.index = 0
|
||||
self.raw_materials_dict.get(key)
|
||||
|
||||
warehouses = self.mrp_warehouses or []
|
||||
for d in self.raw_materials_dict.get(key):
|
||||
if self.filters.based_on != "Work Order":
|
||||
d.required_qty = d.required_qty * data.qty_to_manufacture
|
||||
|
||||
if not warehouses:
|
||||
warehouses = [data.warehouse]
|
||||
|
||||
if self.filters.based_on == "Work Order" and d.warehouse:
|
||||
warehouses = [d.warehouse]
|
||||
else:
|
||||
item_details = self.item_details.get(d.item_code)
|
||||
if item_details:
|
||||
warehouses = [item_details["default_warehouse"]]
|
||||
|
||||
d.remaining_qty = d.required_qty
|
||||
self.pick_materials_from_warehouses(d, data, warehouses)
|
||||
|
||||
if (d.remaining_qty and self.filters.raw_material_warehouse
|
||||
and d.remaining_qty != d.required_qty):
|
||||
row = self.get_args()
|
||||
d.warehouse = self.filters.raw_material_warehouse
|
||||
d.required_qty = d.remaining_qty
|
||||
d.allotted_qty = 0
|
||||
row.update(d)
|
||||
self.data.append(row)
|
||||
|
||||
def pick_materials_from_warehouses(self, args, order_data, warehouses):
|
||||
for index, warehouse in enumerate(warehouses):
|
||||
if not args.remaining_qty: return
|
||||
|
||||
row = self.get_args()
|
||||
|
||||
key = (args.item_code, warehouse)
|
||||
bin_data = self.bin_details.get(key)
|
||||
|
||||
if bin_data:
|
||||
row.update(bin_data)
|
||||
|
||||
args.allotted_qty = 0
|
||||
if bin_data and bin_data.get("actual_qty") > 0:
|
||||
args.allotted_qty = (bin_data.get("actual_qty")
|
||||
if (args.required_qty > bin_data.get("actual_qty")) else args.required_qty)
|
||||
|
||||
args.remaining_qty -= args.allotted_qty
|
||||
bin_data["actual_qty"] -= args.allotted_qty
|
||||
|
||||
if ((self.mrp_warehouses and (args.allotted_qty or index == len(warehouses) - 1))
|
||||
or not self.mrp_warehouses):
|
||||
if not self.index:
|
||||
row.update(order_data)
|
||||
self.index += 1
|
||||
|
||||
args.warehouse = warehouse
|
||||
row.update(args)
|
||||
if self.purchase_details.get(key):
|
||||
row.update(self.purchase_details.get(key))
|
||||
|
||||
self.data.append(row)
|
||||
|
||||
def get_args(self):
|
||||
return frappe._dict({
|
||||
"work_order": "",
|
||||
"sales_order": "",
|
||||
"production_item": "",
|
||||
"production_item_name": "",
|
||||
"qty_to_manufacture": "",
|
||||
"produced_qty": ""
|
||||
})
|
||||
|
||||
def get_columns(self):
|
||||
based_on = self.filters.based_on
|
||||
|
||||
self.columns = [{
|
||||
"label": _("ID"),
|
||||
"options": based_on,
|
||||
"fieldname": "name",
|
||||
"fieldtype": "Link",
|
||||
"width": 100
|
||||
}, {
|
||||
"label": _("Item Code"),
|
||||
"fieldname": "production_item",
|
||||
"fieldtype": "Link",
|
||||
"options": "Item",
|
||||
"width": 120
|
||||
}, {
|
||||
"label": _("Item Name"),
|
||||
"fieldname": "production_item_name",
|
||||
"fieldtype": "Data",
|
||||
"width": 130
|
||||
}, {
|
||||
"label": _("Warehouse"),
|
||||
"options": "Warehouse",
|
||||
"fieldname": "for_warehouse",
|
||||
"fieldtype": "Link",
|
||||
"width": 100
|
||||
}, {
|
||||
"label": _("Order Qty"),
|
||||
"fieldname": "qty_to_manufacture",
|
||||
"fieldtype": "Float",
|
||||
"width": 80
|
||||
}, {
|
||||
"label": _("Available"),
|
||||
"fieldname": "available_qty",
|
||||
"fieldtype": "Float",
|
||||
"width": 80
|
||||
}]
|
||||
|
||||
fieldname, fieldtype = "delivery_date", "Date"
|
||||
if self.filters.based_on == "Sales Order" and self.filters.order_by == "Total Amount":
|
||||
fieldname, fieldtype = "base_grand_total", "Currency"
|
||||
elif self.filters.based_on == "Material Request":
|
||||
fieldname = "schedule_date"
|
||||
elif self.filters.based_on == "Work Order":
|
||||
fieldname = "planned_start_date"
|
||||
|
||||
self.columns.append({
|
||||
"label": _(self.filters.order_by),
|
||||
"fieldname": fieldname,
|
||||
"fieldtype": fieldtype,
|
||||
"width": 100
|
||||
})
|
||||
|
||||
self.columns.extend([{
|
||||
"label": _("Raw Material Code"),
|
||||
"fieldname": "item_code",
|
||||
"fieldtype": "Link",
|
||||
"options": "Item",
|
||||
"width": 120
|
||||
}, {
|
||||
"label": _("Raw Material Name"),
|
||||
"fieldname": "raw_material_name",
|
||||
"fieldtype": "Data",
|
||||
"width": 130
|
||||
}, {
|
||||
"label": _("Warehouse"),
|
||||
"options": "Warehouse",
|
||||
"fieldname": "warehouse",
|
||||
"fieldtype": "Link",
|
||||
"width": 110
|
||||
}, {
|
||||
"label": _("Required Qty"),
|
||||
"fieldname": "required_qty",
|
||||
"fieldtype": "Float",
|
||||
"width": 100
|
||||
}, {
|
||||
"label": _("Allotted Qty"),
|
||||
"fieldname": "allotted_qty",
|
||||
"fieldtype": "Float",
|
||||
"width": 100
|
||||
}, {
|
||||
"label": _("Expected Arrival Date"),
|
||||
"fieldname": "arrival_date",
|
||||
"fieldtype": "Date",
|
||||
"width": 160
|
||||
}, {
|
||||
"label": _("Arrival Quantity"),
|
||||
"fieldname": "arrival_qty",
|
||||
"fieldtype": "Float",
|
||||
"width": 140
|
||||
}])
|
||||
|
||||
def document_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
pass
|
@ -688,3 +688,4 @@ erpnext.patches.v12_0.set_serial_no_status
|
||||
erpnext.patches.v12_0.update_price_list_currency_in_bom
|
||||
execute:frappe.delete_doc_if_exists('Dashboard', 'Accounts')
|
||||
erpnext.patches.v13_0.update_actual_start_and_end_date_in_wo
|
||||
erpnext.patches.v12_0.update_bom_in_so_mr
|
||||
|
19
erpnext/patches/v12_0/update_bom_in_so_mr.py
Normal file
19
erpnext/patches/v12_0/update_bom_in_so_mr.py
Normal file
@ -0,0 +1,19 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
def execute():
|
||||
frappe.reload_doc("stock", "doctype", "material_request_item")
|
||||
frappe.reload_doc("selling", "doctype", "sales_order_item")
|
||||
|
||||
for doctype in ["Sales Order", "Material Request"]:
|
||||
condition = " and child_doc.stock_qty > child_doc.produced_qty"
|
||||
if doctype == "Material Request":
|
||||
condition = " and doc.per_ordered < 100 and doc.material_request_type = 'Manufacture'"
|
||||
|
||||
frappe.db.sql(""" UPDATE `tab{doc}` as doc, `tab{doc} Item` as child_doc, tabItem as item
|
||||
SET
|
||||
child_doc.bom_no = item.default_bom
|
||||
WHERE
|
||||
child_doc.item_code = item.name and child_doc.docstatus < 2
|
||||
and item.default_bom is not null and item.default_bom != '' {cond}
|
||||
""".format(doc = doctype, cond = condition))
|
@ -34,6 +34,15 @@ frappe.ui.form.on("Sales Order", {
|
||||
}
|
||||
};
|
||||
})
|
||||
|
||||
frm.set_query("bom_no", "items", function(doc, cdt, cdn) {
|
||||
var row = locals[cdt][cdn];
|
||||
return {
|
||||
filters: {
|
||||
"item": row.item_code
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
refresh: function(frm) {
|
||||
if(frm.doc.docstatus === 1 && frm.doc.status !== 'Closed'
|
||||
|
@ -72,6 +72,8 @@
|
||||
"against_blanket_order",
|
||||
"blanket_order",
|
||||
"blanket_order_rate",
|
||||
"manufacturing_section_section",
|
||||
"bom_no",
|
||||
"planning_section",
|
||||
"projected_qty",
|
||||
"actual_qty",
|
||||
@ -212,6 +214,7 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "UOM",
|
||||
"options": "UOM",
|
||||
"print_hide": 0,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
@ -764,12 +767,24 @@
|
||||
"fieldname": "against_blanket_order",
|
||||
"fieldtype": "Check",
|
||||
"label": "Against Blanket Order"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname": "bom_no",
|
||||
"fieldtype": "Link",
|
||||
"label": "BOM No",
|
||||
"no_copy": 1,
|
||||
"options": "BOM",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "manufacturing_section_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Manufacturing Section"
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-03-05 14:20:28.085117",
|
||||
"modified": "2020-05-15 18:13:43.006493",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Selling",
|
||||
"name": "Sales Order Item",
|
||||
|
@ -30,7 +30,16 @@ frappe.ui.form.on('Material Request', {
|
||||
return {
|
||||
filters: {'company': doc.company}
|
||||
};
|
||||
})
|
||||
});
|
||||
|
||||
frm.set_query("bom_no", "items", function(doc, cdt, cdn) {
|
||||
var row = locals[cdt][cdn];
|
||||
return {
|
||||
filters: {
|
||||
"item": row.item_code
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
onload: function(frm) {
|
||||
|
@ -53,6 +53,8 @@
|
||||
"dimension_col_break",
|
||||
"cost_center",
|
||||
"section_break_37",
|
||||
"bom_no",
|
||||
"section_break_46",
|
||||
"page_break"
|
||||
],
|
||||
"fields": [
|
||||
@ -371,8 +373,10 @@
|
||||
"label": "Image"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:parent.material_request_type == \"Manufacture\"",
|
||||
"fieldname": "section_break_37",
|
||||
"fieldtype": "Section Break"
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Manufacturing"
|
||||
},
|
||||
{
|
||||
"fieldname": "received_qty",
|
||||
@ -428,12 +432,24 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "Source Warehouse (Material Transfer)",
|
||||
"options": "Warehouse"
|
||||
},
|
||||
{
|
||||
"fieldname": "bom_no",
|
||||
"fieldtype": "Link",
|
||||
"label": "BOM No",
|
||||
"no_copy": 1,
|
||||
"options": "BOM",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_46",
|
||||
"fieldtype": "Section Break"
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-05-01 09:00:00.992835",
|
||||
"modified": "2020-05-15 09:00:00.992835",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Material Request Item",
|
||||
|
@ -305,7 +305,8 @@ def get_basic_details(args, item, overwrite_warehouse=True):
|
||||
"weight_uom":item.weight_uom,
|
||||
"last_purchase_rate": item.last_purchase_rate if args.get("doctype") in ["Purchase Order"] else 0,
|
||||
"transaction_date": args.get("transaction_date"),
|
||||
"against_blanket_order": args.get("against_blanket_order")
|
||||
"against_blanket_order": args.get("against_blanket_order"),
|
||||
"bom_no": item.get("default_bom")
|
||||
})
|
||||
|
||||
if item.get("enable_deferred_revenue") or item.get("enable_deferred_expense"):
|
||||
|
Loading…
x
Reference in New Issue
Block a user