diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index 62740e6e6e1c9c81370ec7b4292bdcb2664476e7..4c3deaa0f0a0df164c3061e1c4fc79be6095c4b2 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -321,7 +321,7 @@ class PurchaseOrder(BuyingController): 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) @@ -498,6 +498,17 @@ class PurchaseOrder(BuyingController): 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 df265717256a09eaefad5b01bb55c30c9379a4e5..71e074f3c4131804732455005d90600adee671aa 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -24,6 +24,7 @@ from frappe.utils import ( 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, @@ -132,7 +133,6 @@ class StockEntry(StockController): 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() @@ -198,6 +198,7 @@ class StockEntry(StockController): return False def on_submit(self): + self.validate_closed_subcontracting_order() self.update_stock_ledger() self.update_work_order() self.validate_subcontract_order() @@ -218,6 +219,7 @@ class StockEntry(StockController): 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() @@ -1127,19 +1129,9 @@ class StockEntry(StockController): ) ) - 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 587a3b4ebfaeca06688d30e3ed25fd3e9af10f02..4c8a0ad60ed456e00b01068906bd3af57f799945 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 64ac3a31f218d797e08d28900691a8179c011b54..6dc9c3dde8a3266abffa50d294d14233d056af69 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 0fe8c13efbdf6da427d41bf328722061e1f0c14b..daccbbbd0f9bd94a22c4fdbea9d53fd182eab825 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.model.mapper import get_mapped_doc 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 @@ class SubcontractingOrder(SubcontractingController): 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 @@ class SubcontractingOrder(SubcontractingController): 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 @@ class SubcontractingOrder(SubcontractingController): 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 @@ class SubcontractingOrder(SubcontractingController): 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 get_mapped_subcontracting_receipt(source_name, target_doc=None): @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 7ca12642c5f551b82e2d07e178bc79cd6dc43f3a..ec54944a84968d194734ee6c27cd8766b12ecbb2 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 37dabf1bfbe71102fa815fadd93a9b4a4b536f10..6c0ee45d9c5b8c40cd1619faacf763fdd2692714 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 @@ class TestSubcontractingOrder(FrappeTestCase): 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 @@ class TestSubcontractingOrder(FrappeTestCase): 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 575c4eda731835c47802fdc1a544132707f1140e..05357999a1b5e40fd10bfdf93c711b1d09e0eded 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 d2da87e3c32be3e12ffcd111c1dbe78e1d383b44..aab1d11539a01fc5a9a04dcf9a5d6b31d32cefa8 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py @@ -8,6 +8,7 @@ from frappe.utils import cint, flt, get_link_to_form, getdate, nowdate 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 @@ -72,6 +73,7 @@ class SubcontractingReceipt(SubcontractingController): 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() @@ -95,6 +97,7 @@ class SubcontractingReceipt(SubcontractingController): "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() @@ -105,6 +108,11 @@ class SubcontractingReceipt(SubcontractingController): 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):