Pricing Rule: first commit
diff --git a/erpnext/accounts/doctype/pricing_rule/__init__.py b/erpnext/accounts/doctype/pricing_rule/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/accounts/doctype/pricing_rule/__init__.py
diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
new file mode 100644
index 0000000..6a2a6ef
--- /dev/null
+++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
@@ -0,0 +1,36 @@
+# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
+# MIT License. See license.txt
+
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe import throw, _
+
+class DocType:
+	def __init__(self, d, dl):
+		self.doc, self.doclist = d, dl
+		
+	def validate(self):
+		self.validate_mandatory()
+		self.cleanup_fields_value()
+		
+	def validate_mandatory(self):
+		for field in ["apply_on", "applicable_for", "price_or_discount"]:
+			val = self.doc.fields.get("applicable_for")
+			if val and not self.doc.fields.get(frappe.scrub(val)):
+				throw("{fname} {msg}".format(fname = _(val), msg = _(" is mandatory")), 
+					frappe.MandatoryError)
+		
+	def cleanup_fields_value(self):
+		fields = ["item_code", "item_group", "brand", "customer", "customer_group", "territory", 
+			"supplier", "supplier_type", "campaign", "sales_partner", "price", "discount"]
+			
+		for field_with_value in ["apply_on", "applicable_for", "price_or_discount"]:
+			val = self.doc.fields.get(field_with_value)
+			if val:
+				fields.remove(frappe.scrub(val))
+			
+		for field in fields:
+			self.doc.fields[field] = None
+		
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.txt b/erpnext/accounts/doctype/pricing_rule/pricing_rule.txt
new file mode 100644
index 0000000..bf8e892
--- /dev/null
+++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.txt
@@ -0,0 +1,270 @@
+[
+ {
+  "creation": "2014-02-21 15:02:51", 
+  "docstatus": 0, 
+  "modified": "2014-02-25 13:59:13", 
+  "modified_by": "Administrator", 
+  "owner": "Administrator"
+ }, 
+ {
+  "autoname": "PRULE.#####", 
+  "doctype": "DocType", 
+  "document_type": "Master", 
+  "istable": 0, 
+  "module": "Accounts", 
+  "name": "__common__"
+ }, 
+ {
+  "doctype": "DocField", 
+  "name": "__common__", 
+  "parent": "Pricing Rule", 
+  "parentfield": "fields", 
+  "parenttype": "DocType", 
+  "permlevel": 0
+ }, 
+ {
+  "create": 1, 
+  "doctype": "DocPerm", 
+  "name": "__common__", 
+  "parent": "Pricing Rule", 
+  "parentfield": "permissions", 
+  "parenttype": "DocType", 
+  "permlevel": 0, 
+  "read": 1, 
+  "write": 1
+ }, 
+ {
+  "doctype": "DocType", 
+  "name": "Pricing Rule"
+ }, 
+ {
+  "doctype": "DocField", 
+  "fieldname": "basic_section", 
+  "fieldtype": "Section Break", 
+  "label": "Basic Section"
+ }, 
+ {
+  "default": "Item Code", 
+  "doctype": "DocField", 
+  "fieldname": "apply_on", 
+  "fieldtype": "Select", 
+  "label": "Apply On", 
+  "options": "\nItem Code\nItem Group\nBrand", 
+  "reqd": 1
+ }, 
+ {
+  "depends_on": "eval:doc.apply_on==\"Item Code\"", 
+  "doctype": "DocField", 
+  "fieldname": "item_code", 
+  "fieldtype": "Link", 
+  "label": "Item Code", 
+  "options": "Item", 
+  "reqd": 0
+ }, 
+ {
+  "depends_on": "eval:doc.apply_on==\"Item Group\"", 
+  "doctype": "DocField", 
+  "fieldname": "item_group", 
+  "fieldtype": "Link", 
+  "label": "Item Group", 
+  "options": "Item Group"
+ }, 
+ {
+  "depends_on": "eval:doc.apply_on==\"Brand\"", 
+  "doctype": "DocField", 
+  "fieldname": "brand", 
+  "fieldtype": "Link", 
+  "label": "Brand", 
+  "options": "Brand"
+ }, 
+ {
+  "doctype": "DocField", 
+  "fieldname": "priority", 
+  "fieldtype": "Select", 
+  "label": "Priority", 
+  "options": "\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20"
+ }, 
+ {
+  "doctype": "DocField", 
+  "fieldname": "col_break1", 
+  "fieldtype": "Column Break"
+ }, 
+ {
+  "default": "Today", 
+  "doctype": "DocField", 
+  "fieldname": "valid_from", 
+  "fieldtype": "Date", 
+  "label": "Valid From"
+ }, 
+ {
+  "doctype": "DocField", 
+  "fieldname": "valid_upto", 
+  "fieldtype": "Date", 
+  "label": "Valid Upto"
+ }, 
+ {
+  "doctype": "DocField", 
+  "fieldname": "disable", 
+  "fieldtype": "Check", 
+  "label": "Disable"
+ }, 
+ {
+  "doctype": "DocField", 
+  "fieldname": "price_discount_section", 
+  "fieldtype": "Section Break", 
+  "label": "Price / Discount"
+ }, 
+ {
+  "default": "Discount", 
+  "doctype": "DocField", 
+  "fieldname": "price_or_discount", 
+  "fieldtype": "Select", 
+  "label": "Price or Discount", 
+  "options": "\nPrice\nDiscount", 
+  "reqd": 1
+ }, 
+ {
+  "doctype": "DocField", 
+  "fieldname": "col_break2", 
+  "fieldtype": "Column Break"
+ }, 
+ {
+  "depends_on": "eval:doc.price_or_discount==\"Price\"", 
+  "doctype": "DocField", 
+  "fieldname": "price", 
+  "fieldtype": "Float", 
+  "label": "Price"
+ }, 
+ {
+  "depends_on": "eval:doc.price_or_discount==\"Discount\"", 
+  "doctype": "DocField", 
+  "fieldname": "discount", 
+  "fieldtype": "Float", 
+  "label": "Discount"
+ }, 
+ {
+  "depends_on": "eval:doc.price_or_discount==\"Discount\"", 
+  "doctype": "DocField", 
+  "fieldname": "for_price_list", 
+  "fieldtype": "Link", 
+  "label": "For Price List", 
+  "options": "Price List"
+ }, 
+ {
+  "doctype": "DocField", 
+  "fieldname": "applicability_section", 
+  "fieldtype": "Section Break", 
+  "label": "Applicability"
+ }, 
+ {
+  "doctype": "DocField", 
+  "fieldname": "applicable_for", 
+  "fieldtype": "Select", 
+  "label": "Applicable For", 
+  "options": "\nCustomer\nCustomer Group\nTerritory\nSales Person\nSales Partner\nCampaign\nSupplier\nSupplier Type"
+ }, 
+ {
+  "doctype": "DocField", 
+  "fieldname": "col_break3", 
+  "fieldtype": "Column Break"
+ }, 
+ {
+  "depends_on": "eval:doc.applicable_for==\"Customer\"", 
+  "doctype": "DocField", 
+  "fieldname": "customer", 
+  "fieldtype": "Link", 
+  "label": "Customer", 
+  "options": "Customer"
+ }, 
+ {
+  "depends_on": "eval:doc.applicable_for==\"Customer Group\"", 
+  "doctype": "DocField", 
+  "fieldname": "customer_group", 
+  "fieldtype": "Link", 
+  "label": "Customer Group", 
+  "options": "Customer Group"
+ }, 
+ {
+  "depends_on": "eval:doc.applicable_for==\"Territory\"", 
+  "doctype": "DocField", 
+  "fieldname": "territory", 
+  "fieldtype": "Link", 
+  "label": "Territory", 
+  "options": "Territory"
+ }, 
+ {
+  "depends_on": "eval:doc.applicable_for==\"Sales Partner\"", 
+  "doctype": "DocField", 
+  "fieldname": "sales_partner", 
+  "fieldtype": "Link", 
+  "label": "Sales Partner", 
+  "options": "Sales Partner"
+ }, 
+ {
+  "depends_on": "eval:doc.applicable_for==\"Campaign\"", 
+  "doctype": "DocField", 
+  "fieldname": "campaign", 
+  "fieldtype": "Link", 
+  "label": "Campaign", 
+  "options": "Campaign"
+ }, 
+ {
+  "depends_on": "eval:doc.applicable_for==\"Supplier\"", 
+  "doctype": "DocField", 
+  "fieldname": "supplier", 
+  "fieldtype": "Link", 
+  "label": "Supplier", 
+  "options": "Supplier"
+ }, 
+ {
+  "depends_on": "eval:doc.applicable_for==\"Supplier Type\"", 
+  "doctype": "DocField", 
+  "fieldname": "supplier_type", 
+  "fieldtype": "Link", 
+  "label": "Supplier Type", 
+  "options": "Supplier Type"
+ }, 
+ {
+  "doctype": "DocField", 
+  "fieldname": "qty_section", 
+  "fieldtype": "Section Break", 
+  "label": "Based on Qty"
+ }, 
+ {
+  "doctype": "DocField", 
+  "fieldname": "min_qty", 
+  "fieldtype": "Float", 
+  "label": "Min Qty"
+ }, 
+ {
+  "doctype": "DocField", 
+  "fieldname": "col_break4", 
+  "fieldtype": "Column Break"
+ }, 
+ {
+  "doctype": "DocField", 
+  "fieldname": "max_qty", 
+  "fieldtype": "Float", 
+  "label": "Max Qty"
+ }, 
+ {
+  "doctype": "DocPerm", 
+  "role": "Accounts Manager"
+ }, 
+ {
+  "doctype": "DocPerm", 
+  "role": "Sales Manager"
+ }, 
+ {
+  "doctype": "DocPerm", 
+  "role": "Purchase Manager"
+ }, 
+ {
+  "doctype": "DocPerm", 
+  "role": "Website Manager"
+ }, 
+ {
+  "doctype": "DocPerm", 
+  "role": "System Manager"
+ }
+]
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py
new file mode 100644
index 0000000..d09902c
--- /dev/null
+++ b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py
@@ -0,0 +1,68 @@
+# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+
+
+from __future__ import unicode_literals
+import unittest
+import frappe
+
+class TestPricingRule(unittest.TestCase):
+	def test_pricing_rule_for_discount(self):
+		from erpnext.stock.get_item_details import get_item_details
+		from frappe import MandatoryError
+		
+		args = frappe._dict({
+			"item_code": "_Test Item",
+			"company": "_Test Company",
+			"price_list": "_Test Price List",
+			"currency": "_Test Currency",
+			"doctype": "Sales Order",
+			"conversion_rate": 1,
+			"price_list_currency": "_Test Currency",
+			"plc_conversion_rate": 1,
+			"order_type": "Sales",
+			"transaction_type": "selling",
+			"customer": "_Test Customer",
+		})
+		
+		details = get_item_details(args)
+		self.assertEquals(details.get("discount_percentage"), 10)
+		
+		prule = frappe.bean(copy=test_records[0])
+		prule.doc.apply_on = "Item Group"
+		prule.doc.item_group = "_Test Item Group"
+		prule.doc.discount = 15
+		prule.insert()
+		
+		details = get_item_details(args)
+		self.assertEquals(details.get("discount_percentage"), 10)
+		
+		prule = frappe.bean(copy=test_records[0])
+		prule.doc.applicable_for = "Customer"
+		self.assertRaises(MandatoryError, prule.insert)
+		prule.doc.customer = "_Test Customer"
+		prule.doc.discount = 20
+		prule.insert()
+		details = get_item_details(args)
+		self.assertEquals(details.get("discount_percentage"), 20)
+
+		args.item_code = "_Test Item 2"
+		details = get_item_details(args)
+		self.assertEquals(details.get("discount_percentage"), 15)
+		
+		args.customer = None
+		details = get_item_details(args)
+		self.assertEquals(details.get("discount_percentage"), 15)
+		
+
+test_records = [
+	[{
+		"doctype": "Pricing Rule", 
+		"apply_on": "Item Code", 
+		"item_code": "_Test Item", 
+		"price_or_discount": "Discount", 
+		"price": 0, 
+		"discount": 10, 
+	}],
+
+]
\ No newline at end of file
diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py
index f990ec9..25a0c71 100644
--- a/erpnext/stock/doctype/item/test_item.py
+++ b/erpnext/stock/doctype/item/test_item.py
@@ -18,7 +18,7 @@
 		item.doc.default_warehouse = None
 		self.assertRaises(WarehouseNotSet, item.insert)
 		
-	def atest_get_item_details(self):
+	def test_get_item_details(self):
 		from erpnext.stock.get_item_details import get_item_details
 		to_check = {
 			"item_code": "_Test Item",
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index dfe4cc3..79613c3 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -18,7 +18,7 @@
 			"conversion_rate": 1.0,
 			"selling_price_list": None,
 			"price_list_currency": None,
-			"plc_conversion_rate": 1.0
+			"plc_conversion_rate": 1.0,
 			"doctype": "",
 			"docname": "",
 			"supplier": None,
@@ -60,11 +60,11 @@
 		out.update(get_projected_qty(item.name, out.warehouse))
 	
 	get_price_list_rate(args, item_bean, out)
-	
-	out.update(get_item_discount(out.item_group, args.customer))
 
 	if args.transaction_type == "selling" and cint(args.is_pos):
 		out.update(get_pos_settings_item_details(args.company, args))
+	
+	apply_pricing_rule(out, args)
 		
 	if args.get("doctype") in ("Sales Invoice", "Delivery Note"):
 		if item_bean.doc.has_serial_no == "Yes" and not args.serial_no:
@@ -262,6 +262,80 @@
 			
 	return pos_settings and pos_settings[0] or None
 	
+def apply_pricing_rule(out, args):
+	args_dict = frappe._dict().update(args)
+	args_dict.update(out)
+	
+	for rule_for in ["price", "discount"]:
+		pricing_rules = get_pricing_rules(args_dict, rule_for)
+		if pricing_rules:
+			if rule_for == "discount":
+				out["discount_percentage"] = pricing_rules[-1]["discount"]
+			else:
+				out["base_price_list_rate"] = pricing_rules[0]["price"]
+				out["price_list_rate"] = pricing_rules[0]["price"] * \
+					flt(args_dict.plc_conversion_rate) / flt(args_dict.conversion_rate)
+	
+	
+def get_pricing_rules(args_dict, price_or_discount):	
+	def _filter_pricing_rule(pricing_rules, field_set):
+		p_rules = []
+		for field in field_set:
+			if not p_rules:
+				for p_rule in pricing_rules:
+					if p_rule[field] == args_dict.get(field):
+						p_rules.append(p_rule)
+			else:
+				break
+		
+		return p_rules or pricing_rules
+		
+	pricing_rules = get_all_pricing_rules(args_dict, price_or_discount)
+
+	for field_set in [["item_code", "item_group", "brand"], ["customer", "customer_group", 
+		"territory", "supplier", "supplier_type", "campaign", "sales_partner"]]:
+			if pricing_rules:
+				pricing_rules = _filter_pricing_rule(pricing_rules, field_set)
+
+	# filter for price list
+	if pricing_rules:
+		pricing_rules = filter(lambda x: (not x.for_price_list or 
+			x.for_price_list==args_dict.price_list), pricing_rules)
+			
+	# filter for qty
+	if pricing_rules and args_dict.get("qty"):
+		pricing_rules = filter(lambda x: (args_dict.qty>=flt(x.min_qty) 
+			and (args_dict.qty<=x.max_qty if x.max_qty else True)), pricing_rules)
+			
+	# find pricing rule with highest priority
+	if pricing_rules:
+		max_priority = min([cint(p.priority) for p in pricing_rules])
+		if max_priority:
+			pricing_rules = filter(lambda x: x.priority==max_priority, pricing_rules)
+			
+	if len(pricing_rules) > 1:
+		pricing_rules = sorted(pricing_rules, key=lambda x: x[price_or_discount])
+
+	return pricing_rules
+	
+def get_all_pricing_rules(args_dict, price_or_discount):	
+	conditions = " and ifnull(%s, 0) > 0" % price_or_discount
+	
+	for field in ["customer", "customer_group", "territory", "supplier", "supplier_type", 
+		"campaign", "sales_partner"]:
+			if args_dict.get(field):
+				conditions += " and ifnull("+field+", '') in (%("+field+")s, '')"
+				
+	if args_dict.get("transaction_date"):
+		conditions += """ and %(transaction_date)s between ifnull(valid_from, '2000-01-01') 
+			and ifnull(valid_upto, '2500-12-31')"""
+	
+	return frappe.conn.sql("""select * from `tabPricing Rule` 
+		where (item_code=%(item_code)s or item_group=%(item_group)s or brand=%(brand)s) 
+			and docstatus < 2 and ifnull(disable, 0) = 0 {0}
+		order by priority desc, name desc""".format(conditions), args_dict, as_dict=1)
+		
+	
 def get_serial_nos_by_fifo(args, item_bean):
 	return "\n".join(frappe.db.sql_list("""select name from `tabSerial No` 
 		where item_code=%(item_code)s and warehouse=%(warehouse)s and status='Available'