diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index e9edef76944..cb7911346ec 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -498,7 +498,11 @@ class BOM(WebsiteGenerator): order_by="sequence_id, idx", ): child = self.append("operations", row) - child.hour_rate = flt(row.hour_rate / self.conversion_rate, child.precision("hour_rate")) + # guard against a 0/unset conversion rate (e.g. a foreign-currency BOM with no + # exchange-rate record), mirroring the fallback used elsewhere in this file + child.hour_rate = flt( + row.hour_rate / (flt(self.conversion_rate) or 1), child.precision("hour_rate") + ) @staticmethod def _get_routing_fields(): diff --git a/erpnext/manufacturing/doctype/routing/test_routing.py b/erpnext/manufacturing/doctype/routing/test_routing.py index 1cec3b657b4..851def6d7b6 100644 --- a/erpnext/manufacturing/doctype/routing/test_routing.py +++ b/erpnext/manufacturing/doctype/routing/test_routing.py @@ -85,6 +85,37 @@ class TestRouting(ERPNextTestSuite): self.assertEqual(bom_doc.operations[0].time_in_mins, 30) self.assertEqual(bom_doc.operations[1].time_in_mins, 20) + def test_get_routing_survives_zero_conversion_rate(self): + # A foreign-currency BOM with no exchange-rate record leaves conversion_rate at 0. + # get_routing() divides the operation hour rate by it and used to raise + # ZeroDivisionError; it must now fall back to a rate of 1. + operations = [ + { + "operation": "Test Operation A", + "workstation": "_Test Workstation A", + "hour_rate_rent": 300, + "hour_rate_labour": 750, + "time_in_mins": 30, + }, + ] + setup_operations(operations) + routing_doc = create_routing( + routing_name="Zero Rate Route", + operations=[ + {"operation": "Test Operation A", "workstation": "_Test Workstation A", "time_in_mins": 30} + ], + ) + + bom = frappe.new_doc("BOM") + bom.routing = routing_doc.name + bom.conversion_rate = 0 + + bom.get_routing() # must not raise ZeroDivisionError + + self.assertTrue(bom.operations) + # with the 0 rate falling back to 1, the hour rate is carried over unchanged + self.assertEqual(bom.operations[0].hour_rate, routing_doc.operations[0].hour_rate) + def setup_operations(rows): from erpnext.manufacturing.doctype.operation.test_operation import make_operation