BOM rates as per selected currency and UOM
diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js
index c58c89c..9a140f5 100644
--- a/erpnext/manufacturing/doctype/bom/bom.js
+++ b/erpnext/manufacturing/doctype/bom/bom.js
@@ -5,7 +5,10 @@
frappe.ui.form.on("BOM", {
setup: function(frm) {
- frm.add_fetch('buying_price_list', 'currency', 'currency');
+ frm.add_fetch("item", "description", "description");
+ frm.add_fetch("item", "image", "image");
+ frm.add_fetch("item", "item_name", "item_name");
+ frm.add_fetch("item", "stock_uom", "uom");
frm.set_query("bom_no", "items", function() {
return {
@@ -23,6 +26,38 @@
}
};
});
+
+ frm.set_query("item", function() {
+ return {
+ query: "erpnext.controllers.queries.item_query"
+ };
+ });
+
+ frm.set_query("project", function() {
+ return{
+ filters:[
+ ['Project', 'status', 'not in', 'Completed, Cancelled']
+ ]
+ };
+ });
+
+ frm.set_query("item_code", "items", function() {
+ return {
+ query: "erpnext.controllers.queries.item_query",
+ filters: [["Item", "name", "!=", cur_frm.doc.item]]
+ };
+ });
+
+ frm.set_query("bom_no", "items", function(doc, cdt, cdn) {
+ var d = locals[cdt][cdn];
+ return {
+ filters: {
+ 'item': d.item_code,
+ 'is_active': 1,
+ 'docstatus': 1
+ }
+ };
+ });
},
onload_post_render: function(frm) {
@@ -69,14 +104,14 @@
});
erpnext.bom.BomController = erpnext.TransactionController.extend({
- conversion_rate: function(doc, cdt, cdn) {
+ conversion_rate: function(doc) {
if(this.frm.doc.currency === this.get_company_currency()) {
this.frm.set_value("conversion_rate", 1.0);
} else {
erpnext.bom.update_cost(doc);
}
},
-
+
item_code: function(doc, cdt, cdn){
var scrap_items = false;
var child = locals[cdt][cdn];
@@ -90,7 +125,7 @@
get_bom_material_detail(doc, cdt, cdn, scrap_items);
},
- conversion_factor: function(doc, cdt, cdn, dont_fetch_price_list_rate) {
+ conversion_factor: function(doc, cdt, cdn) {
if(frappe.meta.get_docfield(cdt, "stock_qty", cdn)) {
var item = frappe.get_doc(cdt, cdn);
frappe.model.round_floats_in(item, ["qty", "conversion_factor"]);
@@ -99,30 +134,24 @@
this.toggle_conversion_factor(item);
}
},
-})
+});
$.extend(cur_frm.cscript, new erpnext.bom.BomController({frm: cur_frm}));
-cur_frm.add_fetch("item", "description", "description");
-cur_frm.add_fetch("item", "image", "image");
-cur_frm.add_fetch("item", "item_name", "item_name");
-cur_frm.add_fetch("item", "stock_uom", "uom");
-
-
-cur_frm.cscript.hour_rate = function(doc, dt, dn) {
+cur_frm.cscript.hour_rate = function(doc) {
erpnext.bom.calculate_op_cost(doc);
erpnext.bom.calculate_total(doc);
-}
+};
cur_frm.cscript.time_in_mins = cur_frm.cscript.hour_rate;
cur_frm.cscript.bom_no = function(doc, cdt, cdn) {
get_bom_material_detail(doc, cdt, cdn, false);
-}
+};
cur_frm.cscript.is_default = function(doc) {
if (doc.is_default) cur_frm.set_value("is_active", 1);
-}
+};
var get_bom_material_detail= function(doc, cdt, cdn, scrap_items) {
var d = locals[cdt][cdn];
@@ -141,6 +170,7 @@
$.extend(d, r.message);
refresh_field("items");
refresh_field("scrap_items");
+
doc = locals[doc.doctype][doc.name];
erpnext.bom.calculate_rm_cost(doc);
erpnext.bom.calculate_scrap_materials_cost(doc);
@@ -149,13 +179,13 @@
freeze: true
});
}
-}
+};
-cur_frm.cscript.qty = function(doc, cdt, cdn) {
+cur_frm.cscript.qty = function(doc) {
erpnext.bom.calculate_rm_cost(doc);
erpnext.bom.calculate_scrap_materials_cost(doc);
erpnext.bom.calculate_total(doc);
-}
+};
cur_frm.cscript.rate = function(doc, cdt, cdn) {
var d = locals[cdt][cdn];
@@ -173,14 +203,14 @@
erpnext.bom.calculate_scrap_materials_cost(doc);
erpnext.bom.calculate_total(doc);
}
-}
+};
erpnext.bom.update_cost = function(doc) {
erpnext.bom.calculate_op_cost(doc);
erpnext.bom.calculate_rm_cost(doc);
erpnext.bom.calculate_scrap_materials_cost(doc);
erpnext.bom.calculate_total(doc);
-}
+};
erpnext.bom.calculate_op_cost = function(doc) {
var op = doc.operations || [];
@@ -189,7 +219,7 @@
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(flt(op[i].base_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);
@@ -197,7 +227,7 @@
doc.base_operating_cost += base_operating_cost;
}
refresh_field(['operating_cost', 'base_operating_cost']);
-}
+};
// rm : raw material
erpnext.bom.calculate_rm_cost = function(doc) {
@@ -206,19 +236,23 @@
var base_total_rm_cost = 0;
for(var i=0;i<rm.length;i++) {
var amount = flt(rm[i].rate) * flt(rm[i].qty);
- var base_amount = flt(rm[i].rate) * flt(doc.conversion_rate) * flt(rm[i].qty);
- frappe.model.set_value('BOM Item', rm[i].name, 'base_rate', flt(rm[i].rate) * flt(doc.conversion_rate))
- frappe.model.set_value('BOM Item', rm[i].name, 'amount', amount)
- frappe.model.set_value('BOM Item', rm[i].name, 'qty_consumed_per_unit', flt(rm[i].qty)/flt(doc.quantity))
- frappe.model.set_value('BOM Item', rm[i].name, 'base_amount', base_amount)
+ var base_amount = amount * flt(doc.conversion_rate);
+
+ frappe.model.set_value('BOM Item', rm[i].name, 'base_rate',
+ flt(rm[i].rate) * flt(doc.conversion_rate));
+ frappe.model.set_value('BOM Item', rm[i].name, 'amount', amount);
+ frappe.model.set_value('BOM Item', rm[i].name, 'base_amount', base_amount);
+ frappe.model.set_value('BOM Item', rm[i].name,
+ 'qty_consumed_per_unit', flt(rm[i].stock_qty)/flt(doc.quantity));
+
total_rm_cost += amount;
base_total_rm_cost += base_amount;
}
cur_frm.set_value("raw_material_cost", total_rm_cost);
cur_frm.set_value("base_raw_material_cost", base_total_rm_cost);
-}
+};
-//sm : scrap material
+// sm : scrap material
erpnext.bom.calculate_scrap_materials_cost = function(doc) {
var sm = doc.scrap_items || [];
var total_sm_cost = 0;
@@ -226,64 +260,34 @@
for(var i=0;i<sm.length;i++) {
var base_rate = flt(sm[i].rate) * flt(doc.conversion_rate);
- var amount = flt(sm[i].rate) * flt(sm[i].qty);
- var base_amount = flt(sm[i].rate) * flt(sm[i].qty) * flt(doc.conversion_rate);
+ var amount = flt(sm[i].rate) * flt(sm[i].stock_qty);
+ var base_amount = amount * flt(doc.conversion_rate);
+
frappe.model.set_value('BOM Scrap Item',sm[i].name, 'base_rate', base_rate);
frappe.model.set_value('BOM Scrap Item',sm[i].name, 'amount', amount);
frappe.model.set_value('BOM Scrap Item',sm[i].name, 'base_amount', base_amount);
-
+
total_sm_cost += amount;
base_total_sm_cost += base_amount;
}
-
+
cur_frm.set_value("scrap_material_cost", total_sm_cost);
cur_frm.set_value("base_scrap_material_cost", base_total_sm_cost);
-}
+};
// Calculate Total Cost
erpnext.bom.calculate_total = function(doc) {
var total_cost = flt(doc.operating_cost) + flt(doc.raw_material_cost) - flt(doc.scrap_material_cost);
- var base_total_cost = flt(doc.base_operating_cost) + flt(doc.base_raw_material_cost) - flt(doc.base_scrap_material_cost);
+ var base_total_cost = flt(doc.base_operating_cost) + flt(doc.base_raw_material_cost)
+ - flt(doc.base_scrap_material_cost);
+
cur_frm.set_value("total_cost", total_cost);
cur_frm.set_value("base_total_cost", base_total_cost);
-}
+};
-
-cur_frm.fields_dict['item'].get_query = function(doc) {
- return{
- query: "erpnext.controllers.queries.item_query"
- }
-}
-
-cur_frm.fields_dict['project'].get_query = function(doc, dt, dn) {
- return{
- filters:[
- ['Project', 'status', 'not in', 'Completed, Cancelled']
- ]
- }
-}
-
-cur_frm.fields_dict['items'].grid.get_field('item_code').get_query = function(doc) {
- return{
- query: "erpnext.controllers.queries.item_query",
- filters: [["Item", "name", "!=", cur_frm.doc.item]]
- }
-}
-
-cur_frm.fields_dict['items'].grid.get_field('bom_no').get_query = function(doc, cdt, cdn) {
- var d = locals[cdt][cdn];
- return{
- filters:{
- 'item': d.item_code,
- 'is_active': 1,
- 'docstatus': 1
- }
- }
-}
-
-cur_frm.cscript.validate = function(doc, dt, dn) {
- erpnext.bom.update_cost(doc)
-}
+cur_frm.cscript.validate = function(doc) {
+ erpnext.bom.update_cost(doc);
+};
frappe.ui.form.on("BOM Operation", "operation", function(frm, cdt, cdn) {
var d = locals[cdt][cdn];
@@ -304,7 +308,7 @@
frappe.model.set_value(d.doctype, d.name, "workstation", data.message.workstation);
}
}
- })
+ });
});
frappe.ui.form.on("BOM Operation", "workstation", function(frm, cdt, cdn) {
@@ -317,21 +321,22 @@
name: d.workstation
},
callback: function (data) {
- frappe.model.set_value(d.doctype, d.name, "hour_rate", data.message.hour_rate);
- frappe.model.set_value(d.doctype, d.name, "base_hour_rate", flt(data.message.hour_rate) * flt(frm.doc.conversion_rate));
+ frappe.model.set_value(d.doctype, d.name, "base_hour_rate", data.message.hour_rate);
+ frappe.model.set_value(d.doctype, d.name, "hour_rate",
+ flt(flt(data.message.hour_rate) / flt(frm.doc.conversion_rate)), 2);
+
erpnext.bom.calculate_op_cost(frm.doc);
erpnext.bom.calculate_total(frm.doc);
}
- })
+ });
});
frappe.ui.form.on("BOM Item", "qty", function(frm, cdt, cdn) {
var d = locals[cdt][cdn];
d.stock_qty = d.qty * d.conversion_factor;
- refresh_field("items");
+ refresh_field("stock_qty", d.name, d.parentfield);
});
-
-
+
frappe.ui.form.on("BOM Operation", "operations_remove", function(frm) {
erpnext.bom.calculate_op_cost(frm.doc);
erpnext.bom.calculate_total(frm.doc);
@@ -344,7 +349,7 @@
var toggle_operations = function(frm) {
frm.toggle_display("operations_section", cint(frm.doc.with_operations) == 1);
-}
+};
frappe.ui.form.on("BOM", "with_operations", function(frm) {
if(!cint(frm.doc.with_operations)) {
@@ -355,4 +360,4 @@
cur_frm.cscript.image = function() {
refresh_field("image_view");
-}
+};
diff --git a/erpnext/manufacturing/doctype/bom/bom.json b/erpnext/manufacturing/doctype/bom/bom.json
index 46a1ffd..2558df3 100644
--- a/erpnext/manufacturing/doctype/bom/bom.json
+++ b/erpnext/manufacturing/doctype/bom/bom.json
@@ -328,6 +328,37 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Company",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Company",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 1,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
"fieldname": "currency_detail",
"fieldtype": "Section Break",
"hidden": 0,
@@ -1057,37 +1088,6 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
- "fieldname": "company",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Company",
- "length": 0,
- "no_copy": 0,
- "options": "Company",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 1,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "amended_from",
"fieldtype": "Link",
"hidden": 0,
@@ -1322,7 +1322,7 @@
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
- "collapsible": 0,
+ "collapsible": 1,
"columns": 0,
"depends_on": "eval:!doc.__islocal",
"fieldname": "section_break0",
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index e48a002..bf5fef5 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -2,7 +2,7 @@
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
-import frappe
+import frappe, erpnext
from frappe.utils import cint, cstr, flt
from frappe import _
from erpnext.setup.utils import get_exchange_rate
@@ -41,16 +41,12 @@
self.name = 'BOM-' + self.item + ('-%.3i' % idx)
def validate(self):
- # if not self.route:
self.route = frappe.scrub(self.name).replace('_', '-')
self.clear_operations()
self.validate_main_item()
self.validate_currency()
self.set_conversion_rate()
-
- from erpnext.utilities.transaction_base import validate_uom_is_integer
- validate_uom_is_integer(self, "stock_uom", "stock_qty", "BOM Item")
-
+ self.validate_uom_is_interger()
self.update_stock_qty()
self.set_bom_material_details()
self.validate_materials()
@@ -96,14 +92,18 @@
def set_bom_material_details(self):
for item in self.get("items"):
- ret = self.get_bom_material_detail({"item_code": item.item_code, "item_name": item.item_name, "bom_no": item.bom_no,
- "stock_qty": item.stock_qty})
+ self.validate_bom_currecny(item)
+
+ ret = self.get_bom_material_detail({
+ "item_code": item.item_code,
+ "item_name": item.item_name,
+ "bom_no": item.bom_no,
+ "stock_qty": item.stock_qty
+ })
for r in ret:
if not item.get(r):
item.set(r, ret[r])
- self.validate_bom_currecny(item)
-
def get_bom_material_detail(self, args=None):
""" Get raw material details like uom, desc and rate"""
if not args:
@@ -126,17 +126,19 @@
'image' : item and args['image'] or '',
'stock_uom' : item and args['stock_uom'] or '',
'uom' : item and args['stock_uom'] or '',
- 'conversion_factor' : 1,
+ 'conversion_factor': 1,
'bom_no' : args['bom_no'],
- 'rate' : rate,
+ 'rate' : rate / self.conversion_rate,
+ 'qty' : args.get("qty") or args.get("stock_qty") or 1,
'stock_qty' : args.get("qty") or args.get("stock_qty") or 1,
- 'base_rate' : rate if self.company_currency() == self.currency else rate * self.conversion_rate
+ 'base_rate' : rate
}
return ret_item
def validate_bom_currecny(self, item):
if item.get('bom_no') and frappe.db.get_value('BOM', item.get('bom_no'), 'currency') != self.currency:
- frappe.throw(_("Row {0}: Currency of the BOM #{1} should be equal to the selected currency {2}").format(item.idx, item.bom_no, self.currency))
+ frappe.throw(_("Row {0}: Currency of the BOM #{1} should be equal to the selected currency {2}")
+ .format(item.idx, item.bom_no, self.currency))
def get_rm_rate(self, arg):
""" Get raw material rate as per selected method, if bom exists takes bom cost """
@@ -148,17 +150,21 @@
if self.rm_cost_as_per == 'Valuation Rate':
rate = self.get_valuation_rate(arg)
elif self.rm_cost_as_per == 'Last Purchase Rate':
- rate = arg['last_purchase_rate']
+ rate = arg['last_purchase_rate'] \
+ or frappe.db.get_value("Item", arg['item_code'], "last_purchase_rate")
elif self.rm_cost_as_per == "Price List":
if not self.buying_price_list:
frappe.throw(_("Please select Price List"))
- rate = frappe.db.get_value("Item Price", {"price_list": self.buying_price_list,
- "item_code": arg["item_code"]}, "price_list_rate") or 0
+ rate = frappe.db.get_value("Item Price",
+ {"price_list": self.buying_price_list, "item_code": arg["item_code"]}, "price_list_rate")
+ price_list_currency = frappe.db.get_value("Price List", self.buying_price_list, "currency")
+ if price_list_currency != self.company_currency():
+ rate = flt(rate * self.conversion_rate)
if arg['bom_no'] and (not rate or self.set_rate_of_sub_assembly_item_based_on_bom):
rate = self.get_bom_unitcost(arg['bom_no'])
- return rate
+ return flt(rate)
def update_cost(self, update_parent=True, from_child_bom=False):
if self.docstatus == 2:
@@ -167,10 +173,9 @@
existing_bom_cost = self.total_cost
for d in self.get("items"):
- rate = self.get_bom_material_detail({'item_code': d.item_code, 'bom_no': d.bom_no,
- 'stock_qty': d.stock_qty})["rate"]
+ rate = self.get_rm_rate({"item_code": d.item_code, "bom_no": d.bom_no})
if rate:
- d.rate = rate
+ d.rate = rate * flt(d.conversion_factor) / flt(self.conversion_rate)
if self.docstatus == 1:
self.flags.ignore_validate_update_after_submit = True
@@ -190,7 +195,7 @@
frappe.msgprint(_("Cost Updated"))
def get_bom_unitcost(self, bom_no):
- bom = frappe.db.sql("""select name, total_cost/quantity as unit_cost from `tabBOM`
+ bom = frappe.db.sql("""select name, base_total_cost/quantity as unit_cost from `tabBOM`
where is_active = 1 and name = %s""", bom_no, as_dict=1)
return bom and bom[0]['unit_cost'] or 0
@@ -253,14 +258,14 @@
frappe.throw(_("Quantity should be greater than 0"))
def validate_currency(self):
- if self.rm_cost_as_per == 'Price List' and \
- frappe.db.get_value('Price List', self.buying_price_list, 'currency') != self.currency:
- frappe.throw(_("Currency of the price list {0} is not similar with the selected currency {1}").format(self.buying_price_list, self.currency))
+ if self.rm_cost_as_per == 'Price List':
+ price_list_currency = frappe.db.get_value('Price List', self.buying_price_list, 'currency')
+ if price_list_currency not in (self.currency, self.company_currency()):
+ frappe.throw(_("Currency of the price list {0} must be {1} or {2}")
+ .format(self.buying_price_list, self.currency, self.company_currency()))
-
def update_stock_qty(self):
for m in self.get('items'):
-
if not m.conversion_factor:
m.conversion_factor = flt(get_conversion_factor(m.item_code, m.uom)['conversion_factor'])
if m.uom and m.qty:
@@ -268,10 +273,17 @@
if not m.uom and m.stock_uom:
m.uom = m.stock_uom
m.qty = m.stock_qty
-
-
+
+ def validate_uom_is_interger(self):
+ from erpnext.utilities.transaction_base import validate_uom_is_integer
+ validate_uom_is_integer(self, "uom", "qty", "BOM Item")
+ validate_uom_is_integer(self, "stock_uom", "stock_qty", "BOM Item")
+
def set_conversion_rate(self):
- self.conversion_rate = get_exchange_rate(self.currency, self.company_currency())
+ if self.currency == self.company_currency():
+ self.conversion_rate = 1
+ elif self.conversion_rate == 1 or flt(self.conversion_rate) <= 0:
+ self.conversion_rate = get_exchange_rate(self.currency, self.company_currency())
def validate_materials(self):
""" Validate raw material entries """
@@ -289,7 +301,7 @@
for m in self.get('items'):
if m.bom_no:
validate_bom_no(m.item_code, m.bom_no)
- if flt(m.stock_qty) <= 0:
+ if flt(m.qty) <= 0:
frappe.throw(_("Quantity required for Item {0} in row {1}").format(m.item_code, m.idx))
check_list.append(m)
@@ -361,12 +373,13 @@
for d in self.get('operations'):
if d.workstation:
if not d.hour_rate:
- d.hour_rate = flt(frappe.db.get_value("Workstation", d.workstation, "hour_rate"))
+ hour_rate = flt(frappe.db.get_value("Workstation", d.workstation, "hour_rate"))
+ d.hour_rate = hour_rate / flt(self.conversion_rate)
if d.hour_rate and d.time_in_mins:
- d.operating_cost = flt(d.hour_rate) * flt(d.time_in_mins) / 60.0
d.base_hour_rate = flt(d.hour_rate) * flt(self.conversion_rate)
- d.base_operating_cost = flt(d.base_hour_rate) * flt(d.time_in_mins) / 60.0
+ d.operating_cost = flt(d.hour_rate) * flt(d.time_in_mins) / 60.0
+ d.base_operating_cost = flt(d.operating_cost) * flt(self.conversion_rate)
self.operating_cost += flt(d.operating_cost)
self.base_operating_cost += flt(d.base_operating_cost)
@@ -378,9 +391,11 @@
for d in self.get('items'):
d.base_rate = flt(d.rate) * flt(self.conversion_rate)
- d.amount = flt(d.rate, self.precision("rate", d)) * flt(d.stock_qty, self.precision("stock_qty", d))
+ d.amount = flt(d.rate, d.precision("rate")) * flt(d.qty, d.precision("qty"))
d.base_amount = d.amount * flt(self.conversion_rate)
- d.qty_consumed_per_unit = flt(d.stock_qty, self.precision("stock_qty", d)) / flt(self.quantity, self.precision("quantity"))
+ d.qty_consumed_per_unit = flt(d.stock_qty, d.precision("stock_qty")) \
+ / flt(self.quantity, self.precision("quantity"))
+
total_rm_cost += d.amount
base_total_rm_cost += d.base_amount
@@ -394,7 +409,7 @@
for d in self.get('scrap_items'):
d.base_rate = d.rate * self.conversion_rate
- d.amount = flt(d.rate, self.precision("rate", d)) * flt(d.stock_qty, self.precision("stock_qty", d))
+ d.amount = flt(d.rate, d.precision("rate")) * flt(d.stock_qty, d.precision("stock_qty"))
d.base_amount = d.amount * self.conversion_rate
total_sm_cost += d.amount
base_total_sm_cost += d.base_amount
@@ -421,12 +436,12 @@
'description' : d.description,
'image' : d.image,
'stock_uom' : d.stock_uom,
- 'stock_qty' : flt(d.stock_qty),
+ 'stock_qty' : flt(d.stock_qty),
'rate' : d.base_rate,
}))
def company_currency(self):
- return frappe.db.get_value('Company', self.company, 'default_currency')
+ return erpnext.get_company_currency(self.company)
def add_to_cur_exploded_items(self, args):
if self.cur_exploded_items.get(args.item_code):
@@ -459,6 +474,7 @@
"Add items to Flat BOM table"
frappe.db.sql("""delete from `tabBOM Explosion Item` where parent=%s""", self.name)
self.set('exploded_items', [])
+
for d in sorted(self.cur_exploded_items, key=itemgetter(0)):
ch = self.append('exploded_items', {})
for i in self.cur_exploded_items[d].keys():
diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py
index d678444..b2684e3 100644
--- a/erpnext/manufacturing/doctype/bom/test_bom.py
+++ b/erpnext/manufacturing/doctype/bom/test_bom.py
@@ -14,14 +14,16 @@
class TestBOM(unittest.TestCase):
def test_get_items(self):
from erpnext.manufacturing.doctype.bom.bom import get_bom_items_as_dict
- items_dict = get_bom_items_as_dict(bom=get_default_bom(), company="_Test Company", qty=1, fetch_exploded=0)
+ items_dict = get_bom_items_as_dict(bom=get_default_bom(),
+ company="_Test Company", qty=1, fetch_exploded=0)
self.assertTrue(test_records[2]["items"][0]["item_code"] in items_dict)
self.assertTrue(test_records[2]["items"][1]["item_code"] in items_dict)
self.assertEquals(len(items_dict.values()), 2)
def test_get_items_exploded(self):
from erpnext.manufacturing.doctype.bom.bom import get_bom_items_as_dict
- items_dict = get_bom_items_as_dict(bom=get_default_bom(), company="_Test Company", qty=1, fetch_exploded=1)
+ items_dict = get_bom_items_as_dict(bom=get_default_bom(),
+ company="_Test Company", qty=1, fetch_exploded=1)
self.assertTrue(test_records[2]["items"][0]["item_code"] in items_dict)
self.assertFalse(test_records[2]["items"][1]["item_code"] in items_dict)
self.assertTrue(test_records[0]["items"][0]["item_code"] in items_dict)
@@ -75,5 +77,50 @@
where item_code='_Test Item 2' and docstatus=1""", as_dict=1):
self.assertEqual(d.rate, rm_rate + 10)
+ def test_bom_cost(self):
+ bom = frappe.copy_doc(test_records[2])
+ bom.insert()
+
+ # test amounts in selected currency
+ self.assertEqual(bom.operating_cost, 100)
+ self.assertEqual(bom.raw_material_cost, 8000)
+ self.assertEqual(bom.total_cost, 8100)
+
+ # test amounts in selected currency
+ self.assertEqual(bom.base_operating_cost, 6000)
+ self.assertEqual(bom.base_raw_material_cost, 480000)
+ self.assertEqual(bom.base_total_cost, 486000)
+
+ def test_bom_cost_multi_uom_multi_currency(self):
+ for item_code, rate in (("_Test Item", 3600), ("_Test Item Home Desktop Manufactured", 3000)):
+ item_price = frappe.new_doc("Item Price")
+ item_price.price_list = "_Test Price List"
+ item_price.item_code = item_code
+ item_price.price_list_rate = rate
+ item_price.insert()
+
+ bom = frappe.copy_doc(test_records[2])
+ bom.rm_cost_as_per = "Price List"
+ bom.buying_price_list = "_Test Price List"
+ bom.items[0].uom = "_Test UOM 1"
+ bom.items[0].conversion_factor = 5
+ bom.insert()
+
+ bom.update_cost()
+
+ # test amounts in selected currency
+ self.assertEqual(bom.items[0].rate, 300)
+ self.assertEqual(bom.items[1].rate, 50)
+ self.assertEqual(bom.operating_cost, 100)
+ self.assertEqual(bom.raw_material_cost, 450)
+ self.assertEqual(bom.total_cost, 550)
+
+ # test amounts in selected currency
+ self.assertEqual(bom.items[0].base_rate, 18000)
+ self.assertEqual(bom.items[1].base_rate, 3000)
+ self.assertEqual(bom.base_operating_cost, 6000)
+ self.assertEqual(bom.base_raw_material_cost, 27000)
+ self.assertEqual(bom.base_total_cost, 33000)
+
def get_default_bom(item_code="_Test FG Item 2"):
return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1})
diff --git a/erpnext/manufacturing/doctype/bom/test_records.json b/erpnext/manufacturing/doctype/bom/test_records.json
index 6c24871..cc95642 100644
--- a/erpnext/manufacturing/doctype/bom/test_records.json
+++ b/erpnext/manufacturing/doctype/bom/test_records.json
@@ -6,8 +6,9 @@
"doctype": "BOM Item",
"item_code": "_Test Serialized Item With Series",
"parentfield": "items",
- "stock_qty": 1.0,
+ "qty": 1.0,
"rate": 5000.0,
+ "uom": "_Test UOM",
"stock_uom": "_Test UOM",
"source_warehouse": "_Test Warehouse - _TC"
},
@@ -16,8 +17,9 @@
"doctype": "BOM Item",
"item_code": "_Test Item 2",
"parentfield": "items",
- "stock_qty": 2.0,
+ "qty": 2.0,
"rate": 1000.0,
+ "uom": "_Test UOM",
"stock_uom": "_Test UOM",
"source_warehouse": "_Test Warehouse - _TC"
}
@@ -34,9 +36,9 @@
"scrap_items":[
{
"amount": 2000.0,
- "doctype": "BOM Item",
+ "doctype": "BOM Scrap Item",
"item_code": "_Test Item Home Desktop 100",
- "parentfield": "items",
+ "parentfield": "scrap_items",
"stock_qty": 1.0,
"rate": 2000.0,
"stock_uom": "_Test UOM"
@@ -48,8 +50,9 @@
"doctype": "BOM Item",
"item_code": "_Test Item",
"parentfield": "items",
- "stock_qty": 1.0,
+ "qty": 1.0,
"rate": 5000.0,
+ "uom": "_Test UOM",
"stock_uom": "_Test UOM",
"source_warehouse": "_Test Warehouse - _TC"
},
@@ -58,8 +61,9 @@
"doctype": "BOM Item",
"item_code": "_Test Item Home Desktop 100",
"parentfield": "items",
- "stock_qty": 2.0,
+ "qty": 2.0,
"rate": 1000.0,
+ "uom": "_Test UOM",
"stock_uom": "_Test UOM",
"source_warehouse": "_Test Warehouse - _TC"
}
@@ -78,6 +82,7 @@
"operation": "_Test Operation 1",
"description": "_Test",
"workstation": "_Test Workstation 1",
+ "hour_rate": 100,
"time_in_mins": 60,
"operating_cost": 100
}
@@ -88,19 +93,21 @@
"doctype": "BOM Item",
"item_code": "_Test Item",
"parentfield": "items",
- "stock_qty": 1.0,
+ "qty": 1.0,
"rate": 5000.0,
+ "uom": "_Test UOM",
"stock_uom": "_Test UOM",
"source_warehouse": "_Test Warehouse - _TC"
},
{
- "amount": 2000.0,
+ "amount": 3000.0,
"bom_no": "BOM-_Test Item Home Desktop Manufactured-001",
"doctype": "BOM Item",
"item_code": "_Test Item Home Desktop Manufactured",
"parentfield": "items",
- "stock_qty": 3.0,
+ "qty": 3.0,
"rate": 1000.0,
+ "uom": "_Test UOM",
"stock_uom": "_Test UOM",
"source_warehouse": "_Test Warehouse - _TC"
}
@@ -110,6 +117,8 @@
"is_active": 1,
"is_default": 1,
"currency": "USD",
+ "conversion_rate": 60,
+ "company": "_Test Company",
"item": "_Test FG Item 2",
"quantity": 1.0,
"with_operations": 1
@@ -130,8 +139,9 @@
"doctype": "BOM Item",
"item_code": "_Test Item",
"parentfield": "items",
- "stock_qty": 2.0,
+ "qty": 2.0,
"rate": 3000.0,
+ "uom": "_Test UOM",
"stock_uom": "_Test UOM",
"source_warehouse": "_Test Warehouse - _TC"
}
diff --git a/erpnext/manufacturing/doctype/bom_item/bom_item.json b/erpnext/manufacturing/doctype/bom_item/bom_item.json
index e0c4bab..5147bd0 100644
--- a/erpnext/manufacturing/doctype/bom_item/bom_item.json
+++ b/erpnext/manufacturing/doctype/bom_item/bom_item.json
@@ -171,7 +171,7 @@
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
- "collapsible": 0,
+ "collapsible": 1,
"columns": 0,
"fieldname": "section_break_5",
"fieldtype": "Section Break",
@@ -182,6 +182,7 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
+ "label": "Description",
"length": 0,
"no_copy": 0,
"permlevel": 0,
@@ -373,7 +374,7 @@
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
- "reqd": 0,
+ "reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
@@ -404,7 +405,7 @@
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
- "reqd": 0,
+ "reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
@@ -464,7 +465,39 @@
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
- "reqd": 1,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "stock_uom",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Stock UOM",
+ "length": 0,
+ "no_copy": 0,
+ "oldfieldname": "stock_uom",
+ "oldfieldtype": "Data",
+ "options": "UOM",
+ "permlevel": 0,
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
@@ -505,8 +538,8 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
- "fieldname": "stock_uom",
- "fieldtype": "Link",
+ "fieldname": "rate_amount_section",
+ "fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@@ -514,16 +547,45 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
- "label": "Stock UOM",
+ "label": "Rate & Amount",
"length": 0,
"no_copy": 0,
- "oldfieldname": "stock_uom",
- "oldfieldtype": "Data",
- "options": "UOM",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "description": "",
+ "fieldname": "rate",
+ "fieldtype": "Currency",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 0,
+ "label": "Rate",
+ "length": 0,
+ "no_copy": 0,
+ "options": "currency",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
- "read_only": 1,
+ "read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
@@ -537,8 +599,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
- "description": "See \"Rate Of Materials Based On\" in Costing Section",
- "fieldname": "rate",
+ "fieldname": "base_rate",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
@@ -547,17 +608,47 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
- "label": "Rate",
+ "label": "Basic Rate (Company Currency)",
"length": 0,
"no_copy": 0,
- "options": "currency",
+ "options": "Company:company:default_currency",
"permlevel": 0,
+ "precision": "",
+ "print_hide": 1,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "column_break_21",
+ "fieldtype": "Column Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
- "reqd": 1,
+ "reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
@@ -602,37 +693,6 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
- "fieldname": "base_rate",
- "fieldtype": "Currency",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Basic Rate (Company Currency)",
- "length": 0,
- "no_copy": 0,
- "options": "Company:company:default_currency",
- "permlevel": 0,
- "precision": "",
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "base_amount",
"fieldtype": "Currency",
"hidden": 0,
@@ -700,7 +760,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
- "in_list_view": 1,
+ "in_list_view": 0,
"in_standard_filter": 0,
"label": "Scrap %",
"length": 0,
@@ -760,7 +820,7 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
- "modified": "2017-07-04 17:42:37.218408",
+ "modified": "2017-08-18 16:22:46.078661",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "BOM Item",
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index c14cbab..d874234 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -437,3 +437,4 @@
erpnext.patches.v8_6.rename_bom_update_tool
erpnext.patches.v8_9.add_setup_progress_actions
erpnext.patches.v8_9.rename_company_sales_target_field
+erpnext.patches.v8_8.set_bom_rate_as_per_uom
\ No newline at end of file
diff --git a/erpnext/patches/v8_8/__init__.py b/erpnext/patches/v8_8/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/patches/v8_8/__init__.py
diff --git a/erpnext/patches/v8_8/set_bom_rate_as_per_uom.py b/erpnext/patches/v8_8/set_bom_rate_as_per_uom.py
new file mode 100644
index 0000000..5b169cd
--- /dev/null
+++ b/erpnext/patches/v8_8/set_bom_rate_as_per_uom.py
@@ -0,0 +1,13 @@
+# Copyright (c) 2017, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+import frappe
+
+def execute():
+ frappe.db.sql("""
+ update `tabBOM Item`
+ set rate = rate * conversion_factor
+ where uom != stock_uom and docstatus < 2
+ and conversion_factor not in (0, 1)
+ """)
\ No newline at end of file