From 444f956e7b6f0d085ce09a1048164065ad55e6c2 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Fri, 20 Jun 2014 15:59:49 +0530 Subject: [PATCH 1/5] Pricing Rule fixes and improvements. Fixes #1795 --- .../doctype/pricing_rule/pricing_rule.js | 27 ++ .../doctype/pricing_rule/pricing_rule.json | 440 +++++++++--------- .../doctype/pricing_rule/pricing_rule.py | 196 +++++++- .../doctype/pricing_rule/test_pricing_rule.py | 7 +- .../purchase_invoice/purchase_invoice.json | 16 +- .../doctype/sales_invoice/sales_invoice.json | 16 +- .../purchase_order/purchase_order.json | 16 +- .../doctype/purchase_order/purchase_order.py | 13 +- .../supplier_quotation.json | 16 +- .../supplier_quotation/supplier_quotation.py | 1 + erpnext/controllers/accounts_controller.py | 6 +- erpnext/patches.txt | 1 + .../set_pricing_rule_for_buying_or_selling.py | 12 + erpnext/public/js/transaction.js | 117 +++-- .../selling/doctype/quotation/quotation.json | 16 +- .../selling/doctype/quotation/quotation.py | 2 +- .../doctype/sales_order/sales_order.json | 16 +- .../doctype/sales_order/sales_order.py | 9 +- .../doctype/delivery_note/delivery_note.json | 16 +- .../doctype/delivery_note/delivery_note.py | 1 + erpnext/stock/doctype/item/test_item.py | 12 +- .../purchase_receipt/purchase_receipt.json | 16 +- .../purchase_receipt/purchase_receipt.py | 1 + erpnext/stock/get_item_details.py | 161 +------ 24 files changed, 695 insertions(+), 439 deletions(-) create mode 100644 erpnext/patches/v4_0/set_pricing_rule_for_buying_or_selling.py diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.js b/erpnext/accounts/doctype/pricing_rule/pricing_rule.js index 356cc0de9c..a1859e5d57 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.js +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.js @@ -61,4 +61,31 @@ frappe.ui.form.on("Pricing Rule", "refresh", function(frm) { ''].join("\n"); set_field_options("pricing_rule_help", help_content); + + cur_frm.cscript.set_options_for_applicable_for(); }); + +cur_frm.cscript.set_options_for_applicable_for = function() { + var options = [""]; + var applicable_for = cur_frm.doc.applicable_for; + + if(cur_frm.doc.selling) { + options = $.merge(options, ["Customer", "Customer Group", "Territory", "Sales Partner", "Campaign"]); + } + if(cur_frm.doc.buying) { + $.merge(options, ["Supplier", "Supplier Type"]); + } + + set_field_options("applicable_for", options.join("\n")); + + if(!in_list(options, applicable_for)) applicable_for = null; + cur_frm.set_value("applicable_for", applicable_for) +} + +cur_frm.cscript.selling = function() { + cur_frm.cscript.set_options_for_applicable_for(); +} + +cur_frm.cscript.buying = function() { + cur_frm.cscript.set_options_for_applicable_for(); +} diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json index b20563fa76..e15cea83b8 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json @@ -1,288 +1,300 @@ { - "allow_import": 1, - "autoname": "PRULE.#####", - "creation": "2014-02-21 15:02:51", - "docstatus": 0, - "doctype": "DocType", - "document_type": "Master", + "allow_import": 1, + "autoname": "PRULE.#####", + "creation": "2014-02-21 15:02:51", + "docstatus": 0, + "doctype": "DocType", + "document_type": "Master", "fields": [ { - "fieldname": "applicability_section", - "fieldtype": "Section Break", - "in_list_view": 0, - "label": "Applicability", + "fieldname": "applicability_section", + "fieldtype": "Section Break", + "in_list_view": 0, + "label": "Applicability", "permlevel": 0 - }, + }, { - "default": "Item Code", - "fieldname": "apply_on", - "fieldtype": "Select", - "in_list_view": 1, - "label": "Apply On", - "options": "\nItem Code\nItem Group\nBrand", - "permlevel": 0, + "default": "Item Code", + "fieldname": "apply_on", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Apply On", + "options": "\nItem Code\nItem Group\nBrand", + "permlevel": 0, "reqd": 1 - }, + }, { - "depends_on": "eval:doc.apply_on==\"Item Code\"", - "fieldname": "item_code", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Item Code", - "options": "Item", - "permlevel": 0, + "depends_on": "eval:doc.apply_on==\"Item Code\"", + "fieldname": "item_code", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Item Code", + "options": "Item", + "permlevel": 0, "reqd": 0 - }, + }, { - "depends_on": "eval:doc.apply_on==\"Item Group\"", - "fieldname": "item_group", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Item Group", - "options": "Item Group", + "depends_on": "eval:doc.apply_on==\"Item Group\"", + "fieldname": "item_group", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Item Group", + "options": "Item Group", "permlevel": 0 - }, + }, { - "depends_on": "eval:doc.apply_on==\"Brand\"", - "fieldname": "brand", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Brand", - "options": "Brand", + "depends_on": "eval:doc.apply_on==\"Brand\"", + "fieldname": "brand", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Brand", + "options": "Brand", "permlevel": 0 - }, + }, { - "fieldname": "applicable_for", - "fieldtype": "Select", - "label": "Applicable For", - "options": "\nCustomer\nCustomer Group\nTerritory\nSales Partner\nCampaign\nSupplier\nSupplier Type", + "fieldname": "selling", + "fieldtype": "Check", + "label": "Selling", "permlevel": 0 - }, + }, { - "depends_on": "eval:doc.applicable_for==\"Customer\"", - "fieldname": "customer", - "fieldtype": "Link", - "label": "Customer", - "options": "Customer", + "fieldname": "buying", + "fieldtype": "Check", + "label": "Buying", "permlevel": 0 - }, + }, { - "depends_on": "eval:doc.applicable_for==\"Customer Group\"", - "fieldname": "customer_group", - "fieldtype": "Link", - "label": "Customer Group", - "options": "Customer Group", + "fieldname": "applicable_for", + "fieldtype": "Select", + "label": "Applicable For", + "options": "\nCustomer\nCustomer Group\nTerritory\nSales Partner\nCampaign\nSupplier\nSupplier Type", "permlevel": 0 - }, + }, { - "depends_on": "eval:doc.applicable_for==\"Territory\"", - "fieldname": "territory", - "fieldtype": "Link", - "label": "Territory", - "options": "Territory", + "depends_on": "eval:doc.applicable_for==\"Customer\"", + "fieldname": "customer", + "fieldtype": "Link", + "label": "Customer", + "options": "Customer", "permlevel": 0 - }, + }, { - "depends_on": "eval:doc.applicable_for==\"Sales Partner\"", - "fieldname": "sales_partner", - "fieldtype": "Link", - "label": "Sales Partner", - "options": "Sales Partner", + "depends_on": "eval:doc.applicable_for==\"Customer Group\"", + "fieldname": "customer_group", + "fieldtype": "Link", + "label": "Customer Group", + "options": "Customer Group", "permlevel": 0 - }, + }, { - "depends_on": "eval:doc.applicable_for==\"Campaign\"", - "fieldname": "campaign", - "fieldtype": "Link", - "label": "Campaign", - "options": "Campaign", + "depends_on": "eval:doc.applicable_for==\"Territory\"", + "fieldname": "territory", + "fieldtype": "Link", + "label": "Territory", + "options": "Territory", "permlevel": 0 - }, + }, { - "depends_on": "eval:doc.applicable_for==\"Supplier\"", - "fieldname": "supplier", - "fieldtype": "Link", - "label": "Supplier", - "options": "Supplier", + "depends_on": "eval:doc.applicable_for==\"Sales Partner\"", + "fieldname": "sales_partner", + "fieldtype": "Link", + "label": "Sales Partner", + "options": "Sales Partner", "permlevel": 0 - }, + }, { - "depends_on": "eval:doc.applicable_for==\"Supplier Type\"", - "fieldname": "supplier_type", - "fieldtype": "Link", - "label": "Supplier Type", - "options": "Supplier Type", + "depends_on": "eval:doc.applicable_for==\"Campaign\"", + "fieldname": "campaign", + "fieldtype": "Link", + "label": "Campaign", + "options": "Campaign", "permlevel": 0 - }, + }, { - "fieldname": "min_qty", - "fieldtype": "Float", - "label": "Min Qty", + "depends_on": "eval:doc.applicable_for==\"Supplier\"", + "fieldname": "supplier", + "fieldtype": "Link", + "label": "Supplier", + "options": "Supplier", "permlevel": 0 - }, + }, { - "fieldname": "max_qty", - "fieldtype": "Float", - "label": "Max Qty", + "depends_on": "eval:doc.applicable_for==\"Supplier Type\"", + "fieldname": "supplier_type", + "fieldtype": "Link", + "label": "Supplier Type", + "options": "Supplier Type", "permlevel": 0 - }, + }, { - "fieldname": "col_break1", - "fieldtype": "Column Break", + "fieldname": "max_qty", + "fieldtype": "Float", + "label": "Max Qty", "permlevel": 0 - }, + }, { - "fieldname": "company", - "fieldtype": "Link", - "label": "Company", - "options": "Company", + "fieldname": "min_qty", + "fieldtype": "Float", + "label": "Min Qty", "permlevel": 0 - }, + }, { - "default": "Today", - "fieldname": "valid_from", - "fieldtype": "Date", - "label": "Valid From", + "fieldname": "col_break1", + "fieldtype": "Column Break", "permlevel": 0 - }, + }, { - "fieldname": "valid_upto", - "fieldtype": "Date", - "label": "Valid Upto", + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company", "permlevel": 0 - }, + }, { - "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", + "default": "Today", + "fieldname": "valid_from", + "fieldtype": "Date", + "label": "Valid From", "permlevel": 0 - }, + }, { - "fieldname": "disable", - "fieldtype": "Check", - "label": "Disable", + "fieldname": "valid_upto", + "fieldtype": "Date", + "label": "Valid Upto", "permlevel": 0 - }, + }, { - "fieldname": "price_discount_section", - "fieldtype": "Section Break", - "label": "Price / Discount", + "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", "permlevel": 0 - }, + }, { - "default": "Discount Percentage", - "fieldname": "price_or_discount", - "fieldtype": "Select", - "label": "Price or Discount", - "options": "\nPrice\nDiscount Percentage", - "permlevel": 0, + "fieldname": "disable", + "fieldtype": "Check", + "label": "Disable", + "permlevel": 0 + }, + { + "fieldname": "price_discount_section", + "fieldtype": "Section Break", + "label": "Price / Discount", + "permlevel": 0 + }, + { + "default": "Discount Percentage", + "fieldname": "price_or_discount", + "fieldtype": "Select", + "label": "Price or Discount", + "options": "\nPrice\nDiscount Percentage", + "permlevel": 0, "reqd": 1 - }, + }, { - "fieldname": "col_break2", - "fieldtype": "Column Break", + "fieldname": "col_break2", + "fieldtype": "Column Break", "permlevel": 0 - }, + }, { - "depends_on": "eval:doc.price_or_discount==\"Price\"", - "fieldname": "price", - "fieldtype": "Float", - "label": "Price", + "depends_on": "eval:doc.price_or_discount==\"Price\"", + "fieldname": "price", + "fieldtype": "Float", + "label": "Price", "permlevel": 0 - }, + }, { - "depends_on": "eval:doc.price_or_discount==\"Discount Percentage\"", - "fieldname": "discount_percentage", - "fieldtype": "Float", - "label": "Discount Percentage", + "depends_on": "eval:doc.price_or_discount==\"Discount Percentage\"", + "fieldname": "discount_percentage", + "fieldtype": "Float", + "label": "Discount Percentage", "permlevel": 0 - }, + }, { - "depends_on": "eval:doc.price_or_discount==\"Discount Percentage\"", - "fieldname": "for_price_list", - "fieldtype": "Link", - "label": "For Price List", - "options": "Price List", + "depends_on": "eval:doc.price_or_discount==\"Discount Percentage\"", + "fieldname": "for_price_list", + "fieldtype": "Link", + "label": "For Price List", + "options": "Price List", "permlevel": 0 - }, + }, { - "fieldname": "help_section", - "fieldtype": "Section Break", - "label": "", - "options": "Simple", + "fieldname": "help_section", + "fieldtype": "Section Break", + "label": "", + "options": "Simple", "permlevel": 0 - }, + }, { - "fieldname": "pricing_rule_help", - "fieldtype": "HTML", - "label": "Pricing Rule Help", + "fieldname": "pricing_rule_help", + "fieldtype": "HTML", + "label": "Pricing Rule Help", "permlevel": 0 } - ], - "icon": "icon-gift", - "idx": 1, - "istable": 0, - "modified": "2014-05-28 15:36:29.403659", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Pricing Rule", - "owner": "Administrator", + ], + "icon": "icon-gift", + "idx": 1, + "istable": 0, + "modified": "2014-06-19 15:00:09.962572", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Pricing Rule", + "owner": "Administrator", "permissions": [ { - "create": 1, - "delete": 1, - "export": 0, - "import": 0, - "permlevel": 0, - "read": 1, - "report": 1, - "role": "Accounts Manager", + "create": 1, + "delete": 1, + "export": 0, + "import": 0, + "permlevel": 0, + "read": 1, + "report": 1, + "role": "Accounts Manager", "write": 1 - }, + }, { - "create": 1, - "delete": 1, - "export": 0, - "import": 0, - "permlevel": 0, - "print": 0, - "read": 1, - "report": 1, - "role": "Sales Manager", + "create": 1, + "delete": 1, + "export": 0, + "import": 0, + "permlevel": 0, + "print": 0, + "read": 1, + "report": 1, + "role": "Sales Manager", "write": 1 - }, + }, { - "create": 1, - "delete": 1, - "permlevel": 0, - "read": 1, - "report": 1, - "role": "Purchase Manager", + "create": 1, + "delete": 1, + "permlevel": 0, + "read": 1, + "report": 1, + "role": "Purchase Manager", "write": 1 - }, + }, { - "create": 1, - "delete": 1, - "permlevel": 0, - "read": 1, - "report": 1, - "role": "Website Manager", + "create": 1, + "delete": 1, + "permlevel": 0, + "read": 1, + "report": 1, + "role": "Website Manager", "write": 1 - }, + }, { - "create": 1, - "delete": 1, - "export": 1, - "import": 1, - "permlevel": 0, - "read": 1, - "report": 1, - "set_user_permissions": 1, - "role": "System Manager", + "create": 1, + "delete": 1, + "export": 1, + "import": 1, + "permlevel": 0, + "read": 1, + "report": 1, + "restrict": 1, + "role": "System Manager", "write": 1 } - ], - "sort_field": "modified", + ], + "sort_field": "modified", "sort_order": "DESC" -} +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py index a15b45a381..5cf500a597 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py @@ -5,13 +5,17 @@ from __future__ import unicode_literals import frappe +import json from frappe import throw, _ -from frappe.utils import flt +from frappe.utils import flt, cint from frappe.model.document import Document +class MultiplePricingRuleConflict(frappe.ValidationError): pass + class PricingRule(Document): def validate(self): self.validate_mandatory() + self.validate_applicable_for_selling_or_buying() self.validate_min_max_qty() self.cleanup_fields_value() self.validate_price_or_discount() @@ -22,6 +26,18 @@ class PricingRule(Document): if tocheck and not self.get(tocheck): throw(_("{0} is required").format(self.meta.get_label(tocheck)), frappe.MandatoryError) + def validate_applicable_for_selling_or_buying(self): + if not self.selling and not self.buying: + throw(_("Atleast one of the Selling or Buying must be selected")) + + if not self.selling and self.applicable_for in ["Customer", "Customer Group", + "Territory", "Sales Partner", "Campaign"]: + throw(_("Selling must be checked, if Applicable For is selected as {0}" + .format(self.applicable_for))) + + if not self.buying and self.applicable_for in ["Supplier", "Supplier Type"]: + throw(_("Buying must be checked, if Applicable For is selected as {0}" + .format(self.applicable_for))) def validate_min_max_qty(self): if self.min_qty and self.max_qty and flt(self.min_qty) > flt(self.max_qty): @@ -44,3 +60,181 @@ class PricingRule(Document): for field in ["Price", "Discount Percentage"]: if flt(self.get(frappe.scrub(field))) < 0: throw(_("{0} can not be negative").format(field)) + +#-------------------------------------------------------------------------------- + +@frappe.whitelist() +def apply_pricing_rule(args): + """ + args = { + "item_list": [{"doctype": "", "name": "", "item_code": "", "brand": "", "item_group": ""}, ...], + "customer": "something", + "customer_group": "something", + "territory": "something", + "supplier": "something", + "supplier_type": "something", + "currency": "something", + "conversion_rate": "something", + "price_list": "something", + "plc_conversion_rate": "something", + "company": "something", + "transaction_date": "something", + "campaign": "something", + "sales_partner": "something", + "ignore_pricing_rule": "something" + } + """ + if isinstance(args, basestring): + args = json.loads(args) + + args = frappe._dict(args) + + # list of dictionaries + out = [] + + if args.get("parenttype") == "Material Request": return out + + if not args.transaction_type: + args.transaction_type = "buying" if frappe.get_meta(args.parenttype).get_field("supplier") \ + else "selling" + + for item in args.get("item_list"): + args_copy = args.copy() + args_copy.update(item) + out.append(get_pricing_rule_for_item(args_copy)) + + return out + +def get_pricing_rule_for_item(args): + if args.get("parenttype") == "Material Request": return {} + + item_details = frappe._dict({ + "doctype": args.doctype, + "name": args.name, + "pricing_rule": None + }) + + if args.ignore_pricing_rule or not args.item_code: + return item_details + + if not (args.item_group and args.brand): + args.item_group, args.brand = frappe.db.get_value("Item", args.item_code, ["item_group", "brand"]) + + if args.customer and not (args.customer_group and args.territory): + args.customer_group, args.territory = frappe.db.get_value("Customer", args.customer, + ["customer_group", "territory"]) + elif args.supplier and not args.supplier_type: + args.supplier_type = frappe.db.get_value("Supplier", args.supplier, "supplier_type") + + pricing_rules = get_pricing_rules(args) + pricing_rule = filter_pricing_rules(args, pricing_rules) + + if pricing_rule: + item_details.pricing_rule = pricing_rule.name + if pricing_rule.price_or_discount == "Price": + item_details.update({ + "price_list_rate": pricing_rule.price*flt(args.plc_conversion_rate)/flt(args.conversion_rate), + "discount_percentage": 0.0 + }) + else: + item_details.discount_percentage = pricing_rule.discount_percentage + + return item_details + +def get_pricing_rules(args): + def _get_tree_conditions(parenttype, allow_blank=True): + field = frappe.scrub(parenttype) + condition = "" + if args.get(field): + lft, rgt = frappe.db.get_value(parenttype, args[field], ["lft", "rgt"]) + parent_groups = frappe.db.sql_list("""select name from `tab%s` + where lft<=%s and rgt>=%s""" % (parenttype, '%s', '%s'), (lft, rgt)) + + if parent_groups: + if allow_blank: parent_groups.append('') + condition = " ifnull("+field+", '') in ('" + "', '".join(parent_groups)+"')" + + return condition + + + conditions = "" + for field in ["company", "customer", "supplier", "supplier_type", "campaign", "sales_partner"]: + if args.get(field): + conditions += " and ifnull("+field+", '') in (%("+field+")s, '')" + else: + conditions += " and ifnull("+field+", '') = ''" + + for parenttype in ["Customer Group", "Territory"]: + group_condition = _get_tree_conditions(parenttype) + if group_condition: + conditions += " and " + group_condition + + conditions += " and ifnull(for_price_list, '') in (%(price_list)s, '')" + + if args.get("transaction_date"): + conditions += """ and %(transaction_date)s between ifnull(valid_from, '2000-01-01') + and ifnull(valid_upto, '2500-12-31')""" + + return frappe.db.sql("""select * from `tabPricing Rule` + where (item_code=%(item_code)s or {item_group_condition} or brand=%(brand)s) + and docstatus < 2 and ifnull(disable, 0) = 0 + and ifnull({transaction_type}, 0) = 1 {conditions} + order by priority desc, name desc""".format( + item_group_condition=_get_tree_conditions("Item Group", False), + transaction_type=args.transaction_type, conditions=conditions), args, as_dict=1) + +def filter_pricing_rules(args, pricing_rules): + # filter for qty + if pricing_rules and args.get("qty"): + pricing_rules = filter(lambda x: (args.qty>=flt(x.min_qty) + and (args.qty<=x.max_qty if x.max_qty else True)), pricing_rules) + + # find pricing rule with highest priority + if pricing_rules: + max_priority = max([cint(p.priority) for p in pricing_rules]) + if max_priority: + pricing_rules = filter(lambda x: cint(x.priority)==max_priority, pricing_rules) + + # apply internal priority + all_fields = ["item_code", "item_group", "brand", "customer", "customer_group", "territory", + "supplier", "supplier_type", "campaign", "sales_partner"] + + if len(pricing_rules) > 1: + for field_set in [["item_code", "item_group", "brand"], + ["customer", "customer_group", "territory"], ["supplier", "supplier_type"]]: + remaining_fields = list(set(all_fields) - set(field_set)) + if if_all_rules_same(pricing_rules, remaining_fields): + pricing_rules = apply_internal_priority(pricing_rules, field_set, args) + break + + if len(pricing_rules) > 1: + price_or_discount = list(set([d.price_or_discount for d in pricing_rules])) + if len(price_or_discount) == 1 and price_or_discount[0] == "Discount Percentage": + pricing_rules = filter(lambda x: x.for_price_list==args.price_list, pricing_rules) \ + or pricing_rules + + if len(pricing_rules) > 1: + frappe.throw(_("Multiple Price Rule exists with same criteria, please resolve \ + conflict by assigning priority. Price Rules: {0}") + .format("\n".join([d.name for d in pricing_rules])), MultiplePricingRuleConflict) + elif pricing_rules: + return pricing_rules[0] + +def if_all_rules_same(pricing_rules, fields): + all_rules_same = True + val = [pricing_rules[0][k] for k in fields] + for p in pricing_rules[1:]: + if val != [p[k] for k in fields]: + all_rules_same = False + break + + return all_rules_same + +def apply_internal_priority(pricing_rules, field_set, args): + filtered_rules = [] + for field in field_set: + if args.get(field): + filtered_rules = filter(lambda x: x[field]==args[field], pricing_rules) + if filtered_rules: break + + return filtered_rules or pricing_rules diff --git a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py index b17c995298..e8496d068b 100644 --- a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py @@ -17,6 +17,7 @@ class TestPricingRule(unittest.TestCase): "doctype": "Pricing Rule", "apply_on": "Item Code", "item_code": "_Test Item", + "selling": 1, "price_or_discount": "Discount Percentage", "price": 0, "discount_percentage": 10, @@ -29,13 +30,15 @@ class TestPricingRule(unittest.TestCase): "company": "_Test Company", "price_list": "_Test Price List", "currency": "_Test Currency", - "doctype": "Sales Order", + "parenttype": "Sales Order", "conversion_rate": 1, "price_list_currency": "_Test Currency", "plc_conversion_rate": 1, "order_type": "Sales", "transaction_type": "selling", "customer": "_Test Customer", + "doctype": "Sales Order Item", + "name": None }) details = get_item_details(args) self.assertEquals(details.get("discount_percentage"), 10) @@ -71,7 +74,7 @@ class TestPricingRule(unittest.TestCase): self.assertEquals(details.get("discount_percentage"), 5) frappe.db.sql("update `tabPricing Rule` set priority=NULL where campaign='_Test Campaign'") - from erpnext.stock.get_item_details import MultiplePricingRuleConflict + from erpnext.accounts.doctype.pricing_rule.pricing_rule import MultiplePricingRuleConflict self.assertRaises(MultiplePricingRuleConflict, get_item_details, args) args.item_code = "_Test Item 2" diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 505a3ba79a..8eb3b0907e 100755 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -231,6 +231,14 @@ "print_hide": 1, "read_only": 0 }, + { + "fieldname": "ignore_pricing_rule", + "fieldtype": "Check", + "label": "Ignore Pricing Rule", + "no_copy": 1, + "permlevel": 1, + "print_hide": 1 + }, { "fieldname": "items", "fieldtype": "Section Break", @@ -744,7 +752,7 @@ "icon": "icon-file-text", "idx": 1, "is_submittable": 1, - "modified": "2014-06-04 08:45:25.582170", + "modified": "2014-06-19 15:50:50.898237", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", @@ -823,6 +831,12 @@ "role": "Auditor", "submit": 0, "write": 0 + }, + { + "permlevel": 1, + "read": 1, + "role": "Accounts Manager", + "write": 1 } ], "read_only_onload": 1, diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index 983f2bb405..a07b69d09f 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -241,6 +241,14 @@ "read_only": 0, "reqd": 1 }, + { + "fieldname": "ignore_pricing_rule", + "fieldtype": "Check", + "label": "Ignore Pricing Rule", + "no_copy": 1, + "permlevel": 1, + "print_hide": 1 + }, { "fieldname": "items", "fieldtype": "Section Break", @@ -1180,7 +1188,7 @@ "icon": "icon-file-text", "idx": 1, "is_submittable": 1, - "modified": "2014-05-27 03:49:17.806077", + "modified": "2014-06-19 16:01:19.720382", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", @@ -1225,6 +1233,12 @@ "read": 1, "report": 1, "role": "Customer" + }, + { + "permlevel": 1, + "read": 1, + "role": "Accounts Manager", + "write": 1 } ], "read_only_onload": 1, diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json index d293683ef4..794c0415bd 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.json +++ b/erpnext/buying/doctype/purchase_order/purchase_order.json @@ -197,6 +197,14 @@ "permlevel": 0, "print_hide": 1 }, + { + "fieldname": "ignore_pricing_rule", + "fieldtype": "Check", + "label": "Ignore Pricing Rule", + "no_copy": 1, + "permlevel": 1, + "print_hide": 1 + }, { "fieldname": "items", "fieldtype": "Section Break", @@ -636,7 +644,7 @@ "icon": "icon-file-text", "idx": 1, "is_submittable": 1, - "modified": "2014-05-27 03:49:15.948363", + "modified": "2014-06-19 15:58:06.375217", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order", @@ -696,6 +704,12 @@ "read": 1, "report": 1, "role": "Supplier" + }, + { + "permlevel": 1, + "read": 1, + "role": "Purchase Manager", + "write": 1 } ], "read_only_onload": 1, diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index 91cc865b7b..3a081249f3 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -187,12 +187,13 @@ class PurchaseOrder(BuyingController): def on_update(self): pass +def set_missing_values(source, target): + target.ignore_pricing_rule = 1 + target.run_method("set_missing_values") + target.run_method("calculate_taxes_and_totals") + @frappe.whitelist() def make_purchase_receipt(source_name, target_doc=None): - def set_missing_values(source, target): - target.run_method("set_missing_values") - target.run_method("calculate_taxes_and_totals") - def update_item(obj, target, source_parent): target.qty = flt(obj.qty) - flt(obj.received_qty) target.stock_qty = (flt(obj.qty) - flt(obj.received_qty)) * flt(obj.conversion_factor) @@ -226,10 +227,6 @@ def make_purchase_receipt(source_name, target_doc=None): @frappe.whitelist() def make_purchase_invoice(source_name, target_doc=None): - def set_missing_values(source, target): - target.run_method("set_missing_values") - target.run_method("calculate_taxes_and_totals") - def update_item(obj, target, source_parent): target.amount = flt(obj.amount) - flt(obj.billed_amt) target.base_amount = target.amount * flt(source_parent.conversion_rate) diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json index 19b0283c50..c3c5bf4f39 100644 --- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json +++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json @@ -196,6 +196,14 @@ "permlevel": 0, "print_hide": 1 }, + { + "fieldname": "ignore_pricing_rule", + "fieldtype": "Check", + "label": "Ignore Pricing Rule", + "no_copy": 1, + "permlevel": 1, + "print_hide": 1 + }, { "fieldname": "items", "fieldtype": "Section Break", @@ -562,7 +570,7 @@ "icon": "icon-shopping-cart", "idx": 1, "is_submittable": 1, - "modified": "2014-05-27 03:49:20.226683", + "modified": "2014-06-19 15:54:27.919675", "modified_by": "Administrator", "module": "Buying", "name": "Supplier Quotation", @@ -640,6 +648,12 @@ "role": "Supplier", "submit": 0, "write": 0 + }, + { + "permlevel": 1, + "read": 1, + "role": "Purchase Manager", + "write": 1 } ], "read_only_onload": 1, diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py index 74a37b38d5..2af7bb93a6 100644 --- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py +++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py @@ -54,6 +54,7 @@ class SupplierQuotation(BuyingController): @frappe.whitelist() def make_purchase_order(source_name, target_doc=None): def set_missing_values(source, target): + target.ignore_pricing_rule = 1 target.run_method("set_missing_values") target.run_method("get_schedule_dates") target.run_method("calculate_taxes_and_totals") diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 3af82903a6..847e09e73d 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -89,14 +89,14 @@ class AccountsController(TransactionBase): """set missing item values""" from erpnext.stock.get_item_details import get_item_details if hasattr(self, "fname"): - parent_dict = {"doctype": self.doctype} + parent_dict = {} for fieldname in self.meta.get_valid_columns(): parent_dict[fieldname] = self.get(fieldname) for item in self.get(self.fname): if item.get("item_code"): - args = item.as_dict() - args.update(parent_dict) + args = parent_dict.copy() + args.update(item.as_dict()) ret = get_item_details(args) for fieldname, value in ret.items(): diff --git a/erpnext/patches.txt b/erpnext/patches.txt index dd57c60b18..041bbd3cc0 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -62,3 +62,4 @@ erpnext.patches.v4_0.update_other_charges_in_custom_purchase_print_formats erpnext.patches.v4_0.create_price_list_if_missing execute:frappe.db.sql("update `tabItem` set end_of_life=null where end_of_life='0000-00-00'") #2014-06-16 erpnext.patches.v4_0.update_users_report_view_settings +erpnext.patches.v4_0.set_pricing_rule_for_buying_or_selling diff --git a/erpnext/patches/v4_0/set_pricing_rule_for_buying_or_selling.py b/erpnext/patches/v4_0/set_pricing_rule_for_buying_or_selling.py new file mode 100644 index 0000000000..218029d8ae --- /dev/null +++ b/erpnext/patches/v4_0/set_pricing_rule_for_buying_or_selling.py @@ -0,0 +1,12 @@ +# 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 frappe + +def execute(): + frappe.db.sql("""update `tabPricing Rule` set selling=1 where ifnull(applicable_for, '') in + ('', 'Customer', 'Customer Group', 'Territory', 'Sales Partner', 'Campaign')""") + + frappe.db.sql("""update `tabPricing Rule` set buying=1 where ifnull(applicable_for, '') in + ('', 'Supplier', 'Supplier Type')""") diff --git a/erpnext/public/js/transaction.js b/erpnext/public/js/transaction.js index 2c372042ea..ea576d5ae0 100644 --- a/erpnext/public/js/transaction.js +++ b/erpnext/public/js/transaction.js @@ -116,8 +116,8 @@ erpnext.TransactionController = erpnext.stock.StockController.extend({ barcode: item.barcode, serial_no: item.serial_no, warehouse: item.warehouse, - doctype: me.frm.doc.doctype, - docname: me.frm.doc.name, + parenttype: me.frm.doc.doctype, + parent: me.frm.doc.name, customer: me.frm.doc.customer, supplier: me.frm.doc.supplier, currency: me.frm.doc.currency, @@ -130,7 +130,10 @@ erpnext.TransactionController = erpnext.stock.StockController.extend({ order_type: me.frm.doc.order_type, is_pos: cint(me.frm.doc.is_pos), is_subcontracted: me.frm.doc.is_subcontracted, - transaction_date: me.frm.doc.transaction_date + transaction_date: me.frm.doc.transaction_date, + ignore_pricing_rule: me.frm.doc.ignore_pricing_rule, + doctype: item.doctype, + name: item.name } }, callback: function(r) { @@ -196,7 +199,7 @@ erpnext.TransactionController = erpnext.stock.StockController.extend({ } this.frm.script_manager.trigger("currency"); - this.apply_pricing_rule() + this.apply_pricing_rule(); } }, @@ -229,7 +232,12 @@ erpnext.TransactionController = erpnext.stock.StockController.extend({ this.frm.set_value("plc_conversion_rate", this.frm.doc.conversion_rate); } if(flt(this.frm.doc.conversion_rate)>0.0) { - this.apply_pricing_rule(); + if(this.frm.doc.ignore_pricing_rule) { + this.calculate_taxes_and_totals(); + } else { + this.apply_pricing_rule(); + } + } }, @@ -283,12 +291,11 @@ erpnext.TransactionController = erpnext.stock.StockController.extend({ } if(this.frm.doc.price_list_currency === this.frm.doc.currency) { this.frm.set_value("conversion_rate", this.frm.doc.plc_conversion_rate); - this.apply_pricing_rule(); } }, qty: function(doc, cdt, cdn) { - this.apply_pricing_rule(frappe.get_doc(cdt, cdn)); + this.apply_pricing_rule(frappe.get_doc(cdt, cdn), true); }, // tax rate @@ -331,51 +338,71 @@ erpnext.TransactionController = erpnext.stock.StockController.extend({ this.calculate_taxes_and_totals(); }, - apply_pricing_rule: function(item) { + ignore_pricing_rule: function() { + this.apply_pricing_rule(); + }, + + apply_pricing_rule: function(item, calculate_taxes_and_totals) { var me = this; - - var _apply_pricing_rule = function(item) { - return me.frm.call({ - method: "erpnext.stock.get_item_details.apply_pricing_rule", - child: item, - args: { - args: { - item_code: item.item_code, - item_group: item.item_group, - brand: item.brand, - qty: item.qty, - customer: me.frm.doc.customer, - customer_group: me.frm.doc.customer_group, - territory: me.frm.doc.territory, - supplier: me.frm.doc.supplier, - supplier_type: me.frm.doc.supplier_type, - currency: me.frm.doc.currency, - conversion_rate: me.frm.doc.conversion_rate, - price_list: me.frm.doc.selling_price_list || - me.frm.doc.buying_price_list, - plc_conversion_rate: me.frm.doc.plc_conversion_rate, - company: me.frm.doc.company, - transaction_date: me.frm.doc.transaction_date || me.frm.doc.posting_date, - campaign: me.frm.doc.campaign, - sales_partner: me.frm.doc.sales_partner - } - }, - callback: function(r) { - if(!r.exc) { - me.frm.script_manager.trigger("price_list_rate", item.doctype, item.name); - } + var item_list = this._get_item_list(item); + var args = { + "item_list": item_list, + "customer": me.frm.doc.customer, + "customer_group": me.frm.doc.customer_group, + "territory": me.frm.doc.territory, + "supplier": me.frm.doc.supplier, + "supplier_type": me.frm.doc.supplier_type, + "currency": me.frm.doc.currency, + "conversion_rate": me.frm.doc.conversion_rate, + "price_list": me.frm.doc.selling_price_list || me.frm.doc.buying_price_list, + "plc_conversion_rate": me.frm.doc.plc_conversion_rate, + "company": me.frm.doc.company, + "transaction_date": me.frm.doc.transaction_date || me.frm.doc.posting_date, + "campaign": me.frm.doc.campaign, + "sales_partner": me.frm.doc.sales_partner, + "ignore_pricing_rule": me.frm.doc.ignore_pricing_rule, + "parenttype": me.frm.doc.doctype, + "parent": me.frm.doc.name + }; + return this.frm.call({ + method: "erpnext.accounts.doctype.pricing_rule.pricing_rule.apply_pricing_rule", + args: { args: args }, + callback: function(r) { + if (!r.exc) { + $.each(r.message, function(i, d) { + $.each(d, function(k, v) { + if (["doctype", "name"].indexOf(k)===-1) { + frappe.model.set_value(d.doctype, d.name, k, v); + } + }); + }); + if(calculate_taxes_and_totals) me.calculate_taxes_and_totals(); } + } + }); + }, + + _get_item_list: function(item) { + var item_list = []; + var append_item = function(d) { + item_list.push({ + "doctype": d.doctype, + "name": d.name, + "item_code": d.item_code, + "item_group": d.item_group, + "brand": d.brand, + "qty": d.qty }); - } + }; - - if(item) { - _apply_pricing_rule(item); + if (item) { + append_item(item); } else { - $.each(this.get_item_doclist(), function(n, item) { - _apply_pricing_rule(item); + $.each(this.get_item_doclist(), function(i, d) { + append_item(d); }); } + return item_list; }, included_in_print_rate: function(doc, cdt, cdn) { diff --git a/erpnext/selling/doctype/quotation/quotation.json b/erpnext/selling/doctype/quotation/quotation.json index 02217386de..1ae0adb363 100644 --- a/erpnext/selling/doctype/quotation/quotation.json +++ b/erpnext/selling/doctype/quotation/quotation.json @@ -274,6 +274,14 @@ "read_only": 0, "reqd": 1 }, + { + "fieldname": "ignore_pricing_rule", + "fieldtype": "Check", + "label": "Ignore Pricing Rule", + "no_copy": 1, + "permlevel": 1, + "print_hide": 1 + }, { "fieldname": "items", "fieldtype": "Section Break", @@ -818,7 +826,7 @@ "idx": 1, "is_submittable": 1, "max_attachments": 1, - "modified": "2014-05-27 03:49:16.670976", + "modified": "2014-06-19 15:59:30.019826", "modified_by": "Administrator", "module": "Selling", "name": "Quotation", @@ -896,6 +904,12 @@ "role": "Maintenance User", "submit": 1, "write": 1 + }, + { + "permlevel": 1, + "read": 1, + "role": "Sales Manager", + "write": 1 } ], "read_only_onload": 1, diff --git a/erpnext/selling/doctype/quotation/quotation.py b/erpnext/selling/doctype/quotation/quotation.py index 62577db247..f396191a2d 100644 --- a/erpnext/selling/doctype/quotation/quotation.py +++ b/erpnext/selling/doctype/quotation/quotation.py @@ -102,7 +102,7 @@ def _make_sales_order(source_name, target_doc=None, ignore_permissions=False): if customer: target.customer = customer.name target.customer_name = customer.customer_name - + target.ignore_pricing_rule = 1 target.ignore_permissions = ignore_permissions target.run_method("set_missing_values") target.run_method("calculate_taxes_and_totals") diff --git a/erpnext/selling/doctype/sales_order/sales_order.json b/erpnext/selling/doctype/sales_order/sales_order.json index c8992271dc..a036370db5 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.json +++ b/erpnext/selling/doctype/sales_order/sales_order.json @@ -285,6 +285,14 @@ "print_hide": 1, "reqd": 1 }, + { + "fieldname": "ignore_pricing_rule", + "fieldtype": "Check", + "label": "Ignore Pricing Rule", + "no_copy": 1, + "permlevel": 1, + "print_hide": 1 + }, { "fieldname": "items", "fieldtype": "Section Break", @@ -874,7 +882,7 @@ "idx": 1, "is_submittable": 1, "issingle": 0, - "modified": "2014-05-27 08:39:19.027965", + "modified": "2014-06-19 16:00:06.626037", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order", @@ -953,6 +961,12 @@ "read": 1, "report": 1, "role": "Material User" + }, + { + "permlevel": 1, + "read": 1, + "role": "Sales Manager", + "write": 1 } ], "read_only_onload": 1, diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index 24da5773a3..c14612ba34 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -245,9 +245,6 @@ class SalesOrder(SellingController): def get_portal_page(self): return "order" if self.docstatus==1 else None -def set_missing_values(source, target): - target.run_method("set_missing_values") - target.run_method("calculate_taxes_and_totals") @frappe.whitelist() def make_material_request(source_name, target_doc=None): @@ -274,6 +271,11 @@ def make_material_request(source_name, target_doc=None): @frappe.whitelist() def make_delivery_note(source_name, target_doc=None): + def set_missing_values(source, target): + target.ignore_pricing_rule = 1 + target.run_method("set_missing_values") + target.run_method("calculate_taxes_and_totals") + def update_item(source, target, source_parent): target.base_amount = (flt(source.qty) - flt(source.delivered_qty)) * flt(source.base_rate) target.amount = (flt(source.qty) - flt(source.delivered_qty)) * flt(source.rate) @@ -312,6 +314,7 @@ def make_delivery_note(source_name, target_doc=None): def make_sales_invoice(source_name, target_doc=None): def set_missing_values(source, target): target.is_pos = 0 + target.ignore_pricing_rule = 1 target.run_method("set_missing_values") target.run_method("calculate_taxes_and_totals") diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json index 9b13b10ec8..690fd055fa 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.json +++ b/erpnext/stock/doctype/delivery_note/delivery_note.json @@ -275,6 +275,14 @@ "read_only": 0, "reqd": 1 }, + { + "fieldname": "ignore_pricing_rule", + "fieldtype": "Check", + "label": "Ignore Pricing Rule", + "no_copy": 1, + "permlevel": 1, + "print_hide": 1 + }, { "fieldname": "items", "fieldtype": "Section Break", @@ -999,7 +1007,7 @@ "idx": 1, "in_create": 0, "is_submittable": 1, - "modified": "2014-05-27 03:49:09.721622", + "modified": "2014-06-19 16:00:47.326127", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note", @@ -1073,6 +1081,12 @@ "read": 1, "report": 1, "role": "Customer" + }, + { + "permlevel": 1, + "read": 1, + "role": "Material Manager", + "write": 1 } ], "read_only_onload": 1, diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index bbc9f81ff4..4b147cc361 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -280,6 +280,7 @@ def make_sales_invoice(source_name, target_doc=None): def update_accounts(source, target): target.is_pos = 0 + target.ignore_pricing_rule = 1 target.run_method("set_missing_values") if len(target.get("entries")) == 0: diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index 506e5d016c..7ab93ebf4c 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -17,7 +17,7 @@ class TestItem(unittest.TestCase): item.is_stock_item = "Yes" item.default_warehouse = None self.assertRaises(WarehouseNotSet, item.insert) - + def test_get_item_details(self): from erpnext.stock.get_item_details import get_item_details to_check = { @@ -41,23 +41,23 @@ class TestItem(unittest.TestCase): "uom": "_Test UOM", "conversion_factor": 1.0, } - + make_test_records("Item Price") - + details = get_item_details({ "item_code": "_Test Item", "company": "_Test Company", "price_list": "_Test Price List", "currency": "_Test Currency", - "doctype": "Sales Order", + "parenttype": "Sales Order", "conversion_rate": 1, "price_list_currency": "_Test Currency", "plc_conversion_rate": 1, "order_type": "Sales", "transaction_type": "selling" }) - + for key, value in to_check.iteritems(): self.assertEquals(value, details.get(key)) -test_records = frappe.get_test_records('Item') \ No newline at end of file +test_records = frappe.get_test_records('Item') diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json index e585bef754..ae748ce3fe 100755 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json @@ -195,6 +195,14 @@ "permlevel": 0, "print_hide": 1 }, + { + "fieldname": "ignore_pricing_rule", + "fieldtype": "Check", + "label": "Ignore Pricing Rule", + "no_copy": 1, + "permlevel": 1, + "print_hide": 1 + }, { "fieldname": "items", "fieldtype": "Section Break", @@ -754,7 +762,7 @@ "icon": "icon-truck", "idx": 1, "is_submittable": 1, - "modified": "2014-05-27 03:49:16.302198", + "modified": "2014-06-19 15:58:37.932064", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt", @@ -821,6 +829,12 @@ "read": 1, "report": 1, "role": "Supplier" + }, + { + "permlevel": 1, + "read": 1, + "role": "Material Manager", + "write": 1 } ], "read_only_onload": 1, diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 13bb193f4b..71c07eba02 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -287,6 +287,7 @@ def make_purchase_invoice(source_name, target_doc=None): frappe.throw(_("All items have already been invoiced")) doc = frappe.get_doc(target) + doc.ignore_pricing_rule = 1 doc.run_method("set_missing_values") doc.run_method("calculate_taxes_and_totals") diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index c5c1280fdb..fe320d153a 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -6,8 +6,7 @@ import frappe from frappe import _, throw from frappe.utils import flt, cint, add_days import json - -class MultiplePricingRuleConflict(frappe.ValidationError): pass +from erpnext.accounts.doctype.pricing_rule.pricing_rule import get_pricing_rule_for_item @frappe.whitelist() def get_item_details(args): @@ -20,14 +19,15 @@ def get_item_details(args): "selling_price_list": None, "price_list_currency": None, "plc_conversion_rate": 1.0, - "doctype": "", - "docname": "", + "parenttype": "", + "parent": "", "supplier": None, "transaction_date": None, "conversion_rate": 1.0, "buying_price_list": None, "is_subcontracted": "Yes" / "No", - "transaction_type": "selling" + "transaction_type": "selling", + "ignore_pricing_rule": 0/1 } """ @@ -37,7 +37,8 @@ def get_item_details(args): args = frappe._dict(args) if not args.get("transaction_type"): - if args.get("doctype")=="Material Request" or frappe.get_meta(args.get("doctype")).get_field("supplier"): + if args.get("parenttype")=="Material Request" or \ + frappe.get_meta(args.get("parenttype")).get_field("supplier"): args.transaction_type = "buying" else: args.transaction_type = "selling" @@ -73,9 +74,9 @@ def get_item_details(args): if args.get(key) is None: args[key] = value - out.update(apply_pricing_rule(args)) + out.update(get_pricing_rule_for_item(args)) - if args.get("doctype") in ("Sales Invoice", "Delivery Note"): + if args.get("parenttype") in ("Sales Invoice", "Delivery Note"): if item_doc.has_serial_no == "Yes" and not args.serial_no: out.serial_no = get_serial_nos_by_fifo(args, item_doc) @@ -113,7 +114,7 @@ def validate_item_details(args, item): elif item.is_sales_item != "Yes": throw(_("Item {0} must be a Sales Item").format(item.name)) - elif args.transaction_type == "buying" and args.doctype != "Material Request": + elif args.transaction_type == "buying" and args.parenttype != "Material Request": # validate if purchase item or subcontracted item if item.is_purchase_item != "Yes": throw(_("Item {0} must be a Purchase Item").format(item.name)) @@ -144,7 +145,7 @@ def get_basic_details(args, item_doc): "item_tax_rate": json.dumps(dict(([d.tax_type, d.tax_rate] for d in item_doc.get("item_tax")))), "uom": item.stock_uom, - "min_order_qty": flt(item.min_order_qty) if args.doctype == "Material Request" else "", + "min_order_qty": flt(item.min_order_qty) if args.parenttype == "Material Request" else "", "conversion_factor": 1.0, "qty": 1.0, "price_list_rate": 0.0, @@ -162,7 +163,7 @@ def get_basic_details(args, item_doc): return out def get_price_list_rate(args, item_doc, out): - meta = frappe.get_meta(args.doctype) + meta = frappe.get_meta(args.parenttype) if meta.get_field("currency"): validate_price_list(args) @@ -179,7 +180,7 @@ def get_price_list_rate(args, item_doc, out): if not out.price_list_rate and args.transaction_type == "buying": from erpnext.stock.doctype.item.item import get_last_purchase_details out.update(get_last_purchase_details(item_doc.name, - args.docname, args.conversion_rate)) + args.parent, args.conversion_rate)) def validate_price_list(args): if args.get("price_list"): @@ -248,142 +249,6 @@ def get_pos_settings(company): return pos_settings and pos_settings[0] or None -@frappe.whitelist() -def apply_pricing_rule(args): - if isinstance(args, basestring): - args = json.loads(args) - - args = frappe._dict(args) - out = frappe._dict() - if args.get("doctype") == "Material Request" or not args.get("item_code"): return out - - if not args.get("item_group") or not args.get("brand"): - args.item_group, args.brand = frappe.db.get_value("Item", - args.item_code, ["item_group", "brand"]) - - if args.get("customer") and (not args.get("customer_group") or not args.get("territory")): - args.customer_group, args.territory = frappe.db.get_value("Customer", - args.customer, ["customer_group", "territory"]) - - if args.get("supplier") and not args.get("supplier_type"): - args.supplier_type = frappe.db.get_value("Supplier", args.supplier, "supplier_type") - - pricing_rules = get_pricing_rules(args) - - pricing_rule = filter_pricing_rules(args, pricing_rules) - - if pricing_rule: - out.pricing_rule = pricing_rule.name - if pricing_rule.price_or_discount == "Price": - out.base_price_list_rate = pricing_rule.price - out.price_list_rate = pricing_rule.price*flt(args.plc_conversion_rate)/flt(args.conversion_rate) - out.base_rate = out.base_price_list_rate - out.rate = out.price_list_rate - out.discount_percentage = 0.0 - else: - out.discount_percentage = pricing_rule.discount_percentage - else: - out.pricing_rule = None - - return out - - -def get_pricing_rules(args): - def _get_tree_conditions(doctype, allow_blank=True): - field = frappe.scrub(doctype) - condition = "" - if args.get(field): - lft, rgt = frappe.db.get_value(doctype, args[field], ["lft", "rgt"]) - parent_groups = frappe.db.sql_list("""select name from `tab%s` - where lft<=%s and rgt>=%s""" % (doctype, '%s', '%s'), (lft, rgt)) - - if parent_groups: - if allow_blank: parent_groups.append('') - condition = " ifnull("+field+", '') in ('" + "', '".join(parent_groups)+"')" - - return condition - - - conditions = "" - for field in ["company", "customer", "supplier", "supplier_type", "campaign", "sales_partner"]: - if args.get(field): - conditions += " and ifnull("+field+", '') in (%("+field+")s, '')" - else: - conditions += " and ifnull("+field+", '') = ''" - - for doctype in ["Customer Group", "Territory"]: - group_condition = _get_tree_conditions(doctype) - if group_condition: - conditions += " and " + group_condition - - conditions += " and ifnull(for_price_list, '') in (%(price_list)s, '')" - - if args.get("transaction_date"): - conditions += """ and %(transaction_date)s between ifnull(valid_from, '2000-01-01') - and ifnull(valid_upto, '2500-12-31')""" - - return frappe.db.sql("""select * from `tabPricing Rule` - where (item_code=%(item_code)s or {item_group_condition} or brand=%(brand)s) - and docstatus < 2 and ifnull(disable, 0) = 0 {conditions} - order by priority desc, name desc""".format( - item_group_condition=_get_tree_conditions("Item Group", False), conditions=conditions), - args, as_dict=1) - -def filter_pricing_rules(args, pricing_rules): - # filter for qty - if pricing_rules and args.get("qty"): - pricing_rules = filter(lambda x: (args.qty>=flt(x.min_qty) - and (args.qty<=x.max_qty if x.max_qty else True)), pricing_rules) - - # find pricing rule with highest priority - if pricing_rules: - max_priority = max([cint(p.priority) for p in pricing_rules]) - if max_priority: - pricing_rules = filter(lambda x: cint(x.priority)==max_priority, pricing_rules) - - # apply internal priority - all_fields = ["item_code", "item_group", "brand", "customer", "customer_group", "territory", - "supplier", "supplier_type", "campaign", "sales_partner"] - - if len(pricing_rules) > 1: - for field_set in [["item_code", "item_group", "brand"], - ["customer", "customer_group", "territory"], ["supplier", "supplier_type"]]: - remaining_fields = list(set(all_fields) - set(field_set)) - if if_all_rules_same(pricing_rules, remaining_fields): - pricing_rules = apply_internal_priority(pricing_rules, field_set, args) - break - - if len(pricing_rules) > 1: - price_or_discount = list(set([d.price_or_discount for d in pricing_rules])) - if len(price_or_discount) == 1 and price_or_discount[0] == "Discount Percentage": - pricing_rules = filter(lambda x: x.for_price_list==args.price_list, pricing_rules) \ - or pricing_rules - - if len(pricing_rules) > 1: - frappe.throw(_("Multiple Price Rule exists with same criteria, please resolve \ - conflict by assigning priority. Price Rules: {0}") - .format("\n".join([d.name for d in pricing_rules])), MultiplePricingRuleConflict) - elif pricing_rules: - return pricing_rules[0] - -def if_all_rules_same(pricing_rules, fields): - all_rules_same = True - val = [pricing_rules[0][k] for k in fields] - for p in pricing_rules[1:]: - if val != [p[k] for k in fields]: - all_rules_same = False - break - - return all_rules_same - -def apply_internal_priority(pricing_rules, field_set, args): - filtered_rules = [] - for field in field_set: - if args.get(field): - filtered_rules = filter(lambda x: x[field]==args[field], pricing_rules) - if filtered_rules: break - - return filtered_rules or pricing_rules def get_serial_nos_by_fifo(args, item_doc): return "\n".join(frappe.db.sql_list("""select name from `tabSerial No` From be50c289f8ffcd36dfb8ba927e884d9d966f1ef0 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Fri, 20 Jun 2014 16:19:55 +0530 Subject: [PATCH 2/5] validate pricing rule discount with item max discount --- erpnext/accounts/doctype/pricing_rule/pricing_rule.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py index 5cf500a597..c86e3f61e5 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py @@ -19,6 +19,7 @@ class PricingRule(Document): self.validate_min_max_qty() self.cleanup_fields_value() self.validate_price_or_discount() + self.validate_max_discount() def validate_mandatory(self): for field in ["apply_on", "applicable_for"]: @@ -61,6 +62,13 @@ class PricingRule(Document): if flt(self.get(frappe.scrub(field))) < 0: throw(_("{0} can not be negative").format(field)) + def validate_max_discount(self): + if self.price_or_discount == "Discount Percentage" and self.item_code: + max_discount = frappe.db.get_value("Item", self.item_code, "max_discount") + if flt(self.discount_percentage) > max_discount: + throw(_("Max discount allowed for item: {0} is {1}%".format(self.item_code, max_discount))) + + #-------------------------------------------------------------------------------- @frappe.whitelist() From 1d37698d4c1efb7e15c7a17bdf412105d5502b79 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Fri, 20 Jun 2014 16:30:44 +0530 Subject: [PATCH 3/5] Reload pricing rule in patch --- erpnext/patches/v4_0/set_pricing_rule_for_buying_or_selling.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/patches/v4_0/set_pricing_rule_for_buying_or_selling.py b/erpnext/patches/v4_0/set_pricing_rule_for_buying_or_selling.py index 218029d8ae..8be846ff16 100644 --- a/erpnext/patches/v4_0/set_pricing_rule_for_buying_or_selling.py +++ b/erpnext/patches/v4_0/set_pricing_rule_for_buying_or_selling.py @@ -5,6 +5,7 @@ from __future__ import unicode_literals import frappe def execute(): + frappe.reload_doc("accounts", "doctype", "pricing_rule") frappe.db.sql("""update `tabPricing Rule` set selling=1 where ifnull(applicable_for, '') in ('', 'Customer', 'Customer Group', 'Territory', 'Sales Partner', 'Campaign')""") From 3c946b2a72d014f85f429f5f73b14c62ec692e2a Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Fri, 20 Jun 2014 19:07:44 +0530 Subject: [PATCH 4/5] validate pricing rule discount with item max discount --- erpnext/accounts/doctype/pricing_rule/pricing_rule.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py index c86e3f61e5..77b52b18f8 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py @@ -65,7 +65,7 @@ class PricingRule(Document): def validate_max_discount(self): if self.price_or_discount == "Discount Percentage" and self.item_code: max_discount = frappe.db.get_value("Item", self.item_code, "max_discount") - if flt(self.discount_percentage) > max_discount: + if max_discount and flt(self.discount_percentage) > flt(max_discount): throw(_("Max discount allowed for item: {0} is {1}%".format(self.item_code, max_discount))) From 56f6d017574235beb9ff4d4f140b3477e400964a Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Fri, 20 Jun 2014 19:37:53 +0530 Subject: [PATCH 5/5] Minor fixes --- .../doctype/pricing_rule/pricing_rule.json | 19 +++++++++---------- .../doctype/pricing_rule/pricing_rule.py | 13 +++++++------ 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json index e15cea83b8..2d318c6360 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json @@ -126,18 +126,18 @@ "options": "Supplier Type", "permlevel": 0 }, - { - "fieldname": "max_qty", - "fieldtype": "Float", - "label": "Max Qty", - "permlevel": 0 - }, { "fieldname": "min_qty", "fieldtype": "Float", "label": "Min Qty", "permlevel": 0 }, + { + "fieldname": "max_qty", + "fieldtype": "Float", + "label": "Max Qty", + "permlevel": 0 + }, { "fieldname": "col_break1", "fieldtype": "Column Break", @@ -235,7 +235,7 @@ "icon": "icon-gift", "idx": 1, "istable": 0, - "modified": "2014-06-19 15:00:09.962572", + "modified": "2014-06-20 19:36:22.502381", "modified_by": "Administrator", "module": "Accounts", "name": "Pricing Rule", @@ -244,8 +244,8 @@ { "create": 1, "delete": 1, - "export": 0, - "import": 0, + "export": 1, + "import": 1, "permlevel": 0, "read": 1, "report": 1, @@ -290,7 +290,6 @@ "permlevel": 0, "read": 1, "report": 1, - "restrict": 1, "role": "System Manager", "write": 1 } diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py index 77b52b18f8..967d583aab 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py @@ -66,7 +66,7 @@ class PricingRule(Document): if self.price_or_discount == "Discount Percentage" and self.item_code: max_discount = frappe.db.get_value("Item", self.item_code, "max_discount") if max_discount and flt(self.discount_percentage) > flt(max_discount): - throw(_("Max discount allowed for item: {0} is {1}%".format(self.item_code, max_discount))) + throw(_("Max discount allowed for item: {0} is {1}%").format(self.item_code, max_discount)) #-------------------------------------------------------------------------------- @@ -141,7 +141,8 @@ def get_pricing_rule_for_item(args): item_details.pricing_rule = pricing_rule.name if pricing_rule.price_or_discount == "Price": item_details.update({ - "price_list_rate": pricing_rule.price*flt(args.plc_conversion_rate)/flt(args.conversion_rate), + "price_list_rate": pricing_rule.price/flt(args.conversion_rate) \ + if args.conversion_rate else 0.0, "discount_percentage": 0.0 }) else: @@ -167,10 +168,10 @@ def get_pricing_rules(args): conditions = "" for field in ["company", "customer", "supplier", "supplier_type", "campaign", "sales_partner"]: - if args.get(field): - conditions += " and ifnull("+field+", '') in (%("+field+")s, '')" - else: - conditions += " and ifnull("+field+", '') = ''" + if args.get(field): + conditions += " and ifnull("+field+", '') in (%("+field+")s, '')" + else: + conditions += " and ifnull("+field+", '') = ''" for parenttype in ["Customer Group", "Territory"]: group_condition = _get_tree_conditions(parenttype)