diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py index 2bc39655a97..2b8c01ceaca 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py +++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py @@ -98,6 +98,7 @@ class SubcontractingOrder(SubcontractingController): self.validate_service_items() self.validate_supplied_items() self.set_missing_values() + self.validate_with_previous_doc() self.reset_default_field_value("set_warehouse", "items", "warehouse") def on_submit(self): @@ -108,6 +109,18 @@ class SubcontractingOrder(SubcontractingController): self.update_status() self.update_subcontracted_quantity_in_po(cancel=True) + def validate_with_previous_doc(self): + super().validate_with_previous_doc( + { + "Purchase Order Item": { + "ref_dn_field": "purchase_order_item", + "compare_fields": [["project", "="]], + "is_child_table": True, + "allow_duplicate_prev_row_id": True, + }, + } + ) + def validate_purchase_order_for_subcontracting(self): if self.purchase_order: po = frappe.get_doc("Purchase Order", self.purchase_order) @@ -211,10 +224,10 @@ class SubcontractingOrder(SubcontractingController): if si.fg_item: item = frappe.get_doc("Item", si.fg_item) - qty, subcontracted_quantity, fg_item_qty = frappe.db.get_value( + qty, subcontracted_quantity, fg_item_qty, project = frappe.db.get_value( "Purchase Order Item", si.purchase_order_item, - ["qty", "subcontracted_quantity", "fg_item_qty"], + ["qty", "subcontracted_quantity", "fg_item_qty", "project"], ) available_qty = flt(qty) - flt(subcontracted_quantity) @@ -250,6 +263,7 @@ class SubcontractingOrder(SubcontractingController): "purchase_order_item": si.purchase_order_item, "material_request": si.material_request, "material_request_item": si.material_request_item, + "project": project, } ) else: diff --git a/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py b/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py index 1a54343a1b3..335f8f7c99e 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py +++ b/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py @@ -26,6 +26,7 @@ from erpnext.controllers.tests.test_subcontracting_controller import ( set_backflush_based_on, ) from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom +from erpnext.projects.doctype.project.test_project import make_project from erpnext.stock.doctype.item.test_item import make_item from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import ( @@ -112,6 +113,24 @@ class TestSubcontractingOrder(FrappeTestCase): sco.load_from_db() self.assertEqual(sco.status, "Partially Received") + def test_project_is_carried_over_from_purchase_order(self): + project = make_project({"project_name": "_Test SCO Project"}).name + po = make_subcontracted_purchase_order(project) + + sco = get_mapped_subcontracting_order(source_name=po.name) + + self.assertEqual(sco.project, project) + self.assertEqual(sco.items[0].project, project) + + def test_project_cannot_differ_from_purchase_order(self): + project = make_project({"project_name": "_Test SCO Project"}).name + other_project = make_project({"project_name": "_Test SCO Project 2"}).name + po = make_subcontracted_purchase_order(project) + + sco = get_mapped_subcontracting_order(source_name=po.name) + sco.items[0].project = other_project + self.assertRaises(frappe.ValidationError, sco.save) + def test_make_rm_stock_entry(self): sco = get_subcontracting_order() rm_items = get_rm_items(sco.supplied_items) @@ -873,3 +892,31 @@ def create_subcontracting_order(**args): sco.submit() return sco + + +def make_subcontracted_purchase_order(project): + from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order + + service_items = [ + { + "warehouse": "_Test Warehouse - _TC", + "item_code": "Subcontracted Service Item 7", + "qty": 10, + "rate": 100, + "fg_item": "Subcontracted Item SA7", + "fg_item_qty": 10, + }, + ] + po = create_purchase_order( + rm_items=service_items, + is_subcontracted=1, + supplier_warehouse="_Test Warehouse 1 - _TC", + do_not_submit=1, + ) + po.project = project + for item in po.items: + item.project = project + po.save() + po.submit() + + return po diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py index 501fde5b3dd..89be4fcb344 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py @@ -146,6 +146,7 @@ class SubcontractingReceipt(SubcontractingController): self.get_scrap_items() self.set_missing_values() + self.validate_with_previous_doc() if self.get("_action") == "submit": self.validate_scrap_items() @@ -159,6 +160,24 @@ class SubcontractingReceipt(SubcontractingController): self.set_supplied_items_expense_account() self.set_supplied_items_cost_center() + def validate_with_previous_doc(self): + super().validate_with_previous_doc( + { + "Subcontracting Order Item": { + "ref_dn_field": "subcontracting_order_item", + "compare_fields": [["project", "="]], + "is_child_table": True, + "allow_duplicate_prev_row_id": True, + }, + "Purchase Order Item": { + "ref_dn_field": "purchase_order_item", + "compare_fields": [["project", "="]], + "is_child_table": True, + "allow_duplicate_prev_row_id": True, + }, + } + ) + def on_submit(self): self.validate_closed_subcontracting_order() self.validate_bom_required_qty() @@ -962,5 +981,7 @@ def add_po_items_to_pr(scr_doc, target_doc): "warehouse": item.warehouse, "purchase_order": item.parent, "purchase_order_item": item.name, + "project": item.project, + "cost_center": item.cost_center, }, ) diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py index 26025169979..d79b6b1bfbc 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py @@ -25,6 +25,7 @@ from erpnext.controllers.tests.test_subcontracting_controller import ( set_backflush_based_on, ) from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom +from erpnext.projects.doctype.project.test_project import make_project from erpnext.stock.doctype.item.test_item import make_item from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import get_gl_entries from erpnext.stock.doctype.serial_and_batch_bundle.test_serial_and_batch_bundle import ( @@ -38,6 +39,9 @@ from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import ( make_subcontracting_receipt, ) +from erpnext.subcontracting.doctype.subcontracting_order.test_subcontracting_order import ( + make_subcontracted_purchase_order, +) from erpnext.subcontracting.doctype.subcontracting_receipt.subcontracting_receipt import ( BOMQuantityError, ) @@ -50,6 +54,26 @@ class TestSubcontractingReceipt(FrappeTestCase): make_service_items() make_bom_for_subcontracted_items() + def test_project_is_carried_over_from_subcontracting_order(self): + project = make_project({"project_name": "_Test SCR Project"}).name + po = make_subcontracted_purchase_order(project) + sco = get_subcontracting_order(po_name=po.name) + + scr = make_subcontracting_receipt(sco.name) + + self.assertEqual(scr.project, project) + self.assertEqual(scr.items[0].project, project) + + def test_project_cannot_differ_from_subcontracting_order(self): + project = make_project({"project_name": "_Test SCR Project"}).name + other_project = make_project({"project_name": "_Test SCR Project 2"}).name + po = make_subcontracted_purchase_order(project) + sco = get_subcontracting_order(po_name=po.name) + + scr = make_subcontracting_receipt(sco.name) + scr.items[0].project = other_project + self.assertRaises(frappe.ValidationError, scr.save) + def test_subcontracting(self): set_backflush_based_on("BOM") make_stock_entry(item_code="_Test Item", qty=100, target="_Test Warehouse 1 - _TC", basic_rate=100)