diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index bdd224f5167..1cc7cadd1e4 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -1246,6 +1246,12 @@ class BOM(WebsiteGenerator): "Row {0}: Workstation or Workstation Type is mandatory for an operation {1}" ).format(d.idx, d.operation) ) + if not d.time_in_mins or d.time_in_mins <= 0: + frappe.throw( + _("Row {0}: Operation time should be greater than 0 for operation {1}").format( + d.idx, d.operation + ) + ) def get_tree_representation(self) -> BOMTree: """Get a complete tree representation preserving order of child items.""" diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py index 6df3a0036fe..3296559afc5 100644 --- a/erpnext/manufacturing/doctype/bom/test_bom.py +++ b/erpnext/manufacturing/doctype/bom/test_bom.py @@ -133,6 +133,15 @@ class TestBOM(ERPNextTestSuite): self.assertAlmostEqual(bom.base_raw_material_cost, base_raw_material_cost) self.assertAlmostEqual(bom.base_total_cost, base_raw_material_cost + base_op_cost) + @timeout + def test_bom_no_operation_time_validation(self): + bom = frappe.copy_doc(self.globalTestRecords["BOM"][2]) + bom.docstatus = 0 + for op_row in bom.operations: + op_row.time_in_mins = 0 + + self.assertRaises(frappe.ValidationError, bom.save) + @timeout def test_bom_cost_with_batch_size(self): bom = frappe.copy_doc(self.globalTestRecords["BOM"][2]) diff --git a/erpnext/manufacturing/doctype/job_card/test_job_card.py b/erpnext/manufacturing/doctype/job_card/test_job_card.py index 9304c3ae191..ed0d84c36e5 100644 --- a/erpnext/manufacturing/doctype/job_card/test_job_card.py +++ b/erpnext/manufacturing/doctype/job_card/test_job_card.py @@ -96,6 +96,7 @@ class TestJobCard(ERPNextTestSuite): "workstation": "_Test Workstation 1", "bom_no": cut_bom, "skip_material_transfer": 1, + "time_in_mins": 60, }, ) final_bom.append( @@ -105,6 +106,7 @@ class TestJobCard(ERPNextTestSuite): "workstation": "_Test Workstation 1", "bom_no": stitch_bom, "skip_material_transfer": 1, + "time_in_mins": 60, }, ) final_bom.append( @@ -115,6 +117,7 @@ class TestJobCard(ERPNextTestSuite): "bom_no": final_bom.name, "is_final_finished_good": 1, "skip_material_transfer": 1, + "time_in_mins": 60, }, ) final_bom.append("items", {"item_code": stitch_fg.name, "qty": 1, "operation_row_id": 3}) @@ -927,6 +930,7 @@ class TestJobCard(ERPNextTestSuite): "bom_no": sfg_bom.name, "finished_good_qty": 1, "sequence_id": 1, + "time_in_mins": 60, } operation2 = { "operation": "Test Operation B", @@ -936,6 +940,7 @@ class TestJobCard(ERPNextTestSuite): "finished_good_qty": 1, "is_final_finished_good": 1, "sequence_id": 2, + "time_in_mins": 60, } make_workstation(operation1)