[buying, selling] [refactor] get item details
diff --git a/buying/doctype/purchase_common/purchase_common.js b/buying/doctype/purchase_common/purchase_common.js
index dacee80..ff875ba 100644
--- a/buying/doctype/purchase_common/purchase_common.js
+++ b/buying/doctype/purchase_common/purchase_common.js
@@ -83,26 +83,48 @@
 	
 	item_code: function(doc, cdt, cdn) {
 		var me = this;
-		var item = locals[cdt][cdn];
+		var item = wn.model.get_doc(cdt, cdn);
 		
+		// validate company
 		if(item.item_code) {
-			this.frm.call({
-				method: "buying.utils.get_item_details",
-				child: item,
-				args: {
-					args: {
-						doctype: me.frm.doc.doctype,
-						docname: me.frm.doc.name,
-						item_code: item.item_code,
-						warehouse: item.warehouse,
-						supplier: me.frm.doc.supplier,
-						conversion_rate: me.frm.doc.conversion_rate,
-						price_list_name: me.frm.doc.price_list_name,
-						price_list_currency: me.frm.doc.price_list_currency,
-						plc_conversion_rate: me.frm.doc.plc_conversion_rate
-					}
-				},
+			var fetch = true;
+			$.each(["company", "supplier"], function(i, fieldname) {
+				if(!me.frm.doc[fieldname]) {
+					fetch = false;
+					msgprint(wn._("Please specify") + ": " + 
+						wn.meta.get_label(me.frm.doc.doctype, fieldname, me.frm.doc.name) + 
+						". " + wn._("It is needed to fetch Item Details."));
+				}
 			});
+			
+			if(!fetch) {
+				item.item_code = null;
+				refresh_field("item_code", item.name, item.parentfield);
+			} else {
+				this.frm.call({
+					method: "buying.utils.get_item_details",
+					child: item,
+					args: {
+						args: {
+							item_code: item.item_code,
+							warehouse: item.warehouse,
+							doctype: me.frm.doc.doctype,
+							docname: me.frm.doc.name,
+							supplier: me.frm.doc.supplier,
+							conversion_rate: me.frm.doc.conversion_rate,
+							price_list_name: me.frm.doc.price_list_name,
+							price_list_currency: me.frm.doc.price_list_currency,
+							plc_conversion_rate: me.frm.doc.plc_conversion_rate,
+							is_subcontracted: me.frm.doc.is_subcontracted,
+							company: me.frm.doc.company,
+							currency: me.frm.doc.currency
+						}
+					},
+					callback: function(r) {
+						// TODO: calculate
+					}
+				});
+			}
 		}
 	},
 	
diff --git a/buying/utils.py b/buying/utils.py
index 0431e64..54197b4 100644
--- a/buying/utils.py
+++ b/buying/utils.py
@@ -16,6 +16,7 @@
 
 from __future__ import unicode_literals
 import webnotes
+from webnotes import msgprint, _
 from webnotes.utils import getdate, flt, add_days
 import json
 
@@ -29,7 +30,11 @@
 			"warehouse": None,
 			"supplier": None,
 			"transaction_date": None,
-			"conversion_rate": 1.0
+			"conversion_rate": 1.0,
+			"price_list_name": None,
+			"price_list_currency": None,
+			"plc_conversion_rate": 1.0,
+			"is_subcontracted": "Yes" / "No"
 		}
 	"""
 	if isinstance(args, basestring):
@@ -37,36 +42,14 @@
 		
 	args = webnotes._dict(args)
 	
-	item_wrapper = webnotes.bean("Item", args.item_code)
-	item = item_wrapper.doc
+	item_bean = webnotes.bean("Item", args.item_code)
+	item = item_bean.doc
 	
-	from stock.utils import validate_end_of_life
-	validate_end_of_life(item.name, item.end_of_life)
+	_validate_item_details(args, item)
 	
-	# fetch basic values
-	out = webnotes._dict()
-	out.update({
-		"item_name": item.item_name,
-		"item_group": item.item_group,
-		"brand": item.brand,
-		"description": item.description,
-		"qty": 0,
-		"stock_uom": item.stock_uom,
-		"uom": item.stock_uom,
-		"conversion_factor": 1.0,
-		"warehouse": args.warehouse or item.default_warehouse,
-		"item_tax_rate": json.dumps(dict(([d.tax_type, d.tax_rate] for d in 
-			item_wrapper.doclist.get({"parentfield": "item_tax"})))),
-		"batch_no": None,
-		"expense_head": item.purchase_account,
-		"cost_center": item.cost_center
-	})
+	out = _get_basic_details(args, item_bean)
 	
-	if args.supplier:
-		item_supplier = item_wrapper.doclist.get({"parentfield": "item_supplier_details",
-			"supplier": args.supplier})
-		if item_supplier:
-			out["supplier_part_no"] = item_supplier[0].supplier_part_no
+	out.supplier_part_no = _get_supplier_part_no(args, item_bean)
 	
 	if out.warehouse:
 		out.projected_qty = webnotes.conn.get_value("Bin", {"item_code": item.name, 
@@ -84,7 +67,7 @@
 			"Supplier Quotation"]:
 		# try fetching from price list
 		if args.price_list_name and args.price_list_currency:
-			rates_as_per_price_list = get_rates_as_per_price_list(args, item_wrapper.doclist)
+			rates_as_per_price_list = get_rates_as_per_price_list(args, item_bean.doclist)
 			if rates_as_per_price_list:
 				out.update(rates_as_per_price_list)
 		
@@ -95,6 +78,33 @@
 				out.update(last_purchase)
 			
 	return out
+	
+def _get_basic_details(args, item_bean):
+	item = item_bean.doc
+	
+	out = webnotes._dict({
+		"description": item.description_html or item.description,
+		"qty": 0.0,
+		"uom": item.stock_uom,
+		"conversion_factor": 1.0,
+		"warehouse": args.warehouse or item.default_warehouse,
+		"item_tax_rate": json.dumps(dict(([d.tax_type, d.tax_rate] for d in 
+			item_bean.doclist.get({"parentfield": "item_tax"})))),
+		"batch_no": None,
+		"expense_head": item.purchase_account,
+		"cost_center": item.cost_center
+	})
+	
+	for fieldname in ("item_name", "item_group", "brand", "stock_uom"):
+		out[fieldname] = item.fields.get(fieldname)
+	
+	return out
+	
+def _get_supplier_part_no(args, item_bean):
+	item_supplier = item_bean.doclist.get({"parentfield": "item_supplier_details",
+		"supplier": args.supplier})
+	
+	return item_supplier and item_supplier[0].supplier_part_no or None
 
 def get_rates_as_per_price_list(args, item_doclist=None):
 	if not item_doclist:
@@ -117,6 +127,21 @@
 		})
 	else:
 		return webnotes._dict()
+		
+def _validate_item_details(args, item):
+	from utilities.transaction_base import validate_item_fetch
+	validate_item_fetch(args, item)
+	
+	# validate if purchase item or subcontracted item
+	if item.is_purchase_item != "Yes":
+		msgprint(_("Item") + (" %s: " % item.name) + _("not a purchase item"),
+			raise_exception=True)
+	
+	if args.is_subcontracted == "Yes" and item.is_sub_contracted_item != "Yes":
+		msgprint(_("Item") + (" %s: " % item.name) + 
+			_("not a sub-contracted item.") +
+			_("Please select a sub-contracted item or do not sub-contract the transaction."), 
+			raise_exception=True)
 
 def get_last_purchase_details(item_code, doc_name, conversion_rate=1.0):
 	"""returns last purchase details in stock uom"""
diff --git a/controllers/buying_controller.py b/controllers/buying_controller.py
index 209bc60..1fc411f 100644
--- a/controllers/buying_controller.py
+++ b/controllers/buying_controller.py
@@ -22,6 +22,7 @@
 
 from buying.utils import get_item_details
 from setup.utils import get_company_currency
+from utilities.transaction_base import validate_conversion_rate
 
 from controllers.stock_controller import StockController
 
@@ -32,12 +33,11 @@
 		super(BuyingController, self).validate()
 		self.validate_stock_or_nonstock_items()
 		self.validate_warehouse_belongs_to_company()
+		
 		if self.meta.get_field("currency"):
 			self.company_currency = get_company_currency(self.doc.company)
-			self.validate_conversion_rate("currency", "conversion_rate")
-			
-			if self.doc.price_list_name and self.doc.price_list_currency:
-				self.validate_conversion_rate("price_list_currency", "plc_conversion_rate")
+			validate_conversion_rate(self.doc.currency, self.doc.conversion_rate,
+				self.meta.get_label("conversion_rate"), self.doc.company)
 			
 			# IMPORTANT: enable this only when client side code is similar to this one
 			# self.calculate_taxes_and_totals()
@@ -88,28 +88,6 @@
 				if not item.fields.get(r):
 					item.fields[r] = ret[r]
 	
-	def validate_conversion_rate(self, currency_field, conversion_rate_field):
-		"""common validation for currency and price list currency"""
-		
-		currency = self.doc.fields.get(currency_field)
-		conversion_rate = flt(self.doc.fields.get(conversion_rate_field))
-		conversion_rate_label = self.meta.get_label(conversion_rate_field)
-		
-		if conversion_rate == 0:
-			msgprint(conversion_rate_label + _(' cannot be 0'), raise_exception=True)
-		
-		# parenthesis for 'OR' are necessary as we want it to evaluate as 
-		# mandatory valid condition and (1st optional valid condition 
-		# 	or 2nd optional valid condition)
-		valid_conversion_rate = (conversion_rate and 
-			((currency == self.company_currency and conversion_rate == 1.00)
-				or (currency != self.company_currency and conversion_rate != 1.00)))
-
-		if not valid_conversion_rate:
-			msgprint(_('Please enter valid ') + conversion_rate_label + (': ') 
-				+ ("1 %s = [?] %s" % (currency, self.company_currency)),
-				raise_exception=True)
-
 	def set_total_in_words(self):
 		from webnotes.utils import money_in_words
 		company_currency = get_company_currency(self.doc.company)
diff --git a/controllers/selling_controller.py b/controllers/selling_controller.py
index 36d9d8e..63b87e1 100644
--- a/controllers/selling_controller.py
+++ b/controllers/selling_controller.py
@@ -353,3 +353,10 @@
 					del tax.fields[fieldname]
 			
 			tax.item_wise_tax_detail = json.dumps(tax.item_wise_tax_detail)
+			
+	def validate_order_type(self):
+		valid_types = ["Sales", "Maintenance"]
+		if self.doc.order_type not in valid_types:
+			msgprint(_(self.meta.get_label("order_type")) + " " + 
+				_("must be one of") + ": " + comma_or(valid_types),
+				raise_exception=True)
diff --git a/selling/doctype/quotation/quotation.py b/selling/doctype/quotation/quotation.py
index c154a6a..7e83131 100644
--- a/selling/doctype/quotation/quotation.py
+++ b/selling/doctype/quotation/quotation.py
@@ -142,6 +142,8 @@
 	#do not allow sales item in maintenance quotation and service item in sales quotation
 	#-----------------------------------------------------------------------------------------------
 	def validate_order_type(self):
+		super(DocType, self).validate_order_type()
+		
 		if self.doc.order_type in ['Maintenance', 'Service']:
 			for d in getlist(self.doclist, 'quotation_details'):
 				is_service_item = sql("select is_service_item from `tabItem` where name=%s", d.item_code)
diff --git a/selling/doctype/sales_common/sales_common.js b/selling/doctype/sales_common/sales_common.js
index 1d020e6..67c7539 100644
--- a/selling/doctype/sales_common/sales_common.js
+++ b/selling/doctype/sales_common/sales_common.js
@@ -21,6 +21,83 @@
 // cur_frm.cscript.other_fname - wn.require('app/accounts/doctype/sales_taxes_and_charges_master/sales_taxes_and_charges_master.js'); fieldname
 // cur_frm.cscript.sales_team_fname - Sales Team fieldname
 
+wn.provide("erpnext.selling");
+
+erpnext.selling.SellingController = wn.ui.form.Controller.extend({
+	setup: function() {
+		
+	},
+	
+	refresh: function() {
+		
+	},
+	
+	item_code: function(doc, cdt, cdn) {
+		var me = this;
+		var item = wn.model.get_doc(cdt, cdn);
+		if(item.item_code) {
+			var fetch = true;
+			$.each(["company", "customer"], function(i, fieldname) {
+				if(!me.frm.doc[fieldname]) {
+					fetch = false;
+					msgprint(wn._("Please specify") + ": " + 
+						wn.meta.get_label(me.frm.doc.doctype, fieldname, me.frm.doc.name) + 
+						". " + wn._("It is needed to fetch Item Details."));
+				}
+			});
+			
+			if(!fetch) {
+				item.item_code = null;
+				refresh_field("item_code", item.name, item.parentfield);
+			} else {
+				this.frm.call({
+					method: "selling.utils.get_item_details",
+					child: item,
+					args: {
+						args: {
+							item_code: item.item_code,
+							warehouse: item.warehouse,
+							doctype: me.frm.doc.doctype,
+							customer: me.frm.doc.customer,
+							currency: me.frm.doc.currency,
+							conversion_rate: me.frm.doc.conversion_rate,
+							price_list_name: me.frm.doc.price_list_name,
+							price_list_currency: me.frm.doc.price_list_currency,
+							plc_conversion_rate: me.frm.doc.plc_conversion_rate,
+							company: me.frm.doc.company,
+							order_type: me.frm.doc.order_type
+							
+						}
+					},
+					callback: function(r) {
+						// TODO: calculate
+					}
+				});
+			}
+		}
+	},
+	
+	update_item_details: function() {
+		
+	},
+	
+	set_dynamic_labels: function() {
+		
+	},
+	
+	
+});
+
+// to save previous state of cur_frm.cscript
+var prev_cscript = {};
+$.extend(prev_cscript, cur_frm.cscript);
+
+cur_frm.cscript = new erpnext.selling.SellingController({frm: cur_frm});
+
+// for backward compatibility: combine new and previous states
+$.extend(cur_frm.cscript, prev_cscript);
+
+
 // ============== Load Default Taxes ===================
 cur_frm.cscript.load_taxes = function(doc, cdt, cdn, callback) {
 	// run if this is not executed from dt_map...
@@ -264,7 +341,7 @@
 
 // ******************** ITEM CODE ******************************** 
 cur_frm.fields_dict[cur_frm.cscript.fname].grid.get_field("item_code").get_query = function(doc, cdt, cdn) {
-	if (inList(['Maintenance', 'Service'], doc.order_type)) {
+	if (doc.order_type == "Maintenance") {
 	 	return erpnext.queries.item({
 			'ifnull(tabItem.is_service_item, "No")': 'Yes'
 		});
@@ -275,34 +352,6 @@
 	}
 }
 
-
-cur_frm.cscript.item_code = function(doc, cdt, cdn) {
-	var fname = cur_frm.cscript.fname;
-	var d = locals[cdt][cdn];
-	if (d.item_code) {
-		if (!doc.company) {
-			msgprint("Please select company to proceed");
-			d.item_code = '';
-			refresh_field('item_code', d.name, fname);
-		} else {
-			var callback = function(r, rt){
-				cur_frm.cscript.recalc(doc, 1);
-			}
-			var args = {
-				'item_code':d.item_code, 
-				'income_account':d.income_account, 
-				'cost_center': d.cost_center, 
-				'warehouse': d.warehouse
-			};
-			get_server_fields('get_item_details',JSON.stringify(args), 
-				fname,doc,cdt,cdn,1,callback);
-		}
-	}
-	if(cur_frm.cscript.custom_item_code){
-		cur_frm.cscript.custom_item_code(doc, cdt, cdn);
-	}
-}
-
 //Barcode
 //
 cur_frm.cscript.barcode = function(doc, cdt, cdn) {
diff --git a/selling/doctype/sales_order/sales_order.py b/selling/doctype/sales_order/sales_order.py
index 6a52e5a..c74e7e1 100644
--- a/selling/doctype/sales_order/sales_order.py
+++ b/selling/doctype/sales_order/sales_order.py
@@ -194,6 +194,8 @@
 						and current Sales Order""" % (self.doc.order_type, d.prevdoc_docname))
 
 	def validate_order_type(self):
+		super(DocType, self).validate_order_type()
+		
 		#validate delivery date
 		if self.doc.order_type == 'Sales' and not self.doc.delivery_date:
 			msgprint("Please enter 'Expected Delivery Date'")
diff --git a/selling/utils.py b/selling/utils.py
index 21e94f7..23574df 100644
--- a/selling/utils.py
+++ b/selling/utils.py
@@ -16,6 +16,9 @@
 
 from __future__ import unicode_literals
 import webnotes
+from webnotes import msgprint, _
+from webnotes.utils import flt
+import json
 
 def get_customer_list(doctype, txt, searchfield, start, page_len, filters):
 	if webnotes.conn.get_default("cust_master_name") == "Customer Name":
@@ -29,4 +32,100 @@
 		case when customer_name like %s then 0 else 1 end,
 		name, customer_name limit %s, %s""" % 
 		(", ".join(fields), searchfield, "%s", "%s", "%s", "%s", "%s", "%s"), 
-		("%%%s%%" % txt, "%%%s%%" % txt, "%%%s%%" % txt, "%%%s%%" % txt, start, page_len))
\ No newline at end of file
+		("%%%s%%" % txt, "%%%s%%" % txt, "%%%s%%" % txt, "%%%s%%" % txt, start, page_len))
+		
+@webnotes.whitelist()
+def get_item_details(args):
+	"""
+		args = {
+			"item_code": "",
+			"warehouse": None,
+			"customer": "",
+			"conversion_rate": 1.0,
+			"price_list_name": None,
+			"price_list_currency": None,
+			"plc_conversion_rate": 1.0
+		}
+	"""
+	if isinstance(args, basestring):
+		args = json.loads(args)
+	args = webnotes._dict(args)
+	
+	item_bean = webnotes.bean("Item", args.item_code)
+	
+	_validate_item_details(args, item_bean.doc)
+	
+	out = _get_basic_details(args, item_bean)
+	
+	if args.price_list_name and args.price_list_currency:
+		out.update(_get_price_list_rate(args, item_bean))
+	
+	if out.warehouse or out.reserved_warehouse:
+		out.update(_get_available_qty(args, out.warehouse or out.reserved_warehouse))
+	
+	out.customer_item_code = _get_customer_item_code(args, item_bean)
+	
+	return out
+	
+def _validate_item_details(args, item):
+	from utilities.transaction_base import validate_item_fetch
+	validate_item_fetch(args, item)
+	
+	# validate if sales item or service item
+	if args.order_type == "Maintenance":
+		if item.is_service_item != "Yes":
+			msgprint(_("Item") + (" %s: " % item.name) + 
+				_("not a service item.") +
+				_("Please select a service item or change the order type to Sales."), 
+				raise_exception=True)
+		
+	elif item.is_sales_item != "Yes":
+		msgprint(_("Item") + (" %s: " % item.name) + _("not a sales item"),
+			raise_exception=True)
+			
+def _get_basic_details(args, item_bean):
+	item = item_bean.doc
+	out = webnotes._dict({
+			"description": item.description_html or item.description,
+			"reserved_warehouse": item.default_warehouse,
+			"warehouse": item.default_warehouse or args.warehouse,
+			"income_account": item.default_income_account or args.income_account,
+			"expense_account": item.purchase_account or args.expense_account,
+			"cost_center": item.default_sales_cost_center or args.cost_center,
+			"qty": 1.0,
+			"adj_rate": 0.0,
+			"export_amount": 0.0,
+			"amount": 0.0,
+			"batch_no": None,
+			"item_tax_rate": json.dumps(dict(([d.tax_type, d.tax_rate] for d in 
+				item_bean.doclist.get({"parentfield": "item_tax"})))),
+		})
+	
+	for fieldname in ("item_name", "item_group", "barcode", "brand", "stock_uom"):
+		out[fieldname] = item.fields.get(fieldname)
+			
+	return out
+	
+def _get_price_list_rate(args, item_bean):
+	base_ref_rate = item_bean.doclist.get({
+		"parentfield": "ref_rate_details",
+		"price_list_name": args.price_list_name, 
+		"price_list_currency": args.price_list_currency,
+		"selling": 1})
+	out = webnotes._dict()
+	out.base_ref_rate = flt(base_ref_rate[0].ref_rate) if base_ref_rate else 0.0
+	out.basic_rate = out.base_ref_rate
+	out.ref_rate = out.base_ref_rate / flt(args.conversion_rate)
+	out.export_rate = out.ref_rate
+	return out
+	
+def _get_available_qty(args, warehouse):
+	return webnotes.conn.get_value("Bin", {"item_code": args.item_code, "warehouse": warehouse}, 
+		["projected_qty", "actual_qty"], as_dict=True) or {}
+		
+def _get_customer_item_code(args, item_bean):
+	customer_item_code = item_bean.doclist.get({"parentfield": "item_customer_details",
+		"customer_name": args.customer})
+	
+	return customer_item_code and customer_item_code[0].ref_code or None
+	
\ No newline at end of file
diff --git a/setup/utils.py b/setup/utils.py
index 1a86921..33fa3e2 100644
--- a/setup/utils.py
+++ b/setup/utils.py
@@ -46,4 +46,4 @@
 	if result and len(result)==1:
 		return {"price_list_currency": result[0][0]}
 	else:
-		return {}
\ No newline at end of file
+		return {}
diff --git a/utilities/transaction_base.py b/utilities/transaction_base.py
index 5d7d1a8..540b385 100644
--- a/utilities/transaction_base.py
+++ b/utilities/transaction_base.py
@@ -16,6 +16,7 @@
 
 from __future__ import unicode_literals
 import webnotes
+from webnotes import msgprint, _
 from webnotes.utils import load_json, cstr, flt, now_datetime
 from webnotes.model.doc import addchild
 
@@ -268,4 +269,42 @@
 	def validate_posting_time(self):
 		if not self.doc.posting_time:
 			self.doc.posting_time = now_datetime().strftime('%H:%M:%S')
-	
\ No newline at end of file
+	
+def validate_conversion_rate(currency, conversion_rate, conversion_rate_label, company):
+	"""common validation for currency and price list currency"""
+	if conversion_rate == 0:
+		msgprint(conversion_rate_label + _(' cannot be 0'), raise_exception=True)
+	
+	company_currency = webnotes.conn.get_value("Company", company, "default_currency")
+	
+	# parenthesis for 'OR' are necessary as we want it to evaluate as 
+	# mandatory valid condition and (1st optional valid condition 
+	# 	or 2nd optional valid condition)
+	valid_conversion_rate = (conversion_rate and 
+		((currency == company_currency and conversion_rate == 1.00)
+			or (currency != company_currency and conversion_rate != 1.00)))
+
+	if not valid_conversion_rate:
+		msgprint(_('Please enter valid ') + conversion_rate_label + (': ') 
+			+ ("1 %s = [?] %s" % (currency, company_currency)),
+			raise_exception=True)
+			
+def validate_item_fetch(args, item):
+	from stock.utils import validate_end_of_life
+	validate_end_of_life(item.name, item.end_of_life)
+	
+	# validate company
+	if not args.company:
+		msgprint(_("Please specify Company"), raise_exception=True)
+	
+	# validate conversion rates
+	meta = webnotes.get_doctype(args.doctype)
+	if meta.get_field("currency"):
+		# validate conversion rate
+		validate_conversion_rate(args.currency, args.conversion_rate, 
+			meta.get_label("conversion_rate"), args.company)
+	
+		# validate price list conversion rate
+		if args.price_list_name and args.price_list_currency:
+			validate_conversion_rate(args.price_list_currency, args.plc_conversion_rate, 
+				meta.get_label("plc_conversion_rate"), args.company)
\ No newline at end of file