Merge pull request #30399 from ankush/wo_dead_links

fix: broken production item links on production plan
This commit is contained in:
Marica 2022-03-24 16:49:38 +05:30 committed by GitHub
commit 6ebb19d063
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 73 additions and 6 deletions

View File

@ -2,6 +2,13 @@
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on('Production Plan', { frappe.ui.form.on('Production Plan', {
before_save: function(frm) {
// preserve temporary names on production plan item to re-link sub-assembly items
frm.doc.po_items.forEach(item => {
item.temporary_name = item.name;
});
},
setup: function(frm) { setup: function(frm) {
frm.custom_make_buttons = { frm.custom_make_buttons = {
'Work Order': 'Work Order / Subcontract PO', 'Work Order': 'Work Order / Subcontract PO',

View File

@ -32,6 +32,7 @@ class ProductionPlan(Document):
self.set_pending_qty_in_row_without_reference() self.set_pending_qty_in_row_without_reference()
self.calculate_total_planned_qty() self.calculate_total_planned_qty()
self.set_status() self.set_status()
self._rename_temporary_references()
def set_pending_qty_in_row_without_reference(self): def set_pending_qty_in_row_without_reference(self):
"Set Pending Qty in independent rows (not from SO or MR)." "Set Pending Qty in independent rows (not from SO or MR)."
@ -57,6 +58,18 @@ class ProductionPlan(Document):
if not flt(d.planned_qty): if not flt(d.planned_qty):
frappe.throw(_("Please enter Planned Qty for Item {0} at row {1}").format(d.item_code, d.idx)) frappe.throw(_("Please enter Planned Qty for Item {0} at row {1}").format(d.item_code, d.idx))
def _rename_temporary_references(self):
""" po_items and sub_assembly_items items are both constructed client side without saving.
Attempt to fix linkages by using temporary names to map final row names.
"""
new_name_map = {d.temporary_name: d.name for d in self.po_items if d.temporary_name}
actual_names = set(new_name_map.values())
for sub_assy in self.sub_assembly_items:
if sub_assy.production_plan_item not in actual_names:
sub_assy.production_plan_item = new_name_map.get(sub_assy.production_plan_item)
@frappe.whitelist() @frappe.whitelist()
def get_open_sales_orders(self): def get_open_sales_orders(self):
""" Pull sales orders which are pending to deliver based on criteria selected""" """ Pull sales orders which are pending to deliver based on criteria selected"""

View File

@ -667,6 +667,39 @@ class TestProductionPlan(FrappeTestCase):
wo_doc.submit() wo_doc.submit()
self.assertEqual(wo_doc.qty, 0.55) self.assertEqual(wo_doc.qty, 0.55)
def test_temporary_name_relinking(self):
pp = frappe.new_doc("Production Plan")
# this can not be unittested so mocking data that would be expected
# from client side.
for _ in range(10):
po_item = pp.append("po_items", {
"name": frappe.generate_hash(length=10),
"temporary_name": frappe.generate_hash(length=10),
})
pp.append("sub_assembly_items", {
"production_plan_item": po_item.temporary_name
})
pp._rename_temporary_references()
for po_item, subassy_item in zip(pp.po_items, pp.sub_assembly_items):
self.assertEqual(po_item.name, subassy_item.production_plan_item)
# bad links should be erased
pp.append("sub_assembly_items", {
"production_plan_item": frappe.generate_hash(length=16)
})
pp._rename_temporary_references()
self.assertIsNone(pp.sub_assembly_items[-1].production_plan_item)
pp.sub_assembly_items.pop()
# reattempting on same doc shouldn't change anything
pp._rename_temporary_references()
for po_item, subassy_item in zip(pp.po_items, pp.sub_assembly_items):
self.assertEqual(po_item.name, subassy_item.production_plan_item)
def create_production_plan(**args): def create_production_plan(**args):
""" """
sales_order (obj): Sales Order Doc Object sales_order (obj): Sales Order Doc Object

View File

@ -27,7 +27,8 @@
"material_request", "material_request",
"material_request_item", "material_request_item",
"product_bundle_item", "product_bundle_item",
"item_reference" "item_reference",
"temporary_name"
], ],
"fields": [ "fields": [
{ {
@ -204,17 +205,25 @@
"fieldtype": "Data", "fieldtype": "Data",
"hidden": 1, "hidden": 1,
"label": "Item Reference" "label": "Item Reference"
},
{
"fieldname": "temporary_name",
"fieldtype": "Data",
"hidden": 1,
"label": "temporary name"
} }
], ],
"idx": 1, "idx": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2021-06-28 18:31:06.822168", "modified": "2022-03-24 04:54:09.940224",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Manufacturing", "module": "Manufacturing",
"name": "Production Plan Item", "name": "Production Plan Item",
"naming_rule": "Random",
"owner": "Administrator", "owner": "Administrator",
"permissions": [], "permissions": [],
"sort_field": "modified", "sort_field": "modified",
"sort_order": "ASC" "sort_order": "ASC",
"states": []
} }

View File

@ -457,7 +457,8 @@ class WorkOrder(Document):
mr_obj.update_requested_qty([self.material_request_item]) mr_obj.update_requested_qty([self.material_request_item])
def update_ordered_qty(self): def update_ordered_qty(self):
if self.production_plan and self.production_plan_item: if self.production_plan and self.production_plan_item \
and not self.production_plan_sub_assembly_item:
qty = frappe.get_value("Production Plan Item", self.production_plan_item, "ordered_qty") or 0.0 qty = frappe.get_value("Production Plan Item", self.production_plan_item, "ordered_qty") or 0.0
if self.docstatus == 1: if self.docstatus == 1:
@ -644,9 +645,13 @@ class WorkOrder(Document):
if not self.qty > 0: if not self.qty > 0:
frappe.throw(_("Quantity to Manufacture must be greater than 0.")) frappe.throw(_("Quantity to Manufacture must be greater than 0."))
if self.production_plan and self.production_plan_item: if self.production_plan and self.production_plan_item \
and not self.production_plan_sub_assembly_item:
qty_dict = frappe.db.get_value("Production Plan Item", self.production_plan_item, ["planned_qty", "ordered_qty"], as_dict=1) qty_dict = frappe.db.get_value("Production Plan Item", self.production_plan_item, ["planned_qty", "ordered_qty"], as_dict=1)
if not qty_dict:
return
allowance_qty = flt(frappe.db.get_single_value("Manufacturing Settings", allowance_qty = flt(frappe.db.get_single_value("Manufacturing Settings",
"overproduction_percentage_for_work_order"))/100 * qty_dict.get("planned_qty", 0) "overproduction_percentage_for_work_order"))/100 * qty_dict.get("planned_qty", 0)

View File

@ -360,4 +360,4 @@ erpnext.patches.v14_0.update_batch_valuation_flag
erpnext.patches.v14_0.delete_non_profit_doctypes erpnext.patches.v14_0.delete_non_profit_doctypes
erpnext.patches.v14_0.update_employee_advance_status erpnext.patches.v14_0.update_employee_advance_status
erpnext.patches.v13_0.add_cost_center_in_loans erpnext.patches.v13_0.add_cost_center_in_loans
erpnext.patches.v13_0.remove_unknown_links_to_prod_plan_items erpnext.patches.v13_0.remove_unknown_links_to_prod_plan_items # 24-03-2022