Merge branch 'bom_currency' into develop

This commit is contained in:
Rohit Waghchaure 2017-09-05 11:26:22 +05:30
commit 2967d7ed01
9 changed files with 366 additions and 213 deletions

View File

@ -5,7 +5,10 @@ frappe.provide("erpnext.bom");
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 @@ frappe.ui.form.on("BOM", {
}
};
});
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 @@ frappe.ui.form.on("BOM", {
});
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,39 +125,34 @@ erpnext.bom.BomController = erpnext.TransactionController.extend({
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"]);
item.stock_qty = flt(item.qty * item.conversion_factor, precision("stock_qty", item));
refresh_field("stock_qty", item.name, item.parentfield);
this.toggle_conversion_factor(item);
this.frm.events.update_cost(this.frm);
}
},
})
});
$.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 +171,7 @@ var get_bom_material_detail= function(doc, cdt, cdn, scrap_items) {
$.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 +180,13 @@ var get_bom_material_detail= function(doc, cdt, cdn, scrap_items) {
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 +204,14 @@ cur_frm.cscript.rate = function(doc, cdt, cdn) {
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 +220,7 @@ erpnext.bom.calculate_op_cost = function(doc) {
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 +228,7 @@ erpnext.bom.calculate_op_cost = function(doc) {
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 +237,23 @@ erpnext.bom.calculate_rm_cost = function(doc) {
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 +261,34 @@ erpnext.bom.calculate_scrap_materials_cost = function(doc) {
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 +309,7 @@ frappe.ui.form.on("BOM Operation", "operation", function(frm, cdt, cdn) {
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 +322,22 @@ frappe.ui.form.on("BOM Operation", "workstation", function(frm, cdt, cdn) {
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 +350,7 @@ frappe.ui.form.on("BOM Item", "items_remove", function(frm) {
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 +361,4 @@ frappe.ui.form.on("BOM", "with_operations", function(frm) {
cur_frm.cscript.image = function() {
refresh_field("image_view");
}
};

View File

@ -322,6 +322,37 @@
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"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,
@ -1051,37 +1082,6 @@
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"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,
@ -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",

View File

@ -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 @@ class BOM(WebsiteGenerator):
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 @@ class BOM(WebsiteGenerator):
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 @@ class BOM(WebsiteGenerator):
'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 @@ class BOM(WebsiteGenerator):
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 @@ class BOM(WebsiteGenerator):
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 @@ class BOM(WebsiteGenerator):
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 @@ class BOM(WebsiteGenerator):
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 @@ class BOM(WebsiteGenerator):
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 @@ class BOM(WebsiteGenerator):
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 @@ class BOM(WebsiteGenerator):
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 @@ class BOM(WebsiteGenerator):
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 @@ class BOM(WebsiteGenerator):
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 @@ class BOM(WebsiteGenerator):
'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 @@ class BOM(WebsiteGenerator):
"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():

View File

@ -14,14 +14,16 @@ test_records = frappe.get_test_records('BOM')
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 @@ class TestBOM(unittest.TestCase):
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})

View File

@ -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"
}

View File

@ -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
@ -596,37 +687,6 @@
"unique": 0,
"width": "150px"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"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,
@ -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",

View File

@ -437,3 +437,4 @@ erpnext.patches.v8_7.fix_purchase_receipt_status
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

View File

View File

@ -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)
""")