diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index 09a8c94b9e9..b8b2c31385f 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 c1e801d2faf..7f563ddd269 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.set_gross_profit(item); if(calculate_taxes_and_totals) me.calculate_taxes_and_totals(); } } @@ -876,6 +877,13 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ refresh_field('to_date'); } } + }, + + set_gross_profit: function(item) { + if (this.frm.doc.doctype == "Sales Order" && item.valuation_rate) { + rate = flt(item.rate) * flt(this.frm.doc.conversion_rate || 1); + item.gross_profit = flt(((rate - item.valuation_rate) * item.qty), precision("amount", item)); + } } }); @@ -888,7 +896,8 @@ frappe.ui.form.on(cur_frm.doctype + " Item", "rate", function(frm, cdt, cdn) { } else { item.discount_percentage = 0.0; } - + + cur_frm.cscript.set_gross_profit(item); cur_frm.cscript.calculate_taxes_and_totals(); }) diff --git a/erpnext/selling/doctype/product_bundle/product_bundle.py b/erpnext/selling/doctype/product_bundle/product_bundle.py index 363d334f22f..c8a71677f93 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/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js index b7dff77a6d3..fed5a63b215 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 9e8c2a8caa8..fb65ee8f1a3 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,58 @@ "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, + "options": "Company:company:default_currency", + "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, + "options": "Company:company:default_currency", + "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 +1392,7 @@ "istable": 1, "max_attachments": 0, "menu_index": 0, - "modified": "2016-02-22 09:35:19.701876", + "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 ce64f328c27..05a53877fd0 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.set_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.set_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 afd7bca282b..9fbde8d31cd 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 import json @@ -29,7 +29,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 e9728684846..8f35de54ac3 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -43,8 +43,23 @@ 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)) + + if frappe.db.exists("Product Bundle", args.item_code): + valuation_rate = 0.0 + 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.get("warehouse"))) get_price_list_rate(args, item_doc, out) @@ -68,6 +83,8 @@ def get_item_details(args): if args.get("is_subcontracted") == "Yes": out.bom = get_default_bom(args.item_code) + + get_gross_profit(out) return out @@ -136,13 +153,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), @@ -302,7 +321,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 +372,10 @@ 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"], as_dict=True) \ + or {"projected_qty": 0, "actual_qty": 0, "valuation_rate": 0} @frappe.whitelist() def get_batch_qty(batch_no,warehouse,item_code): @@ -464,3 +484,31 @@ def get_default_bom(item_code=None): return bom else: frappe.throw(_("No default BOM exists for Item {0}").format(item_code)) + +def get_valuation_rate(item_code, warehouse=None): + item = frappe.get_doc("Item", item_code) + if item.is_stock_item: + 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 get_gross_profit(out): + if out.valuation_rate: + out.update({ + "gross_profit": ((out.base_rate - out.valuation_rate) * out.qty) + }) + + return out + \ No newline at end of file