From d9706271ff32d8082e0deacbd045f8716865f06d Mon Sep 17 00:00:00 2001 From: Sudharsanan11 Date: Sat, 25 Jul 2026 23:54:52 +0530 Subject: [PATCH 1/2] fix(subcontracting): release raw-material reservation when closing a subcontracting order the bin reserved-qty recalc filtered out closed purchase orders but not closed subcontracting orders, so closing a partially-received sco kept the reservation for the unreceived qty and left projected qty understated. apply the same closed-status filter to the subcontracting order path. (cherry picked from commit db91a79d3189482dcbe9ed9154ba40eeb7597464) # Conflicts: # erpnext/stock/doctype/bin/bin.py --- erpnext/stock/doctype/bin/bin.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/bin/bin.py b/erpnext/stock/doctype/bin/bin.py index 2af1f645494..a2c0f09d387 100644 --- a/erpnext/stock/doctype/bin/bin.py +++ b/erpnext/stock/doctype/bin/bin.py @@ -162,7 +162,7 @@ class Bin(Document): & (subcontract_order.docstatus == 1) ) if subcontract_doctype == "Purchase Order" - else (subcontract_order.docstatus == 1) + else ((subcontract_order.status != "Closed") & (subcontract_order.docstatus == 1)) ) ) @@ -199,6 +199,7 @@ class Bin(Document): else ( (Coalesce(se.subcontracting_order, "") != "") & (subcontract_order.name == se.subcontracting_order) + & (subcontract_order.status != "Closed") ) ) ) From 92a7dca67ce7f0b1e474db6c8a50a9ed53de1341 Mon Sep 17 00:00:00 2001 From: Sudharsanan11 Date: Sat, 25 Jul 2026 23:54:54 +0530 Subject: [PATCH 2/2] test(subcontracting): cover reservation release on closing a subcontracting order close a partially-received sco with a reserve warehouse and assert the raw-material reservation is released and projected qty recovers. (cherry picked from commit e4b8065a69e085bfd825d4e01885b4d4de903950) --- .../test_subcontracting_order.py | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py b/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py index 6094229c32d..1a54343a1b3 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py +++ b/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py @@ -336,6 +336,85 @@ class TestSubcontractingOrder(FrappeTestCase): bin_after_cancel_sco.reserved_qty_for_sub_contract, bin_before_sco.reserved_qty_for_sub_contract ) + def test_close_subcontracting_order_releases_reserved_qty(self): + # RM in stock at the reserve warehouse for transfer + make_stock_entry(target="_Test Warehouse - _TC", item_code="_Test Item", qty=10, basic_rate=100) + make_stock_entry( + target="_Test Warehouse - _TC", item_code="_Test Item Home Desktop 100", qty=20, basic_rate=100 + ) + + bin_before_sco = frappe.db.get_value( + "Bin", + filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, + fieldname="reserved_qty_for_sub_contract", + as_dict=1, + ) + + # Create SCO with a reserve warehouse on the supplied items + service_items = [ + { + "warehouse": "_Test Warehouse - _TC", + "item_code": "Subcontracted Service Item 1", + "qty": 10, + "rate": 100, + "fg_item": "_Test FG Item", + "fg_item_qty": 10, + }, + ] + sco = get_subcontracting_order(service_items=service_items) + + # Transfer only 90% of the raw materials to the supplier warehouse + ste = frappe.get_doc(make_rm_stock_entry(sco.name)) + for item in ste.items: + item.qty *= 0.9 + ste.save() + ste.submit() + sco.load_from_db() + self.assertEqual(sco.status, "Partial Material Transferred") + + # Receive only a partial qty so the order stays open (per_received < 100) + scr = make_subcontracting_receipt(sco.name) + scr.items[0].qty -= 1 + scr.save() + scr.submit() + sco.load_from_db() + self.assertEqual(sco.status, "Partially Received") + + # Keep another SCO open so transfers from the closed SCO must not reduce its reservation + open_sco = get_subcontracting_order(service_items=service_items) + self.assertEqual(open_sco.status, "Open") + + bin_before_close = frappe.db.get_value( + "Bin", + filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, + fieldname=["reserved_qty_for_sub_contract", "projected_qty"], + as_dict=1, + ) + + # One unit remains reserved for the partially transferred SCO, plus ten for the open SCO + self.assertEqual( + bin_before_close.reserved_qty_for_sub_contract, + bin_before_sco.reserved_qty_for_sub_contract + 11, + ) + + # Close the partially-received order + sco.update_status("Closed") + self.assertEqual(sco.status, "Closed") + + bin_after_close = frappe.db.get_value( + "Bin", + filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, + fieldname=["reserved_qty_for_sub_contract", "projected_qty"], + as_dict=1, + ) + + # Closing releases the remaining unit without applying its transfer against the open SCO + self.assertEqual( + bin_after_close.reserved_qty_for_sub_contract, + bin_before_sco.reserved_qty_for_sub_contract + 10, + ) + self.assertEqual(bin_after_close.projected_qty, bin_before_close.projected_qty + 1) + def test_send_to_subcontractor_ste_submit_without_sco_write_permission(self): """A Stock-only user (can submit Stock Entries but has no Subcontracting Order write) must be able to submit and cancel a 'Send to Subcontractor' Stock Entry. The SCO status update on the