Merge pull request #39127 from s-aga-r/FIX-38702
feat: provision to close SCO
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py
index 2efb46e..b830e7d 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.py
@@ -452,6 +452,7 @@
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)
@@ -627,6 +628,17 @@
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 2ccee94..bccbc28 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 @@
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 @@
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 @@
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()
@@ -1203,19 +1205,9 @@
)
)
- 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 587a3b4..4c8a0ad 100644
--- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js
+++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js
@@ -101,9 +101,32 @@
},
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 28c52c9..507e233 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 0fe8c13..daccbbb 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 @@
"Material Transferred",
"Partial Material Transferred",
"Cancelled",
+ "Closed",
]
supplied_items: DF.Table[SubcontractingOrderSuppliedItem]
supplier: DF.Link
@@ -112,16 +113,10 @@
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 @@
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 @@
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 @@
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 @@
@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 7ca1264..ec54944 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 @@
"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 37dabf1..6c0ee45 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 @@
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 @@
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 575c4ed..0535799 100644
--- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js
+++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js
@@ -93,7 +93,8 @@
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 52bf13c..7c2a1f1 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 @@
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 @@
"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 @@
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):