Merge pull request #4876 from saurabh6790/valuation_rate_on_so

[enhancement] get valuation rate and gross profit on sales order item
diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py
index 09a8c94..b8b2c31 100644
--- a/erpnext/controllers/selling_controller.py
+++ b/erpnext/controllers/selling_controller.py
@@ -6,7 +6,7 @@
 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 @@
 	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 c1e801d..7f563dd 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -603,6 +603,7 @@
 			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 @@
 				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 @@
 	} 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 363d334..c8a7167 100644
--- a/erpnext/selling/doctype/product_bundle/product_bundle.py
+++ b/erpnext/selling/doctype/product_bundle/product_bundle.py
@@ -14,6 +14,7 @@
 
 	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 @@
 		"""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 b7dff77..fed5a63 100644
--- a/erpnext/selling/doctype/sales_order/sales_order.js
+++ b/erpnext/selling/doctype/sales_order/sales_order.js
@@ -117,21 +117,7 @@
 	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 9e8c2a8..fb65ee8 100644
--- a/erpnext/selling/doctype/sales_order_item/sales_order_item.json
+++ b/erpnext/selling/doctype/sales_order_item/sales_order_item.json
@@ -1222,6 +1222,58 @@
    "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, 
+   "collapsible": 0, 
    "description": "For Production", 
    "fieldname": "planned_qty", 
    "fieldtype": "Float", 
@@ -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 ce64f32..05a5387 100644
--- a/erpnext/selling/sales_common.js
+++ b/erpnext/selling/sales_common.js
@@ -124,7 +124,8 @@
 
 		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 @@
 		} else {
 			this.price_list_rate(doc, cdt, cdn);
 		}
+		this.set_gross_profit(item);
 	},
 
 	commission_rate: function() {
@@ -177,16 +179,21 @@
 
 	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 afd7bca..9fbde8d 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.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 @@
 	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 e972868..8f35de5 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -43,8 +43,23 @@
 	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 @@
 
 	if args.get("is_subcontracted") == "Yes":
 		out.bom = get_default_bom(args.item_code)
+		
+	get_gross_profit(out)
 
 	return out
 
@@ -136,13 +153,15 @@
 	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 @@
 				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 @@
 		{"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 @@
 			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