fix: calculate operating cost based on BOM Quantity (#27464)

* fix: calculate operating cost based on BOM Quantity

* fix: added test cases
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index 28a84b2..232e3a0 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -510,8 +510,14 @@
 			if d.workstation:
 				self.update_rate_and_time(d, update_hour_rate)
 
-			self.operating_cost += flt(d.operating_cost)
-			self.base_operating_cost += flt(d.base_operating_cost)
+			operating_cost = d.operating_cost
+			base_operating_cost = d.base_operating_cost
+			if d.set_cost_based_on_bom_qty:
+				operating_cost = flt(d.cost_per_unit) * flt(self.quantity)
+				base_operating_cost = flt(d.base_cost_per_unit) * flt(self.quantity)
+
+			self.operating_cost += flt(operating_cost)
+			self.base_operating_cost += flt(base_operating_cost)
 
 	def update_rate_and_time(self, row, update_hour_rate = False):
 		if not row.hour_rate or update_hour_rate:
@@ -535,6 +541,8 @@
 			row.base_hour_rate = flt(row.hour_rate) * flt(self.conversion_rate)
 			row.operating_cost = flt(row.hour_rate) * flt(row.time_in_mins) / 60.0
 			row.base_operating_cost = flt(row.operating_cost) * flt(self.conversion_rate)
+			row.cost_per_unit = row.operating_cost / (row.batch_size or 1.0)
+			row.base_cost_per_unit = row.base_operating_cost / (row.batch_size or 1.0)
 
 		if update_hour_rate:
 			row.db_update()
diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py
index 7950dd9..706ea26 100644
--- a/erpnext/manufacturing/doctype/bom/test_bom.py
+++ b/erpnext/manufacturing/doctype/bom/test_bom.py
@@ -108,6 +108,24 @@
 		self.assertAlmostEqual(bom.base_raw_material_cost, base_raw_material_cost)
 		self.assertAlmostEqual(bom.base_total_cost, base_raw_material_cost + base_op_cost)
 
+	def test_bom_cost_with_batch_size(self):
+		bom = frappe.copy_doc(test_records[2])
+		bom.docstatus = 0
+		op_cost = 0.0
+		for op_row in bom.operations:
+			op_row.docstatus = 0
+			op_row.batch_size = 2
+			op_row.set_cost_based_on_bom_qty = 1
+			op_cost += op_row.operating_cost
+
+		bom.save()
+
+		for op_row in bom.operations:
+			self.assertAlmostEqual(op_row.cost_per_unit, op_row.operating_cost / 2)
+
+		self.assertAlmostEqual(bom.operating_cost, op_cost/2)
+		bom.delete()
+
 	def test_bom_cost_multi_uom_multi_currency_based_on_price_list(self):
 		frappe.db.set_value("Price List", "_Test Price List", "price_not_uom_dependent", 1)
 		for item_code, rate in (("_Test Item", 3600), ("_Test Item Home Desktop Manufactured", 3000)):
diff --git a/erpnext/manufacturing/doctype/bom_operation/bom_operation.json b/erpnext/manufacturing/doctype/bom_operation/bom_operation.json
index 4458e6d..ec617f3 100644
--- a/erpnext/manufacturing/doctype/bom_operation/bom_operation.json
+++ b/erpnext/manufacturing/doctype/bom_operation/bom_operation.json
@@ -8,15 +8,23 @@
  "field_order": [
   "sequence_id",
   "operation",
-  "workstation",
-  "description",
   "col_break1",
-  "hour_rate",
+  "workstation",
   "time_in_mins",
-  "operating_cost",
+  "costing_section",
+  "hour_rate",
   "base_hour_rate",
+  "column_break_9",
+  "operating_cost",
   "base_operating_cost",
+  "column_break_11",
   "batch_size",
+  "set_cost_based_on_bom_qty",
+  "cost_per_unit",
+  "base_cost_per_unit",
+  "more_information_section",
+  "description",
+  "column_break_18",
   "image"
  ],
  "fields": [
@@ -117,13 +125,59 @@
    "fieldname": "sequence_id",
    "fieldtype": "Int",
    "label": "Sequence ID"
+  },
+  {
+   "depends_on": "eval:doc.batch_size > 0 && doc.set_cost_based_on_bom_qty",
+   "fieldname": "cost_per_unit",
+   "fieldtype": "Float",
+   "label": "Cost Per Unit",
+   "no_copy": 1,
+   "print_hide": 1,
+   "read_only": 1
+  },
+  {
+   "fieldname": "base_cost_per_unit",
+   "fieldtype": "Float",
+   "hidden": 1,
+   "label": "Base Cost Per Unit",
+   "no_copy": 1,
+   "print_hide": 1,
+   "read_only": 1
+  },
+  {
+   "fieldname": "costing_section",
+   "fieldtype": "Section Break",
+   "label": "Costing"
+  },
+  {
+   "fieldname": "column_break_11",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "more_information_section",
+   "fieldtype": "Section Break",
+   "label": "More Information"
+  },
+  {
+   "fieldname": "column_break_18",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "column_break_9",
+   "fieldtype": "Column Break"
+  },
+  {
+   "default": "0",
+   "fieldname": "set_cost_based_on_bom_qty",
+   "fieldtype": "Check",
+   "label": "Set Operating Cost Based On BOM Quantity"
   }
  ],
  "idx": 1,
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2021-01-12 14:48:09.596843",
+ "modified": "2021-09-13 16:45:01.092868",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "BOM Operation",