From b192ddd13b7d615fff6d7df883c793207b8e6c76 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 4 Jan 2024 17:22:58 +0530 Subject: [PATCH] feat: provision to close SCO (backport #39127) (#39144) * feat: provision to close SCO (cherry picked from commit 5e2669f4b6e8ed917cc8e882b49a4a617a854464) * fix: don't allow to submit/cancel SCR against a closed SCO (cherry picked from commit 9e973476b2a925d4557b3a6b17517e46d652ac20) * fix: don't allow to submit/cancel SE against a closed SCO (cherry picked from commit 5bc2035bd082001e2552138a5df55554f2e6f8de) * fix(ux): filter closed SCO in `Get Items From` dialog (cherry picked from commit bb839b2924c2dd4c2a06b087d7624ca7aac08f04) * fix: don't close PO on SCO close (cherry picked from commit 0d01bd8a5a49f5bba80ef797d5823f762e0fc86a) * fix: update qty on SCO status change (cherry picked from commit 245effcccd3866bac2974023d37cd48b89849aeb) * fix: don't allow to reopen SCO if PO is closed (cherry picked from commit 784b6dcfea2aa9eb3fe557bb4f87cdc1f7341560) * fix: auto close and reopen SCO based on PO status (cherry picked from commit 0819675fce1cffe3a930fe2cbd015998e4e7c00d) * fix(text): test_update_status (cherry picked from commit cdd5441435ee448d3efb2155a93865ec29bbf8ab) --------- Co-authored-by: s-aga-r --- .../doctype/purchase_order/purchase_order.py | 12 +++++++ .../stock/doctype/stock_entry/stock_entry.py | 20 ++++-------- .../subcontracting_order.js | 23 +++++++++++++ .../subcontracting_order.json | 4 +-- .../subcontracting_order.py | 32 +++++++------------ .../subcontracting_order_list.js | 2 +- .../test_subcontracting_order.py | 13 ++++---- .../subcontracting_receipt.js | 3 +- .../subcontracting_receipt.py | 8 +++++ 9 files changed, 72 insertions(+), 45 deletions(-) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index 4c274354b212..a28a310306f2 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -452,6 +452,7 @@ def update_status(self, status): self.update_requested_qty() self.update_ordered_qty() self.update_reserved_qty_for_subcontract() + self.update_subcontracting_order_status() self.notify_update() clear_doctype_notifications(self) @@ -613,6 +614,17 @@ def auto_create_subcontracting_order(self): if frappe.db.get_single_value("Buying Settings", "auto_create_subcontracting_order"): make_subcontracting_order(self.name, save=True, notify=True) + def update_subcontracting_order_status(self): + from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import ( + update_subcontracting_order_status as update_sco_status, + ) + + if self.is_subcontracted and not self.is_old_subcontracting_flow: + sco = frappe.db.get_value("Subcontracting Order", {"purchase_order": self.name, "docstatus": 1}) + + if sco: + update_sco_status(sco, "Closed" if self.status == "Closed" else None) + def item_last_purchase_rate(name, conversion_rate, item_code, conversion_factor=1.0): """get last purchase rate for an item""" diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index d35288a91cb3..9e6ef0f06a7f 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -24,6 +24,7 @@ import erpnext from erpnext.accounts.general_ledger import process_gl_map +from erpnext.buying.utils import check_on_hold_or_closed_status from erpnext.controllers.taxes_and_totals import init_landed_taxes_and_totals from erpnext.manufacturing.doctype.bom.bom import ( add_additional_cost, @@ -208,7 +209,6 @@ def validate(self): self.validate_bom() self.set_process_loss_qty() self.validate_purchase_order() - self.validate_subcontracting_order() if self.purpose in ("Manufacture", "Repack"): self.mark_finished_and_scrap_items() @@ -274,6 +274,7 @@ def is_enqueue_action(self, force=False) -> bool: return False def on_submit(self): + self.validate_closed_subcontracting_order() self.update_stock_ledger() self.update_work_order() self.validate_subcontract_order() @@ -294,6 +295,7 @@ def on_submit(self): self.set_material_request_transfer_status("Completed") def on_cancel(self): + self.validate_closed_subcontracting_order() self.update_subcontract_order_supplied_items() self.update_subcontracting_order_status() @@ -1197,19 +1199,9 @@ def validate_purchase_order(self): ) ) - def validate_subcontracting_order(self): - if self.get("subcontracting_order") and self.purpose in [ - "Send to Subcontractor", - "Material Transfer", - ]: - sco_status = frappe.db.get_value("Subcontracting Order", self.subcontracting_order, "status") - - if sco_status == "Closed": - frappe.throw( - _("Cannot create Stock Entry against a closed Subcontracting Order {0}.").format( - self.subcontracting_order - ) - ) + def validate_closed_subcontracting_order(self): + if self.get("subcontracting_order"): + check_on_hold_or_closed_status("Subcontracting Order", self.subcontracting_order) def mark_finished_and_scrap_items(self): if self.purpose != "Repack" and any( diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js index 587a3b4ebfae..4c8a0ad60ed4 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js +++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js @@ -101,9 +101,32 @@ frappe.ui.form.on('Subcontracting Order', { }, refresh: function (frm) { + if (frm.doc.docstatus == 1 && frm.has_perm("submit")) { + if (frm.doc.status == "Closed") { + frm.add_custom_button(__('Re-open'), () => frm.events.update_subcontracting_order_status(frm), __("Status")); + } else if(flt(frm.doc.per_received, 2) < 100) { + frm.add_custom_button(__('Close'), () => frm.events.update_subcontracting_order_status(frm, "Closed"), __("Status")); + } + } + frm.trigger('get_materials_from_supplier'); }, + update_subcontracting_order_status(frm, status) { + frappe.call({ + method: "erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order.update_subcontracting_order_status", + args: { + sco: frm.doc.name, + status: status, + }, + callback: function (r) { + if (!r.exc) { + frm.reload_doc(); + } + }, + }); + }, + get_materials_from_supplier: function (frm) { let sco_rm_details = []; diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.json b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.json index 28c52c9272d0..507e23365cc2 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.json +++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.json @@ -370,7 +370,7 @@ "in_standard_filter": 1, "label": "Status", "no_copy": 1, - "options": "Draft\nOpen\nPartially Received\nCompleted\nMaterial Transferred\nPartial Material Transferred\nCancelled", + "options": "Draft\nOpen\nPartially Received\nCompleted\nMaterial Transferred\nPartial Material Transferred\nCancelled\nClosed", "print_hide": 1, "read_only": 1, "reqd": 1, @@ -454,7 +454,7 @@ "icon": "fa fa-file-text", "is_submittable": 1, "links": [], - "modified": "2023-06-03 16:18:17.782538", + "modified": "2024-01-03 20:56:04.670380", "modified_by": "Administrator", "module": "Subcontracting", "name": "Subcontracting Order", diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py index 0fe8c13efbdf..daccbbbd0f9b 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py +++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py @@ -7,7 +7,7 @@ from frappe.utils import flt from erpnext.buying.doctype.purchase_order.purchase_order import is_subcontracting_order_created -from erpnext.buying.doctype.purchase_order.purchase_order import update_status as update_po_status +from erpnext.buying.utils import check_on_hold_or_closed_status from erpnext.controllers.subcontracting_controller import SubcontractingController from erpnext.stock.stock_balance import update_bin_qty from erpnext.stock.utils import get_bin @@ -68,6 +68,7 @@ class SubcontractingOrder(SubcontractingController): "Material Transferred", "Partial Material Transferred", "Cancelled", + "Closed", ] supplied_items: DF.Table[SubcontractingOrderSuppliedItem] supplier: DF.Link @@ -112,16 +113,10 @@ def validate(self): def on_submit(self): self.update_prevdoc_status() - self.update_requested_qty() - self.update_ordered_qty_for_subcontracting() - self.update_reserved_qty_for_subcontracting() self.update_status() def on_cancel(self): self.update_prevdoc_status() - self.update_requested_qty() - self.update_ordered_qty_for_subcontracting() - self.update_reserved_qty_for_subcontracting() self.update_status() def validate_purchase_order_for_subcontracting(self): @@ -277,6 +272,9 @@ def populate_items_table(self): self.set_missing_values() def update_status(self, status=None, update_modified=True): + if self.status == "Closed" and self.status != status: + check_on_hold_or_closed_status("Purchase Order", self.purchase_order) + if self.docstatus >= 1 and not status: if self.docstatus == 1: if self.status == "Draft": @@ -285,11 +283,6 @@ def update_status(self, status=None, update_modified=True): status = "Completed" elif self.per_received > 0 and self.per_received < 100: status = "Partially Received" - for item in self.supplied_items: - if not item.returned_qty or (item.supplied_qty - item.consumed_qty - item.returned_qty) > 0: - break - else: - status = "Closed" else: total_required_qty = total_supplied_qty = 0 for item in self.supplied_items: @@ -304,13 +297,12 @@ def update_status(self, status=None, update_modified=True): elif self.docstatus == 2: status = "Cancelled" - if status: - frappe.db.set_value( - "Subcontracting Order", self.name, "status", status, update_modified=update_modified - ) + if status and self.status != status: + self.db_set("status", status, update_modified=update_modified) - if status == "Closed": - update_po_status("Closed", self.purchase_order) + self.update_requested_qty() + self.update_ordered_qty_for_subcontracting() + self.update_reserved_qty_for_subcontracting() @frappe.whitelist() @@ -357,8 +349,8 @@ def update_item(source, target, source_parent): @frappe.whitelist() -def update_subcontracting_order_status(sco): +def update_subcontracting_order_status(sco, status=None): if isinstance(sco, str): sco = frappe.get_doc("Subcontracting Order", sco) - sco.update_status() + sco.update_status(status) diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order_list.js b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order_list.js index 7ca12642c5f5..ec54944a8496 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order_list.js +++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order_list.js @@ -10,7 +10,7 @@ frappe.listview_settings['Subcontracting Order'] = { "Completed": "green", "Partial Material Transferred": "purple", "Material Transferred": "blue", - "Closed": "red", + "Closed": "green", "Cancelled": "red", }; return [__(doc.status), status_colors[doc.status], "status,=," + doc.status]; diff --git a/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py b/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py index 37dabf1bfbe7..6c0ee45d9c5b 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py +++ b/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py @@ -95,14 +95,14 @@ def test_update_status(self): self.assertEqual(sco.status, "Partially Received") # Closed - ste = get_materials_from_supplier(sco.name, [d.name for d in sco.supplied_items]) - ste.save() - ste.submit() - sco.load_from_db() + sco.update_status("Closed") self.assertEqual(sco.status, "Closed") - ste.cancel() - sco.load_from_db() + scr = make_subcontracting_receipt(sco.name) + scr.save() + self.assertRaises(frappe.exceptions.ValidationError, scr.submit) + sco.update_status() self.assertEqual(sco.status, "Partially Received") + scr.cancel() # Completed scr = make_subcontracting_receipt(sco.name) @@ -564,7 +564,6 @@ def test_get_materials_from_supplier(self): sco.load_from_db() - self.assertEqual(sco.status, "Closed") self.assertEqual(sco.supplied_items[0].returned_qty, 5) def test_ordered_qty_for_subcontracting_order(self): diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js index 575c4eda7318..05357999a1b5 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js @@ -93,7 +93,8 @@ frappe.ui.form.on('Subcontracting Receipt', { get_query_filters: { docstatus: 1, per_received: ['<', 100], - company: frm.doc.company + company: frm.doc.company, + status: ['!=', 'Closed'], } }); }, __('Get Items From')); diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py index fc1b697a8e02..475b6030780e 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py @@ -8,6 +8,7 @@ import erpnext from erpnext.accounts.utils import get_account_currency +from erpnext.buying.utils import check_on_hold_or_closed_status from erpnext.controllers.subcontracting_controller import SubcontractingController from erpnext.stock.stock_ledger import get_valuation_rate @@ -142,6 +143,7 @@ def validate(self): self.get_current_stock() def on_submit(self): + self.validate_closed_subcontracting_order() self.validate_available_qty_for_consumption() self.update_status_updater_args() self.update_prevdoc_status() @@ -165,6 +167,7 @@ def on_cancel(self): "Repost Item Valuation", "Serial and Batch Bundle", ) + self.validate_closed_subcontracting_order() self.update_status_updater_args() self.update_prevdoc_status() self.set_consumed_qty_in_subcontract_order() @@ -175,6 +178,11 @@ def on_cancel(self): self.update_status() self.delete_auto_created_batches() + def validate_closed_subcontracting_order(self): + for item in self.items: + if item.subcontracting_order: + check_on_hold_or_closed_status("Subcontracting Order", item.subcontracting_order) + def validate_items_qty(self): for item in self.items: if not (item.qty or item.rejected_qty):