* 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:
parent
2db1e1a737
commit
b192ddd13b
@ -452,6 +452,7 @@ class PurchaseOrder(BuyingController):
|
|||||||
self.update_requested_qty()
|
self.update_requested_qty()
|
||||||
self.update_ordered_qty()
|
self.update_ordered_qty()
|
||||||
self.update_reserved_qty_for_subcontract()
|
self.update_reserved_qty_for_subcontract()
|
||||||
|
self.update_subcontracting_order_status()
|
||||||
self.notify_update()
|
self.notify_update()
|
||||||
clear_doctype_notifications(self)
|
clear_doctype_notifications(self)
|
||||||
|
|
||||||
@ -613,6 +614,17 @@ class PurchaseOrder(BuyingController):
|
|||||||
if frappe.db.get_single_value("Buying Settings", "auto_create_subcontracting_order"):
|
if frappe.db.get_single_value("Buying Settings", "auto_create_subcontracting_order"):
|
||||||
make_subcontracting_order(self.name, save=True, notify=True)
|
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):
|
def item_last_purchase_rate(name, conversion_rate, item_code, conversion_factor=1.0):
|
||||||
"""get last purchase rate for an item"""
|
"""get last purchase rate for an item"""
|
||||||
|
@ -24,6 +24,7 @@ from frappe.utils import (
|
|||||||
|
|
||||||
import erpnext
|
import erpnext
|
||||||
from erpnext.accounts.general_ledger import process_gl_map
|
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.controllers.taxes_and_totals import init_landed_taxes_and_totals
|
||||||
from erpnext.manufacturing.doctype.bom.bom import (
|
from erpnext.manufacturing.doctype.bom.bom import (
|
||||||
add_additional_cost,
|
add_additional_cost,
|
||||||
@ -208,7 +209,6 @@ class StockEntry(StockController):
|
|||||||
self.validate_bom()
|
self.validate_bom()
|
||||||
self.set_process_loss_qty()
|
self.set_process_loss_qty()
|
||||||
self.validate_purchase_order()
|
self.validate_purchase_order()
|
||||||
self.validate_subcontracting_order()
|
|
||||||
|
|
||||||
if self.purpose in ("Manufacture", "Repack"):
|
if self.purpose in ("Manufacture", "Repack"):
|
||||||
self.mark_finished_and_scrap_items()
|
self.mark_finished_and_scrap_items()
|
||||||
@ -274,6 +274,7 @@ class StockEntry(StockController):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
|
self.validate_closed_subcontracting_order()
|
||||||
self.update_stock_ledger()
|
self.update_stock_ledger()
|
||||||
self.update_work_order()
|
self.update_work_order()
|
||||||
self.validate_subcontract_order()
|
self.validate_subcontract_order()
|
||||||
@ -294,6 +295,7 @@ class StockEntry(StockController):
|
|||||||
self.set_material_request_transfer_status("Completed")
|
self.set_material_request_transfer_status("Completed")
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
|
self.validate_closed_subcontracting_order()
|
||||||
self.update_subcontract_order_supplied_items()
|
self.update_subcontract_order_supplied_items()
|
||||||
self.update_subcontracting_order_status()
|
self.update_subcontracting_order_status()
|
||||||
|
|
||||||
@ -1197,19 +1199,9 @@ class StockEntry(StockController):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def validate_subcontracting_order(self):
|
def validate_closed_subcontracting_order(self):
|
||||||
if self.get("subcontracting_order") and self.purpose in [
|
if self.get("subcontracting_order"):
|
||||||
"Send to Subcontractor",
|
check_on_hold_or_closed_status("Subcontracting Order", self.subcontracting_order)
|
||||||
"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 mark_finished_and_scrap_items(self):
|
def mark_finished_and_scrap_items(self):
|
||||||
if self.purpose != "Repack" and any(
|
if self.purpose != "Repack" and any(
|
||||||
|
@ -101,9 +101,32 @@ frappe.ui.form.on('Subcontracting Order', {
|
|||||||
},
|
},
|
||||||
|
|
||||||
refresh: function (frm) {
|
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');
|
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) {
|
get_materials_from_supplier: function (frm) {
|
||||||
let sco_rm_details = [];
|
let sco_rm_details = [];
|
||||||
|
|
||||||
|
@ -370,7 +370,7 @@
|
|||||||
"in_standard_filter": 1,
|
"in_standard_filter": 1,
|
||||||
"label": "Status",
|
"label": "Status",
|
||||||
"no_copy": 1,
|
"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,
|
"print_hide": 1,
|
||||||
"read_only": 1,
|
"read_only": 1,
|
||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
@ -454,7 +454,7 @@
|
|||||||
"icon": "fa fa-file-text",
|
"icon": "fa fa-file-text",
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-06-03 16:18:17.782538",
|
"modified": "2024-01-03 20:56:04.670380",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Subcontracting",
|
"module": "Subcontracting",
|
||||||
"name": "Subcontracting Order",
|
"name": "Subcontracting Order",
|
||||||
|
@ -7,7 +7,7 @@ from frappe.model.mapper import get_mapped_doc
|
|||||||
from frappe.utils import flt
|
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 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.controllers.subcontracting_controller import SubcontractingController
|
||||||
from erpnext.stock.stock_balance import update_bin_qty
|
from erpnext.stock.stock_balance import update_bin_qty
|
||||||
from erpnext.stock.utils import get_bin
|
from erpnext.stock.utils import get_bin
|
||||||
@ -68,6 +68,7 @@ class SubcontractingOrder(SubcontractingController):
|
|||||||
"Material Transferred",
|
"Material Transferred",
|
||||||
"Partial Material Transferred",
|
"Partial Material Transferred",
|
||||||
"Cancelled",
|
"Cancelled",
|
||||||
|
"Closed",
|
||||||
]
|
]
|
||||||
supplied_items: DF.Table[SubcontractingOrderSuppliedItem]
|
supplied_items: DF.Table[SubcontractingOrderSuppliedItem]
|
||||||
supplier: DF.Link
|
supplier: DF.Link
|
||||||
@ -112,16 +113,10 @@ class SubcontractingOrder(SubcontractingController):
|
|||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
self.update_prevdoc_status()
|
self.update_prevdoc_status()
|
||||||
self.update_requested_qty()
|
|
||||||
self.update_ordered_qty_for_subcontracting()
|
|
||||||
self.update_reserved_qty_for_subcontracting()
|
|
||||||
self.update_status()
|
self.update_status()
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
self.update_prevdoc_status()
|
self.update_prevdoc_status()
|
||||||
self.update_requested_qty()
|
|
||||||
self.update_ordered_qty_for_subcontracting()
|
|
||||||
self.update_reserved_qty_for_subcontracting()
|
|
||||||
self.update_status()
|
self.update_status()
|
||||||
|
|
||||||
def validate_purchase_order_for_subcontracting(self):
|
def validate_purchase_order_for_subcontracting(self):
|
||||||
@ -277,6 +272,9 @@ class SubcontractingOrder(SubcontractingController):
|
|||||||
self.set_missing_values()
|
self.set_missing_values()
|
||||||
|
|
||||||
def update_status(self, status=None, update_modified=True):
|
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 and not status:
|
||||||
if self.docstatus == 1:
|
if self.docstatus == 1:
|
||||||
if self.status == "Draft":
|
if self.status == "Draft":
|
||||||
@ -285,11 +283,6 @@ class SubcontractingOrder(SubcontractingController):
|
|||||||
status = "Completed"
|
status = "Completed"
|
||||||
elif self.per_received > 0 and self.per_received < 100:
|
elif self.per_received > 0 and self.per_received < 100:
|
||||||
status = "Partially Received"
|
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:
|
else:
|
||||||
total_required_qty = total_supplied_qty = 0
|
total_required_qty = total_supplied_qty = 0
|
||||||
for item in self.supplied_items:
|
for item in self.supplied_items:
|
||||||
@ -304,13 +297,12 @@ class SubcontractingOrder(SubcontractingController):
|
|||||||
elif self.docstatus == 2:
|
elif self.docstatus == 2:
|
||||||
status = "Cancelled"
|
status = "Cancelled"
|
||||||
|
|
||||||
if status:
|
if status and self.status != status:
|
||||||
frappe.db.set_value(
|
self.db_set("status", status, update_modified=update_modified)
|
||||||
"Subcontracting Order", self.name, "status", status, update_modified=update_modified
|
|
||||||
)
|
|
||||||
|
|
||||||
if status == "Closed":
|
self.update_requested_qty()
|
||||||
update_po_status("Closed", self.purchase_order)
|
self.update_ordered_qty_for_subcontracting()
|
||||||
|
self.update_reserved_qty_for_subcontracting()
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
@ -357,8 +349,8 @@ def get_mapped_subcontracting_receipt(source_name, target_doc=None):
|
|||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def update_subcontracting_order_status(sco):
|
def update_subcontracting_order_status(sco, status=None):
|
||||||
if isinstance(sco, str):
|
if isinstance(sco, str):
|
||||||
sco = frappe.get_doc("Subcontracting Order", sco)
|
sco = frappe.get_doc("Subcontracting Order", sco)
|
||||||
|
|
||||||
sco.update_status()
|
sco.update_status(status)
|
||||||
|
@ -10,7 +10,7 @@ frappe.listview_settings['Subcontracting Order'] = {
|
|||||||
"Completed": "green",
|
"Completed": "green",
|
||||||
"Partial Material Transferred": "purple",
|
"Partial Material Transferred": "purple",
|
||||||
"Material Transferred": "blue",
|
"Material Transferred": "blue",
|
||||||
"Closed": "red",
|
"Closed": "green",
|
||||||
"Cancelled": "red",
|
"Cancelled": "red",
|
||||||
};
|
};
|
||||||
return [__(doc.status), status_colors[doc.status], "status,=," + doc.status];
|
return [__(doc.status), status_colors[doc.status], "status,=," + doc.status];
|
||||||
|
@ -95,14 +95,14 @@ class TestSubcontractingOrder(FrappeTestCase):
|
|||||||
self.assertEqual(sco.status, "Partially Received")
|
self.assertEqual(sco.status, "Partially Received")
|
||||||
|
|
||||||
# Closed
|
# Closed
|
||||||
ste = get_materials_from_supplier(sco.name, [d.name for d in sco.supplied_items])
|
sco.update_status("Closed")
|
||||||
ste.save()
|
|
||||||
ste.submit()
|
|
||||||
sco.load_from_db()
|
|
||||||
self.assertEqual(sco.status, "Closed")
|
self.assertEqual(sco.status, "Closed")
|
||||||
ste.cancel()
|
scr = make_subcontracting_receipt(sco.name)
|
||||||
sco.load_from_db()
|
scr.save()
|
||||||
|
self.assertRaises(frappe.exceptions.ValidationError, scr.submit)
|
||||||
|
sco.update_status()
|
||||||
self.assertEqual(sco.status, "Partially Received")
|
self.assertEqual(sco.status, "Partially Received")
|
||||||
|
scr.cancel()
|
||||||
|
|
||||||
# Completed
|
# Completed
|
||||||
scr = make_subcontracting_receipt(sco.name)
|
scr = make_subcontracting_receipt(sco.name)
|
||||||
@ -564,7 +564,6 @@ class TestSubcontractingOrder(FrappeTestCase):
|
|||||||
|
|
||||||
sco.load_from_db()
|
sco.load_from_db()
|
||||||
|
|
||||||
self.assertEqual(sco.status, "Closed")
|
|
||||||
self.assertEqual(sco.supplied_items[0].returned_qty, 5)
|
self.assertEqual(sco.supplied_items[0].returned_qty, 5)
|
||||||
|
|
||||||
def test_ordered_qty_for_subcontracting_order(self):
|
def test_ordered_qty_for_subcontracting_order(self):
|
||||||
|
@ -93,7 +93,8 @@ frappe.ui.form.on('Subcontracting Receipt', {
|
|||||||
get_query_filters: {
|
get_query_filters: {
|
||||||
docstatus: 1,
|
docstatus: 1,
|
||||||
per_received: ['<', 100],
|
per_received: ['<', 100],
|
||||||
company: frm.doc.company
|
company: frm.doc.company,
|
||||||
|
status: ['!=', 'Closed'],
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, __('Get Items From'));
|
}, __('Get Items From'));
|
||||||
|
@ -8,6 +8,7 @@ from frappe.utils import cint, flt, get_link_to_form, getdate, nowdate
|
|||||||
|
|
||||||
import erpnext
|
import erpnext
|
||||||
from erpnext.accounts.utils import get_account_currency
|
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.controllers.subcontracting_controller import SubcontractingController
|
||||||
from erpnext.stock.stock_ledger import get_valuation_rate
|
from erpnext.stock.stock_ledger import get_valuation_rate
|
||||||
|
|
||||||
@ -142,6 +143,7 @@ class SubcontractingReceipt(SubcontractingController):
|
|||||||
self.get_current_stock()
|
self.get_current_stock()
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
|
self.validate_closed_subcontracting_order()
|
||||||
self.validate_available_qty_for_consumption()
|
self.validate_available_qty_for_consumption()
|
||||||
self.update_status_updater_args()
|
self.update_status_updater_args()
|
||||||
self.update_prevdoc_status()
|
self.update_prevdoc_status()
|
||||||
@ -165,6 +167,7 @@ class SubcontractingReceipt(SubcontractingController):
|
|||||||
"Repost Item Valuation",
|
"Repost Item Valuation",
|
||||||
"Serial and Batch Bundle",
|
"Serial and Batch Bundle",
|
||||||
)
|
)
|
||||||
|
self.validate_closed_subcontracting_order()
|
||||||
self.update_status_updater_args()
|
self.update_status_updater_args()
|
||||||
self.update_prevdoc_status()
|
self.update_prevdoc_status()
|
||||||
self.set_consumed_qty_in_subcontract_order()
|
self.set_consumed_qty_in_subcontract_order()
|
||||||
@ -175,6 +178,11 @@ class SubcontractingReceipt(SubcontractingController):
|
|||||||
self.update_status()
|
self.update_status()
|
||||||
self.delete_auto_created_batches()
|
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):
|
def validate_items_qty(self):
|
||||||
for item in self.items:
|
for item in self.items:
|
||||||
if not (item.qty or item.rejected_qty):
|
if not (item.qty or item.rejected_qty):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user