From 7aa37ec5114d8b5aefb2d1d87bb6a4be2a5afe2a Mon Sep 17 00:00:00 2001 From: marination Date: Wed, 16 Mar 2022 19:45:03 +0530 Subject: [PATCH] feat: BOM Update Log - Created BOM Update Log that will handle queued job status and failures - Moved validation and BG job to thus new doctype - BOM Update Tool only works as an endpoint (cherry picked from commit 4283a13e5a6a6b9f1e8e1cbcc639646a4e957b36) # Conflicts: # erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py --- erpnext/hooks.py | 2 +- .../doctype/bom_update_log/__init__.py | 0 .../doctype/bom_update_log/bom_update_log.js | 8 ++ .../bom_update_log/bom_update_log.json | 101 +++++++++++++++ .../doctype/bom_update_log/bom_update_log.py | 117 ++++++++++++++++++ .../bom_update_log/test_bom_update_log.py | 9 ++ .../bom_update_tool/bom_update_tool.py | 34 ++++- 7 files changed, 266 insertions(+), 5 deletions(-) create mode 100644 erpnext/manufacturing/doctype/bom_update_log/__init__.py create mode 100644 erpnext/manufacturing/doctype/bom_update_log/bom_update_log.js create mode 100644 erpnext/manufacturing/doctype/bom_update_log/bom_update_log.json create mode 100644 erpnext/manufacturing/doctype/bom_update_log/bom_update_log.py create mode 100644 erpnext/manufacturing/doctype/bom_update_log/test_bom_update_log.py diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 906eb10c64f..a21a0313544 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -511,7 +511,7 @@ scheduler_events = { ], "daily_long": [ "erpnext.setup.doctype.email_digest.email_digest.send", - "erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.update_latest_price_in_all_boms", + "erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.auto_update_latest_price_in_all_boms", "erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry.process_expired_allocation", "erpnext.hr.utils.generate_leave_encashment", "erpnext.hr.utils.allocate_earned_leaves", diff --git a/erpnext/manufacturing/doctype/bom_update_log/__init__.py b/erpnext/manufacturing/doctype/bom_update_log/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/manufacturing/doctype/bom_update_log/bom_update_log.js b/erpnext/manufacturing/doctype/bom_update_log/bom_update_log.js new file mode 100644 index 00000000000..6da808e26d1 --- /dev/null +++ b/erpnext/manufacturing/doctype/bom_update_log/bom_update_log.js @@ -0,0 +1,8 @@ +// Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('BOM Update Log', { + // refresh: function(frm) { + + // } +}); diff --git a/erpnext/manufacturing/doctype/bom_update_log/bom_update_log.json b/erpnext/manufacturing/doctype/bom_update_log/bom_update_log.json new file mode 100644 index 00000000000..222168be8cf --- /dev/null +++ b/erpnext/manufacturing/doctype/bom_update_log/bom_update_log.json @@ -0,0 +1,101 @@ +{ + "actions": [], + "autoname": "BOM-UPDT-LOG-.#####", + "creation": "2022-03-16 14:23:35.210155", + "description": "BOM Update Tool Log with job status maintained", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "current_bom", + "new_bom", + "column_break_3", + "update_type", + "status", + "amended_from" + ], + "fields": [ + { + "fieldname": "current_bom", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Current BOM", + "options": "BOM", + "reqd": 1 + }, + { + "fieldname": "new_bom", + "fieldtype": "Link", + "in_list_view": 1, + "label": "New BOM", + "options": "BOM", + "reqd": 1 + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fieldname": "update_type", + "fieldtype": "Select", + "label": "Update Type", + "options": "Replace BOM\nUpdate Cost" + }, + { + "fieldname": "status", + "fieldtype": "Select", + "label": "Status", + "options": "Queued\nIn Progress\nCompleted\nFailed" + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "BOM Update Log", + "print_hide": 1, + "read_only": 1 + } + ], + "in_create": 1, + "index_web_pages_for_search": 1, + "is_submittable": 1, + "links": [], + "modified": "2022-03-16 18:25:49.833836", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "BOM Update Log", + "naming_rule": "Expression (old style)", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "create": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Manufacturing Manager", + "share": 1, + "submit": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "states": [], + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/bom_update_log/bom_update_log.py b/erpnext/manufacturing/doctype/bom_update_log/bom_update_log.py new file mode 100644 index 00000000000..10db0de9a11 --- /dev/null +++ b/erpnext/manufacturing/doctype/bom_update_log/bom_update_log.py @@ -0,0 +1,117 @@ +# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +import frappe +from frappe import _ +from frappe.model.document import Document +from frappe.utils import cstr + +from erpnext.manufacturing.doctype.bom.bom import get_boms_in_bottom_up_order + +from rq.timeouts import JobTimeoutException + + +class BOMMissingError(frappe.ValidationError): pass + +class BOMUpdateLog(Document): + def validate(self): + self.validate_boms_are_specified() + self.validate_same_bom() + self.validate_bom_items() + self.status = "Queued" + + def validate_boms_are_specified(self): + if self.update_type == "Replace BOM" and not (self.current_bom and self.new_bom): + frappe.throw( + msg=_("Please mention the Current and New BOM for replacement."), + title=_("Mandatory"), exc=BOMMissingError + ) + + def validate_same_bom(self): + if cstr(self.current_bom) == cstr(self.new_bom): + frappe.throw(_("Current BOM and New BOM can not be same")) + + def validate_bom_items(self): + current_bom_item = frappe.db.get_value("BOM", self.current_bom, "item") + new_bom_item = frappe.db.get_value("BOM", self.new_bom, "item") + + if current_bom_item != new_bom_item: + frappe.throw(_("The selected BOMs are not for the same item")) + + def on_submit(self): + if frappe.flags.in_test: + return + + if self.update_type == "Replace BOM": + boms = { + "current_bom": self.current_bom, + "new_bom": self.new_bom + } + frappe.enqueue( + method="erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.replace_bom", + boms=boms, doc=self, timeout=40000 + ) + else: + frappe.enqueue( + method="erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.update_cost_queue", + doc=self, timeout=40000 + ) + +def replace_bom(boms, doc): + try: + doc.db_set("status", "In Progress") + if not frappe.flags.in_test: + frappe.db.commit() + + frappe.db.auto_commit_on_many_writes = 1 + + args = frappe._dict(boms) + doc = frappe.get_doc("BOM Update Tool") + doc.current_bom = args.current_bom + doc.new_bom = args.new_bom + doc.replace_bom() + + doc.db_set("status", "Completed") + + except (Exception, JobTimeoutException): + frappe.db.rollback() + frappe.log_error( + msg=frappe.get_traceback(), + title=_("BOM Update Tool Error") + ) + doc.db_set("status", "Failed") + + finally: + frappe.db.auto_commit_on_many_writes = 0 + frappe.db.commit() + +def update_cost_queue(doc): + try: + doc.db_set("status", "In Progress") + if not frappe.flags.in_test: + frappe.db.commit() + + frappe.db.auto_commit_on_many_writes = 1 + + bom_list = get_boms_in_bottom_up_order() + for bom in bom_list: + frappe.get_doc("BOM", bom).update_cost(update_parent=False, from_child_bom=True) + + doc.db_set("status", "Completed") + + except (Exception, JobTimeoutException): + frappe.db.rollback() + frappe.log_error( + msg=frappe.get_traceback(), + title=_("BOM Update Tool Error") + ) + doc.db_set("status", "Failed") + + finally: + frappe.db.auto_commit_on_many_writes = 0 + frappe.db.commit() + +def update_cost(): + bom_list = get_boms_in_bottom_up_order() + for bom in bom_list: + frappe.get_doc("BOM", bom).update_cost(update_parent=False, from_child_bom=True) \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/bom_update_log/test_bom_update_log.py b/erpnext/manufacturing/doctype/bom_update_log/test_bom_update_log.py new file mode 100644 index 00000000000..f74bdc356a7 --- /dev/null +++ b/erpnext/manufacturing/doctype/bom_update_log/test_bom_update_log.py @@ -0,0 +1,9 @@ +# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestBOMUpdateLog(FrappeTestCase): + pass diff --git a/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py index 11092702ca3..7e072a9d1b2 100644 --- a/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py +++ b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py @@ -11,13 +11,11 @@ from frappe.model.document import Document from frappe.utils import cstr, flt from six import string_types -from erpnext.manufacturing.doctype.bom.bom import get_boms_in_bottom_up_order +from erpnext.manufacturing.doctype.bom_update_log.bom_update_log import update_cost class BOMUpdateTool(Document): def replace_bom(self): - self.validate_bom() - unit_cost = get_new_bom_unit_cost(self.new_bom) self.update_new_bom(unit_cost) @@ -43,6 +41,7 @@ class BOMUpdateTool(Document): except Exception: frappe.log_error(frappe.get_traceback()) +<<<<<<< HEAD def validate_bom(self): if cstr(self.current_bom) == cstr(self.new_bom): frappe.throw(_("Current BOM and New BOM can not be same")) @@ -52,6 +51,8 @@ class BOMUpdateTool(Document): ): frappe.throw(_("The selected BOMs are not for the same item")) +======= +>>>>>>> 4283a13e5a (feat: BOM Update Log) def update_new_bom(self, unit_cost): frappe.db.sql( """update `tabBOM Item` set bom_no=%s, @@ -93,16 +94,21 @@ def enqueue_replace_bom(args): if isinstance(args, string_types): args = json.loads(args) +<<<<<<< HEAD frappe.enqueue( "erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.replace_bom", args=args, timeout=40000, ) +======= + create_bom_update_log(boms=args) +>>>>>>> 4283a13e5a (feat: BOM Update Log) frappe.msgprint(_("Queued for replacing the BOM. It may take a few minutes.")) @frappe.whitelist() def enqueue_update_cost(): +<<<<<<< HEAD frappe.enqueue( "erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.update_cost", timeout=40000 ) @@ -110,11 +116,18 @@ def enqueue_update_cost(): _("Queued for updating latest price in all Bill of Materials. It may take a few minutes.") ) +======= + create_bom_update_log(update_type="Update Cost") + frappe.msgprint(_("Queued for updating latest price in all Bill of Materials. It may take a few minutes.")) +>>>>>>> 4283a13e5a (feat: BOM Update Log) -def update_latest_price_in_all_boms(): + +def auto_update_latest_price_in_all_boms(): + "Called via hooks.py." if frappe.db.get_single_value("Manufacturing Settings", "update_bom_costs_automatically"): update_cost() +<<<<<<< HEAD def replace_bom(args): try: @@ -146,3 +159,16 @@ def update_cost(): ) finally: frappe.db.auto_commit_on_many_writes = 0 +======= +def create_bom_update_log(boms=None, update_type="Replace BOM"): + "Creates a BOM Update Log that handles the background job." + current_bom = boms.get("current_bom") if boms else None + new_bom = boms.get("new_bom") if boms else None + log_doc = frappe.get_doc({ + "doctype": "BOM Update Log", + "current_bom": current_bom, + "new_bom": new_bom, + "update_type": update_type + }) + log_doc.submit() +>>>>>>> 4283a13e5a (feat: BOM Update Log)