From d313553ae3620df3f1ccec319754da15301324c9 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Thu, 25 Feb 2016 18:59:20 +0530 Subject: [PATCH 1/5] [enhancement] get valuation rate and gross profit on sales order item --- erpnext/controllers/selling_controller.py | 4 +- erpnext/public/js/controllers/transaction.js | 8 ++- .../doctype/sales_order/sales_order.js | 16 +----- .../sales_order_item/sales_order_item.json | 52 ++++++++++++++++++- erpnext/selling/sales_common.js | 13 +++-- .../stock/doctype/stock_entry/stock_entry.py | 4 +- erpnext/stock/get_item_details.py | 25 ++++++--- 7 files changed, 91 insertions(+), 31 deletions(-) diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index 09a8c94b9e..b8b2c31385 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -6,7 +6,7 @@ import frappe from frappe.utils import cint, flt, cstr, comma_or from erpnext.setup.utils import get_company_currency from frappe import _, throw -from erpnext.stock.get_item_details import get_available_qty +from erpnext.stock.get_item_details import get_bin_details from erpnext.controllers.stock_controller import StockController @@ -24,7 +24,7 @@ class SellingController(StockController): def onload(self): if self.doctype in ("Sales Order", "Delivery Note", "Sales Invoice"): for item in self.get("items"): - item.update(get_available_qty(item.item_code, + item.update(get_bin_details(item.item_code, item.warehouse)) def validate(self): diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index c1e801d2fa..eec40d085e 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -603,6 +603,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ callback: function(r) { if (!r.exc && r.message) { me._set_values_for_item_list(r.message); + if(item) me.gross_profit(item); if(calculate_taxes_and_totals) me.calculate_taxes_and_totals(); } } @@ -876,6 +877,10 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ refresh_field('to_date'); } } + }, + + gross_profit: function(item) { + item.gross_profit = flt((((item.rate - item.valuation_rate) * item.qty) * (this.frm.doc.conversion_rate || 1)), precision("amount", item)); } }); @@ -888,7 +893,8 @@ frappe.ui.form.on(cur_frm.doctype + " Item", "rate", function(frm, cdt, cdn) { } else { item.discount_percentage = 0.0; } - + + cur_frm.cscript.gross_profit(item); cur_frm.cscript.calculate_taxes_and_totals(); }) diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js index b7dff77a6d..fed5a63b21 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.js +++ b/erpnext/selling/doctype/sales_order/sales_order.js @@ -117,21 +117,7 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend( tc_name: function() { this.get_terms(); }, - - warehouse: function(doc, cdt, cdn) { - var item = frappe.get_doc(cdt, cdn); - if(item.item_code && item.warehouse) { - return this.frm.call({ - method: "erpnext.stock.get_item_details.get_available_qty", - child: item, - args: { - item_code: item.item_code, - warehouse: item.warehouse, - }, - }); - } - }, - + make_material_request: function() { frappe.model.open_mapped_doc({ method: "erpnext.selling.doctype.sales_order.sales_order.make_material_request", diff --git a/erpnext/selling/doctype/sales_order_item/sales_order_item.json b/erpnext/selling/doctype/sales_order_item/sales_order_item.json index 9e8c2a8caa..6a6f4545fd 100644 --- a/erpnext/selling/doctype/sales_order_item/sales_order_item.json +++ b/erpnext/selling/doctype/sales_order_item/sales_order_item.json @@ -1218,6 +1218,56 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "valuation_rate", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Valuation Rate", + "length": 0, + "no_copy": 1, + "permlevel": 0, + "precision": "", + "print_hide": 1, + "print_hide_if_no_value": 0, + "read_only": 1, + "report_hide": 1, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "gross_profit", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Gross Profit", + "length": 0, + "no_copy": 1, + "permlevel": 0, + "precision": "", + "print_hide": 1, + "print_hide_if_no_value": 0, + "read_only": 1, + "report_hide": 1, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_on_submit": 0, "bold": 0, @@ -1340,7 +1390,7 @@ "istable": 1, "max_attachments": 0, "menu_index": 0, - "modified": "2016-02-22 09:35:19.701876", + "modified": "2016-02-25 17:52:22.402065", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order Item", diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js index ce64f328c2..a29111f42a 100644 --- a/erpnext/selling/sales_common.js +++ b/erpnext/selling/sales_common.js @@ -124,7 +124,8 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({ item.rate = flt(item.price_list_rate * (1 - item.discount_percentage / 100.0), precision("rate", item)); - + + this.gross_profit(item); this.calculate_taxes_and_totals(); }, @@ -135,6 +136,7 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({ } else { this.price_list_rate(doc, cdt, cdn); } + this.gross_profit(item); }, commission_rate: function() { @@ -177,16 +179,21 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({ warehouse: function(doc, cdt, cdn) { var me = this; - this.batch_no(doc, cdt, cdn); var item = frappe.get_doc(cdt, cdn); + if(item.item_code && item.warehouse) { return this.frm.call({ - method: "erpnext.stock.get_item_details.get_available_qty", + method: "erpnext.stock.get_item_details.get_bin_details", child: item, args: { item_code: item.item_code, warehouse: item.warehouse, }, + callback:function(r){ + if (inList(['Delivery Note', 'Sales Invoice'], doc.doctype)) { + me.batch_no(doc, cdt, cdn); + } + } }); } }, diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 28939cae4c..4c376f12a5 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -8,7 +8,7 @@ from frappe import _ from frappe.utils import cstr, cint, flt, comma_or, getdate, nowdate from erpnext.stock.utils import get_incoming_rate from erpnext.stock.stock_ledger import get_previous_sle, NegativeStockError -from erpnext.stock.get_item_details import get_available_qty, get_default_cost_center, get_conversion_factor +from erpnext.stock.get_item_details import get_bin_details, get_default_cost_center, get_conversion_factor from erpnext.manufacturing.doctype.bom.bom import validate_bom_no from erpnext.accounts.utils import validate_fiscal_year import json @@ -30,7 +30,7 @@ class StockEntry(StockController): def onload(self): if self.docstatus==1: for item in self.get("items"): - item.update(get_available_qty(item.item_code, item.s_warehouse)) + item.update(get_bin_details(item.item_code, item.s_warehouse)) def validate(self): self.pro_doc = None diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index e972868484..e036a5d04f 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -43,8 +43,7 @@ def get_item_details(args): get_party_item_code(args, item_doc, out) if out.get("warehouse"): - out.update(get_available_qty(args.item_code, out.warehouse)) - out.update(get_projected_qty(item.name, out.warehouse)) + out.update(get_bin_details(args.item_code, out.warehouse)) get_price_list_rate(args, item_doc, out) @@ -68,6 +67,8 @@ def get_item_details(args): if args.get("is_subcontracted") == "Yes": out.bom = get_default_bom(args.item_code) + + get_goss_profit(out) return out @@ -136,13 +137,15 @@ def get_basic_details(args, item): user_default_warehouse_list = get_user_default_as_list('Warehouse') user_default_warehouse = user_default_warehouse_list[0] \ if len(user_default_warehouse_list)==1 else "" + + warehouse = user_default_warehouse or args.warehouse or item.default_warehouse out = frappe._dict({ "item_code": item.name, "item_name": item.item_name, "description": cstr(item.description).strip(), "image": cstr(item.image).strip(), - "warehouse": user_default_warehouse or args.warehouse or item.default_warehouse, + "warehouse": warehouse, "income_account": get_default_income_account(args, item), "expense_account": get_default_expense_account(args, item), "cost_center": get_default_cost_center(args, item), @@ -164,7 +167,8 @@ def get_basic_details(args, item): "net_amount": 0.0, "discount_percentage": 0.0, "supplier": item.default_supplier, - "delivered_by_supplier": item.delivered_by_supplier + "delivered_by_supplier": item.delivered_by_supplier, + "valuation_rate": get_bin_details(item.name, warehouse) }) # if default specified in item is for another company, fetch from company @@ -302,7 +306,7 @@ def get_pos_profile_item_details(company, args, pos_profile=None): res[fieldname] = pos_profile.get(fieldname) if res.get("warehouse"): - res.actual_qty = get_available_qty(args.item_code, + res.actual_qty = get_bin_details(args.item_code, res.warehouse).get("actual_qty") return res @@ -353,9 +357,9 @@ def get_projected_qty(item_code, warehouse): {"item_code": item_code, "warehouse": warehouse}, "projected_qty")} @frappe.whitelist() -def get_available_qty(item_code, warehouse): +def get_bin_details(item_code, warehouse): return frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse}, - ["projected_qty", "actual_qty"], as_dict=True) or {"projected_qty": 0, "actual_qty": 0} + ["projected_qty", "actual_qty", "valuation_rate"], as_dict=True) or {"projected_qty": 0, "actual_qty": 0} @frappe.whitelist() def get_batch_qty(batch_no,warehouse,item_code): @@ -464,3 +468,10 @@ def get_default_bom(item_code=None): return bom else: frappe.throw(_("No default BOM exists for Item {0}").format(item_code)) + + +def get_goss_profit(out): + out.update({ + "gross_profit": ((out.price_list_rate - out.valuation_rate) * out.qty) * (out.conversio_rate or 1) + }) + return out From 2d7af6335140b6955612637e9f357a54fcbb0bed Mon Sep 17 00:00:00 2001 From: Saurabh Date: Fri, 26 Feb 2016 11:09:20 +0530 Subject: [PATCH 2/5] [fixe] typo-fixes and set base currency symbol to gross-profit and valuation rate --- erpnext/public/js/controllers/transaction.js | 10 ++++++---- .../doctype/sales_order_item/sales_order_item.json | 4 +++- erpnext/selling/sales_common.js | 2 +- erpnext/stock/get_item_details.py | 8 +++++--- 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index eec40d085e..cb8f4e6835 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -603,7 +603,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ callback: function(r) { if (!r.exc && r.message) { me._set_values_for_item_list(r.message); - if(item) me.gross_profit(item); + if(item) me.set_gross_profit(item); if(calculate_taxes_and_totals) me.calculate_taxes_and_totals(); } } @@ -879,8 +879,10 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ } }, - gross_profit: function(item) { - item.gross_profit = flt((((item.rate - item.valuation_rate) * item.qty) * (this.frm.doc.conversion_rate || 1)), precision("amount", item)); + set_gross_profit: function(item) { + if (this.frm.doc.doctype == "Sales Order") { + item.gross_profit = flt((((item.rate - item.valuation_rate) * item.qty) * (this.frm.doc.conversion_rate || 1)), precision("amount", item)); + } } }); @@ -894,7 +896,7 @@ frappe.ui.form.on(cur_frm.doctype + " Item", "rate", function(frm, cdt, cdn) { item.discount_percentage = 0.0; } - cur_frm.cscript.gross_profit(item); + cur_frm.cscript.set_gross_profit(item); cur_frm.cscript.calculate_taxes_and_totals(); }) diff --git a/erpnext/selling/doctype/sales_order_item/sales_order_item.json b/erpnext/selling/doctype/sales_order_item/sales_order_item.json index 6a6f4545fd..fb65ee8f1a 100644 --- a/erpnext/selling/doctype/sales_order_item/sales_order_item.json +++ b/erpnext/selling/doctype/sales_order_item/sales_order_item.json @@ -1232,6 +1232,7 @@ "label": "Valuation Rate", "length": 0, "no_copy": 1, + "options": "Company:company:default_currency", "permlevel": 0, "precision": "", "print_hide": 1, @@ -1257,6 +1258,7 @@ "label": "Gross Profit", "length": 0, "no_copy": 1, + "options": "Company:company:default_currency", "permlevel": 0, "precision": "", "print_hide": 1, @@ -1390,7 +1392,7 @@ "istable": 1, "max_attachments": 0, "menu_index": 0, - "modified": "2016-02-25 17:52:22.402065", + "modified": "2016-02-26 11:08:24.708912", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order Item", diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js index a29111f42a..ef9cadc0be 100644 --- a/erpnext/selling/sales_common.js +++ b/erpnext/selling/sales_common.js @@ -125,7 +125,7 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({ item.rate = flt(item.price_list_rate * (1 - item.discount_percentage / 100.0), precision("rate", item)); - this.gross_profit(item); + this.set_gross_profit(item); this.calculate_taxes_and_totals(); }, diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index e036a5d04f..9b61c00e83 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -68,7 +68,7 @@ def get_item_details(args): if args.get("is_subcontracted") == "Yes": out.bom = get_default_bom(args.item_code) - get_goss_profit(out) + get_gross_profit(out) return out @@ -470,8 +470,10 @@ def get_default_bom(item_code=None): frappe.throw(_("No default BOM exists for Item {0}").format(item_code)) -def get_goss_profit(out): +def get_gross_profit(out): + if isinstance(out.valuation_rate, dict): return out + out.update({ - "gross_profit": ((out.price_list_rate - out.valuation_rate) * out.qty) * (out.conversio_rate or 1) + "gross_profit": ((out.base_rate - out.valuation_rate) * out.qty) }) return out From 5ada14b8871d019024f3ac059dbe73572907b51c Mon Sep 17 00:00:00 2001 From: Saurabh Date: Fri, 26 Feb 2016 18:02:55 +0530 Subject: [PATCH 3/5] [fixes] calculate valuation rate for non stock item --- erpnext/public/js/controllers/transaction.js | 2 +- erpnext/selling/sales_common.js | 2 +- erpnext/stock/get_item_details.py | 22 +++++++++++++------- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index cb8f4e6835..3023a9f1a2 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -880,7 +880,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ }, set_gross_profit: function(item) { - if (this.frm.doc.doctype == "Sales Order") { + if (this.frm.doc.doctype == "Sales Order" && item.valuation_rate) { item.gross_profit = flt((((item.rate - item.valuation_rate) * item.qty) * (this.frm.doc.conversion_rate || 1)), precision("amount", item)); } } diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js index ef9cadc0be..05a53877fd 100644 --- a/erpnext/selling/sales_common.js +++ b/erpnext/selling/sales_common.js @@ -136,7 +136,7 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({ } else { this.price_list_rate(doc, cdt, cdn); } - this.gross_profit(item); + this.set_gross_profit(item); }, commission_rate: function() { diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 9b61c00e83..a98a25add8 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -44,6 +44,8 @@ def get_item_details(args): if out.get("warehouse"): out.update(get_bin_details(args.item_code, out.warehouse)) + else: + out.update(get_valuation_rate(args.item_code)) get_price_list_rate(args, item_doc, out) @@ -167,8 +169,7 @@ def get_basic_details(args, item): "net_amount": 0.0, "discount_percentage": 0.0, "supplier": item.default_supplier, - "delivered_by_supplier": item.delivered_by_supplier, - "valuation_rate": get_bin_details(item.name, warehouse) + "delivered_by_supplier": item.delivered_by_supplier }) # if default specified in item is for another company, fetch from company @@ -359,7 +360,8 @@ def get_projected_qty(item_code, warehouse): @frappe.whitelist() def get_bin_details(item_code, warehouse): return frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse}, - ["projected_qty", "actual_qty", "valuation_rate"], as_dict=True) or {"projected_qty": 0, "actual_qty": 0} + ["projected_qty", "actual_qty", "valuation_rate"], as_dict=True) \ + or {"projected_qty": 0, "actual_qty": 0, "valuation_rate": 0} @frappe.whitelist() def get_batch_qty(batch_no,warehouse,item_code): @@ -469,11 +471,17 @@ def get_default_bom(item_code=None): else: frappe.throw(_("No default BOM exists for Item {0}").format(item_code)) +def get_valuation_rate(item_code): + valuation_rate =frappe.db.sql("""select sum(base_net_amount) / sum(qty) from `tabPurchase Invoice Item` + where item_code = %s and docstatus=1""", item_code) + + if valuation_rate: + return {"valuation_rate": valuation_rate[0][0]} def get_gross_profit(out): - if isinstance(out.valuation_rate, dict): return out + if out.valuation_rate: + out.update({ + "gross_profit": ((out.base_rate - out.valuation_rate) * out.qty) + }) - out.update({ - "gross_profit": ((out.base_rate - out.valuation_rate) * out.qty) - }) return out From bd01a81638aaae65ec54f182f46054569166ce27 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Mon, 7 Mar 2016 14:32:23 +0530 Subject: [PATCH 4/5] [fixes] calculate valuation rate for stock, non-stop and product bundle items --- erpnext/stock/get_item_details.py | 63 +++++++++++++++++++++++++++---- 1 file changed, 56 insertions(+), 7 deletions(-) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index a98a25add8..7160eb4c6f 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -44,8 +44,20 @@ def get_item_details(args): if out.get("warehouse"): out.update(get_bin_details(args.item_code, out.warehouse)) + + if is_item_product_bundle(args.item_code): + bundled_items = get_bundled_items(args.item_code) + valuation_rate = 0.0 + + for item in bundled_items: + valuation_rate += flt(get_valuation_rate(item.item_code, out).get("valuation_rate") * item.qty) + + out.update({ + "valuation_rate": valuation_rate + }) + else: - out.update(get_valuation_rate(args.item_code)) + out.update(get_valuation_rate(args.item_code, out)) get_price_list_rate(args, item_doc, out) @@ -360,7 +372,7 @@ def get_projected_qty(item_code, warehouse): @frappe.whitelist() def get_bin_details(item_code, warehouse): return frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse}, - ["projected_qty", "actual_qty", "valuation_rate"], as_dict=True) \ + ["projected_qty", "actual_qty"], as_dict=True) \ or {"projected_qty": 0, "actual_qty": 0, "valuation_rate": 0} @frappe.whitelist() @@ -471,12 +483,48 @@ def get_default_bom(item_code=None): else: frappe.throw(_("No default BOM exists for Item {0}").format(item_code)) -def get_valuation_rate(item_code): - valuation_rate =frappe.db.sql("""select sum(base_net_amount) / sum(qty) from `tabPurchase Invoice Item` - where item_code = %s and docstatus=1""", item_code) +def get_valuation_rate(item_code, out): + item = frappe.get_doc("Item", item_code) + if item.is_stock_item: + warehouse = out.get("warehouse") - if valuation_rate: - return {"valuation_rate": valuation_rate[0][0]} + if not warehouse: + warehouse = item.default_warehouse + + return frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse}, + ["valuation_rate"], as_dict=True) or {"valuation_rate": 0} + + elif not item.is_stock_item: + valuation_rate =frappe.db.sql("""select sum(base_net_amount) / sum(qty) + from `tabPurchase Invoice Item` + where item_code = %s and docstatus=1""", item_code) + + if valuation_rate: + return {"valuation_rate": valuation_rate[0][0] or 0.0} + else: + return {"valuation_rate": 0.0} + +def is_item_product_bundle(item_code): + if frappe.db.get_value("Product Bundle", item_code): + return True + return False + +def get_bundled_items(item_code, bundled_items=None): + if not bundled_items: + bundled_items = [] + + doc = frappe.get_doc("Product Bundle", item_code) + + for item in doc.items: + if is_item_product_bundle(item.item_code): + get_bundled_items(item.item_code, bundled_items) + + bundled_items.append(frappe._dict({ + "item_code": item.item_code, + "qty": item.qty + })) + + return bundled_items def get_gross_profit(out): if out.valuation_rate: @@ -485,3 +533,4 @@ def get_gross_profit(out): }) return out + From 3fbf3ce852b3f2f662beb464cfe9b9c5e26d455c Mon Sep 17 00:00:00 2001 From: Saurabh Date: Wed, 9 Mar 2016 15:31:04 +0530 Subject: [PATCH 5/5] [fixes] raise if selects product bundle as sub item in another product bundle. --- erpnext/public/js/controllers/transaction.js | 3 +- .../doctype/product_bundle/product_bundle.py | 8 +++- erpnext/stock/get_item_details.py | 44 +++++-------------- 3 files changed, 20 insertions(+), 35 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 3023a9f1a2..7f563ddd26 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -881,7 +881,8 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ set_gross_profit: function(item) { if (this.frm.doc.doctype == "Sales Order" && item.valuation_rate) { - item.gross_profit = flt((((item.rate - item.valuation_rate) * item.qty) * (this.frm.doc.conversion_rate || 1)), precision("amount", item)); + rate = flt(item.rate) * flt(this.frm.doc.conversion_rate || 1); + item.gross_profit = flt(((rate - item.valuation_rate) * item.qty), precision("amount", item)); } } }); diff --git a/erpnext/selling/doctype/product_bundle/product_bundle.py b/erpnext/selling/doctype/product_bundle/product_bundle.py index 363d334f22..c8a71677f9 100644 --- a/erpnext/selling/doctype/product_bundle/product_bundle.py +++ b/erpnext/selling/doctype/product_bundle/product_bundle.py @@ -14,6 +14,7 @@ class ProductBundle(Document): def validate(self): self.validate_main_item() + self.validate_child_items() from erpnext.utilities.transaction_base import validate_uom_is_integer validate_uom_is_integer(self, "uom", "qty") @@ -21,7 +22,12 @@ class ProductBundle(Document): """Validates, main Item is not a stock item""" if frappe.db.get_value("Item", self.new_item_code, "is_stock_item"): frappe.throw(_("Parent Item {0} must not be a Stock Item").format(self.new_item_code)) - + + def validate_child_items(self): + for item in self.items: + if frappe.db.exists("Product Bundle", item.item_code): + frappe.throw(_("Child Item should not be a Product Bundle. Please remove item `{0}` and save").format(item.item_code)) + def get_new_item_code(doctype, txt, searchfield, start, page_len, filters): from erpnext.controllers.queries import get_match_cond diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 7160eb4c6f..8f35de54ac 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -45,19 +45,21 @@ def get_item_details(args): if out.get("warehouse"): out.update(get_bin_details(args.item_code, out.warehouse)) - if is_item_product_bundle(args.item_code): - bundled_items = get_bundled_items(args.item_code) + if frappe.db.exists("Product Bundle", args.item_code): valuation_rate = 0.0 - - for item in bundled_items: - valuation_rate += flt(get_valuation_rate(item.item_code, out).get("valuation_rate") * item.qty) + bundled_items = frappe.get_doc("Product Bundle", args.item_code) + + for bundle_item in bundled_items.items: + valuation_rate += \ + flt(get_valuation_rate(bundle_item.item_code, out.get("warehouse")).get("valuation_rate") \ + * bundle_item.qty) out.update({ "valuation_rate": valuation_rate }) - + else: - out.update(get_valuation_rate(args.item_code, out)) + out.update(get_valuation_rate(args.item_code, out.get("warehouse"))) get_price_list_rate(args, item_doc, out) @@ -483,11 +485,9 @@ def get_default_bom(item_code=None): else: frappe.throw(_("No default BOM exists for Item {0}").format(item_code)) -def get_valuation_rate(item_code, out): +def get_valuation_rate(item_code, warehouse=None): item = frappe.get_doc("Item", item_code) if item.is_stock_item: - warehouse = out.get("warehouse") - if not warehouse: warehouse = item.default_warehouse @@ -503,29 +503,7 @@ def get_valuation_rate(item_code, out): return {"valuation_rate": valuation_rate[0][0] or 0.0} else: return {"valuation_rate": 0.0} - -def is_item_product_bundle(item_code): - if frappe.db.get_value("Product Bundle", item_code): - return True - return False - -def get_bundled_items(item_code, bundled_items=None): - if not bundled_items: - bundled_items = [] - - doc = frappe.get_doc("Product Bundle", item_code) - - for item in doc.items: - if is_item_product_bundle(item.item_code): - get_bundled_items(item.item_code, bundled_items) - - bundled_items.append(frappe._dict({ - "item_code": item.item_code, - "qty": item.qty - })) - return bundled_items - def get_gross_profit(out): if out.valuation_rate: out.update({ @@ -533,4 +511,4 @@ def get_gross_profit(out): }) return out - + \ No newline at end of file