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 <sagarsharma.s312@gmail.com>
This commit is contained in:
mergify[bot] 2024-01-04 17:22:58 +05:30 committed by GitHub
parent 2db1e1a737
commit b192ddd13b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 72 additions and 45 deletions

View File

@ -452,6 +452,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)
@ -613,6 +614,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"""

View File

@ -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,
@ -208,7 +209,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()
@ -274,6 +274,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()
@ -294,6 +295,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()
@ -1197,19 +1199,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(

View File

@ -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 = [];

View File

@ -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",

View File

@ -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)

View File

@ -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];

View File

@ -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):

View File

@ -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'));

View File

@ -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
@ -142,6 +143,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()
@ -165,6 +167,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()
@ -175,6 +178,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):