Merge branch 'develop' into fg_based_operating_cost
diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js
index 4dd8205..4304193 100644
--- a/erpnext/manufacturing/doctype/bom/bom.js
+++ b/erpnext/manufacturing/doctype/bom/bom.js
@@ -65,7 +65,21 @@
});
},
- onload_post_render(frm) {
+ validate: function(frm) {
+ if (frm.doc.fg_based_operating_cost && frm.doc.with_operations) {
+ frappe.throw({message: __("Please check either with operations or FG Based Operating Cost."), title: __("Mandatory")});
+ }
+ },
+
+ with_operations: function(frm) {
+ frm.set_df_property("fg_based_operating_cost", "hidden", frm.doc.with_operations ? 1 : 0);
+ },
+
+ fg_based_operating_cost: function(frm) {
+ frm.set_df_property("with_operations", "hidden", frm.doc.fg_based_operating_cost ? 1 : 0);
+ },
+
+ onload_post_render: function(frm) {
frm.get_field("items").grid.set_multiple_add("item_code", "qty");
},
@@ -532,18 +546,25 @@
};
erpnext.bom.calculate_op_cost = function(doc) {
- var op = doc.operations || [];
doc.operating_cost = 0.0;
doc.base_operating_cost = 0.0;
- for(var i=0;i<op.length;i++) {
- var operating_cost = flt(flt(op[i].hour_rate) * flt(op[i].time_in_mins) / 60, 2);
- var base_operating_cost = flt(operating_cost * doc.conversion_rate, 2);
- frappe.model.set_value('BOM Operation',op[i].name, "operating_cost", operating_cost);
- frappe.model.set_value('BOM Operation',op[i].name, "base_operating_cost", base_operating_cost);
+ if(doc.with_operations) {
+ doc.operations.forEach((item) => {
+ let operating_cost = flt(flt(item.hour_rate) * flt(item.time_in_mins) / 60, 2);
+ let base_operating_cost = flt(operating_cost * doc.conversion_rate, 2);
+ frappe.model.set_value('BOM Operation',item.name, {
+ "operating_cost": operating_cost,
+ "base_operating_cost": base_operating_cost
+ });
- doc.operating_cost += operating_cost;
- doc.base_operating_cost += base_operating_cost;
+ doc.operating_cost += operating_cost;
+ doc.base_operating_cost += base_operating_cost;
+ });
+ } else if(doc.fg_based_operating_cost) {
+ let total_operating_cost = doc.quantity * flt(doc.operating_cost_per_bom_quantity);
+ doc.operating_cost = total_operating_cost;
+ doc.base_operating_cost = flt(total_operating_cost * doc.conversion_rate, 2);
}
refresh_field(['operating_cost', 'base_operating_cost']);
};
diff --git a/erpnext/manufacturing/doctype/bom/bom.json b/erpnext/manufacturing/doctype/bom/bom.json
index c31b69f..c2b331f 100644
--- a/erpnext/manufacturing/doctype/bom/bom.json
+++ b/erpnext/manufacturing/doctype/bom/bom.json
@@ -33,6 +33,9 @@
"column_break_23",
"transfer_material_against",
"routing",
+ "fg_based_operating_cost",
+ "fg_based_section_section",
+ "operating_cost_per_bom_quantity",
"operations_section",
"operations",
"materials_section",
@@ -575,7 +578,26 @@
{
"fieldname": "scrap_items_section",
"fieldtype": "Section Break",
+ "hide_border": 1,
"label": "Scrap Items"
+ },
+ {
+ "default": "0",
+ "fieldname": "fg_based_operating_cost",
+ "fieldtype": "Check",
+ "label": "FG based Operating Cost"
+ },
+ {
+ "depends_on": "fg_based_operating_cost",
+ "fieldname": "fg_based_section_section",
+ "fieldtype": "Section Break",
+ "label": "FG Based Operating Cost Section"
+ },
+ {
+ "depends_on": "fg_based_operating_cost",
+ "fieldname": "operating_cost_per_bom_quantity",
+ "fieldtype": "Currency",
+ "label": "Operating Cost Per BOM Quantity"
}
],
"icon": "fa fa-sitemap",
@@ -583,7 +605,7 @@
"image_field": "image",
"is_submittable": 1,
"links": [],
- "modified": "2023-01-03 18:42:27.732107",
+ "modified": "2023-01-10 07:47:08.652616",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "BOM",
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index 53af28d..8ab79e6 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -614,18 +614,26 @@
"""Update workstation rate and calculates totals"""
self.operating_cost = 0
self.base_operating_cost = 0
- for d in self.get("operations"):
- if d.workstation:
- self.update_rate_and_time(d, update_hour_rate)
+ if self.get("with_operations"):
+ for d in self.get("operations"):
+ if d.workstation:
+ self.update_rate_and_time(d, update_hour_rate)
- 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)
+ 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)
+ self.operating_cost += flt(operating_cost)
+ self.base_operating_cost += flt(base_operating_cost)
+
+ elif self.get("fg_based_operating_cost"):
+ total_operating_cost = flt(self.get("quantity")) * flt(
+ self.get("operating_cost_per_bom_quantity")
+ )
+ self.operating_cost = total_operating_cost
+ self.base_operating_cost = flt(total_operating_cost * self.conversion_rate, 2)
def update_rate_and_time(self, row, update_hour_rate=False):
if not row.hour_rate or update_hour_rate:
diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py
index 16f5c79..d60feb2 100644
--- a/erpnext/manufacturing/doctype/bom/test_bom.py
+++ b/erpnext/manufacturing/doctype/bom/test_bom.py
@@ -202,6 +202,33 @@
self.assertEqual(bom.items[0].rate, 20)
+ def test_bom_cost_with_fg_based_operating_cost(self):
+ bom = frappe.copy_doc(test_records[4])
+ bom.insert()
+
+ raw_material_cost = 0.0
+ op_cost = 0.0
+
+ op_cost = bom.quantity * bom.operating_cost_per_bom_quantity
+
+ for row in bom.items:
+ raw_material_cost += row.amount
+
+ base_raw_material_cost = raw_material_cost * flt(
+ bom.conversion_rate, bom.precision("conversion_rate")
+ )
+ base_op_cost = op_cost * flt(bom.conversion_rate, bom.precision("conversion_rate"))
+
+ # test amounts in selected currency, almostEqual checks for 7 digits by default
+ self.assertAlmostEqual(bom.operating_cost, op_cost)
+ self.assertAlmostEqual(bom.raw_material_cost, raw_material_cost)
+ self.assertAlmostEqual(bom.total_cost, raw_material_cost + op_cost)
+
+ # test amounts in selected currency
+ self.assertAlmostEqual(bom.base_operating_cost, base_op_cost)
+ 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_subcontractor_sourced_item(self):
item_code = "_Test Subcontracted FG Item 1"
set_backflush_based_on("Material Transferred for Subcontract")
diff --git a/erpnext/manufacturing/doctype/bom/test_records.json b/erpnext/manufacturing/doctype/bom/test_records.json
index 507d319..e9cbdfe 100644
--- a/erpnext/manufacturing/doctype/bom/test_records.json
+++ b/erpnext/manufacturing/doctype/bom/test_records.json
@@ -162,5 +162,31 @@
"item": "_Test Variant Item",
"quantity": 1.0,
"with_operations": 1
+ },
+ {
+ "items": [
+ {
+ "amount": 5000.0,
+ "doctype": "BOM Item",
+ "item_code": "_Test Item",
+ "parentfield": "items",
+ "qty": 2.0,
+ "rate": 3000.0,
+ "uom": "_Test UOM",
+ "stock_uom": "_Test UOM",
+ "source_warehouse": "_Test Warehouse - _TC",
+ "include_item_in_manufacturing": 1
+ }
+ ],
+ "docstatus": 1,
+ "doctype": "BOM",
+ "is_active": 1,
+ "is_default": 1,
+ "currency": "USD",
+ "item": "_Test Variant Item",
+ "quantity": 1.0,
+ "with_operations": 0,
+ "fg_based_operating_cost": 1,
+ "operating_cost_per_bom_quantity": 140
}
]