diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json index 99c5b34fa3..6e7ebd1414 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json @@ -176,7 +176,7 @@ }, { "collapsible": 1, - "depends_on": "eval:doc.apply_on != 'Transaction'", + "depends_on": "eval:doc.apply_on != 'Transaction' && !doc.mixed_conditions", "fieldname": "section_break_18", "fieldtype": "Section Break", "label": "Discount on Other Item" @@ -297,12 +297,12 @@ { "fieldname": "min_qty", "fieldtype": "Float", - "label": "Min Qty" + "label": "Min Qty (As Per Stock UOM)" }, { "fieldname": "max_qty", "fieldtype": "Float", - "label": "Max Qty" + "label": "Max Qty (As Per Stock UOM)" }, { "fieldname": "column_break_21", @@ -481,7 +481,7 @@ "description": "System will notify to increase or decrease quantity or amount ", "fieldname": "threshold_percentage", "fieldtype": "Percent", - "label": "Threshold for Suggestion" + "label": "Threshold for Suggestion (In Percentage)" }, { "description": "Higher the number, higher the priority", @@ -583,10 +583,11 @@ "icon": "fa fa-gift", "idx": 1, "links": [], - "modified": "2021-08-06 15:10:04.219321", + "modified": "2022-09-16 16:00:38.356266", "modified_by": "Administrator", "module": "Accounts", "name": "Pricing Rule", + "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", "permissions": [ { @@ -642,5 +643,6 @@ "show_name_in_global_search": 1, "sort_field": "modified", "sort_order": "DESC", + "states": [], "title_field": "title" -} +} \ 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 98e0a9b215..9af3188e47 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py @@ -324,7 +324,7 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=Fa if isinstance(pricing_rule, str): pricing_rule = frappe.get_cached_doc("Pricing Rule", pricing_rule) - pricing_rule.apply_rule_on_other_items = get_pricing_rule_items(pricing_rule) + pricing_rule.apply_rule_on_other_items = get_pricing_rule_items(pricing_rule) or [] if pricing_rule.get("suggestion"): continue @@ -337,7 +337,6 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=Fa if pricing_rule.mixed_conditions or pricing_rule.apply_rule_on_other: item_details.update( { - "apply_rule_on_other_items": json.dumps(pricing_rule.apply_rule_on_other_items), "price_or_product_discount": pricing_rule.price_or_product_discount, "apply_rule_on": ( frappe.scrub(pricing_rule.apply_rule_on_other) @@ -347,6 +346,9 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=Fa } ) + if pricing_rule.apply_rule_on_other_items: + item_details["apply_rule_on_other_items"] = json.dumps(pricing_rule.apply_rule_on_other_items) + if pricing_rule.coupon_code_based == 1 and args.coupon_code == None: return item_details @@ -492,7 +494,7 @@ def remove_pricing_rule_for_item(pricing_rules, item_details, item_code=None, ra ) if pricing_rule.get("mixed_conditions") or pricing_rule.get("apply_rule_on_other"): - items = get_pricing_rule_items(pricing_rule) + items = get_pricing_rule_items(pricing_rule, other_items=True) item_details.apply_on = ( frappe.scrub(pricing_rule.apply_rule_on_other) if pricing_rule.apply_rule_on_other diff --git a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py index 3bd0cd2e83..0a9db6b0f5 100644 --- a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py @@ -766,6 +766,68 @@ class TestPricingRule(unittest.TestCase): frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule with Min Qty - 1") frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule with Min Qty - 2") + def test_pricing_rule_for_other_items_cond_with_amount(self): + item = make_item("Water Flask New") + other_item = make_item("Other Water Flask New") + make_item_price(item.name, "_Test Price List", 100) + make_item_price(other_item.name, "_Test Price List", 100) + + pricing_rule_record = { + "doctype": "Pricing Rule", + "title": "_Test Water Flask Rule", + "apply_on": "Item Code", + "apply_rule_on_other": "Item Code", + "price_or_product_discount": "Price", + "rate_or_discount": "Discount Percentage", + "other_item_code": other_item.name, + "items": [ + { + "item_code": item.name, + } + ], + "selling": 1, + "currency": "INR", + "min_amt": 200, + "discount_percentage": 10, + "company": "_Test Company", + } + rule = frappe.get_doc(pricing_rule_record) + rule.insert() + + si = create_sales_invoice(do_not_save=True, item_code=item.name) + si.append( + "items", + { + "item_code": other_item.name, + "item_name": other_item.item_name, + "description": other_item.description, + "stock_uom": other_item.stock_uom, + "uom": other_item.stock_uom, + "cost_center": si.items[0].cost_center, + "expense_account": si.items[0].expense_account, + "warehouse": si.items[0].warehouse, + "conversion_factor": 1, + "qty": 1, + }, + ) + si.selling_price_list = "_Test Price List" + si.save() + + self.assertEqual(si.items[0].discount_percentage, 0) + self.assertEqual(si.items[1].discount_percentage, 0) + + si.items[0].qty = 2 + si.save() + + self.assertEqual(si.items[0].discount_percentage, 0) + self.assertEqual(si.items[0].stock_qty, 2) + self.assertEqual(si.items[0].amount, 200) + self.assertEqual(si.items[0].price_list_rate, 100) + self.assertEqual(si.items[1].discount_percentage, 10) + + si.delete() + rule.delete() + test_dependencies = ["Campaign"] diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index 70926cfbd7..1f29d732ba 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -252,12 +252,6 @@ def filter_pricing_rules(args, pricing_rules, doc=None): stock_qty = flt(args.get("stock_qty")) amount = flt(args.get("price_list_rate")) * flt(args.get("qty")) - if pricing_rules[0].apply_rule_on_other: - field = frappe.scrub(pricing_rules[0].apply_rule_on_other) - - if field and pricing_rules[0].get("other_" + field) != args.get(field): - return - pr_doc = frappe.get_cached_doc("Pricing Rule", pricing_rules[0].name) if pricing_rules[0].mixed_conditions and doc: @@ -274,7 +268,7 @@ def filter_pricing_rules(args, pricing_rules, doc=None): amount += data[1] if pricing_rules[0].apply_rule_on_other and not pricing_rules[0].mixed_conditions and doc: - pricing_rules = get_qty_and_rate_for_other_item(doc, pr_doc, pricing_rules) or [] + pricing_rules = get_qty_and_rate_for_other_item(doc, pr_doc, pricing_rules, args) or [] else: pricing_rules = filter_pricing_rules_for_qty_amount(stock_qty, amount, pricing_rules, args) @@ -352,16 +346,14 @@ def validate_quantity_and_amount_for_suggestion(args, qty, amount, item_code, tr if fieldname: msg = _( "If you {0} {1} quantities of the item {2}, the scheme {3} will be applied on the item." - ).format( - type_of_transaction, args.get(fieldname), bold(item_code), bold(args.rule_description) - ) + ).format(type_of_transaction, args.get(fieldname), bold(item_code), bold(args.title)) if fieldname in ["min_amt", "max_amt"]: msg = _("If you {0} {1} worth item {2}, the scheme {3} will be applied on the item.").format( type_of_transaction, fmt_money(args.get(fieldname), currency=args.get("currency")), bold(item_code), - bold(args.rule_description), + bold(args.title), ) frappe.msgprint(msg) @@ -454,17 +446,29 @@ def get_qty_and_rate_for_mixed_conditions(doc, pr_doc, args): return sum_qty, sum_amt, items -def get_qty_and_rate_for_other_item(doc, pr_doc, pricing_rules): - items = get_pricing_rule_items(pr_doc) +def get_qty_and_rate_for_other_item(doc, pr_doc, pricing_rules, row_item): + other_items = get_pricing_rule_items(pr_doc, other_items=True) + pricing_rule_apply_on = apply_on_table.get(pr_doc.get("apply_on")) + apply_on = frappe.scrub(pr_doc.get("apply_on")) + + items = [] + for d in pr_doc.get(pricing_rule_apply_on): + if apply_on == "item_group": + items.extend(get_child_item_groups(d.get(apply_on))) + else: + items.append(d.get(apply_on)) for row in doc.items: - if row.get(frappe.scrub(pr_doc.apply_rule_on_other)) in items: - pricing_rules = filter_pricing_rules_for_qty_amount( - row.get("stock_qty"), row.get("amount"), pricing_rules, row - ) + if row.get(apply_on) in items: + if not row.get("qty"): + continue + + stock_qty = row.get("qty") * (row.get("conversion_factor") or 1.0) + amount = stock_qty * (row.get("price_list_rate") or row.get("rate")) + pricing_rules = filter_pricing_rules_for_qty_amount(stock_qty, amount, pricing_rules, row) if pricing_rules and pricing_rules[0]: - pricing_rules[0].apply_rule_on_other_items = items + pricing_rules[0].apply_rule_on_other_items = other_items return pricing_rules @@ -658,21 +662,21 @@ def apply_pricing_rule_for_free_items(doc, pricing_rule_args, set_missing_values doc.append("items", args) -def get_pricing_rule_items(pr_doc): +def get_pricing_rule_items(pr_doc, other_items=False) -> list: apply_on_data = [] apply_on = frappe.scrub(pr_doc.get("apply_on")) pricing_rule_apply_on = apply_on_table.get(pr_doc.get("apply_on")) - for d in pr_doc.get(pricing_rule_apply_on): - if apply_on == "item_group": - apply_on_data.extend(get_child_item_groups(d.get(apply_on))) - else: - apply_on_data.append(d.get(apply_on)) - - if pr_doc.apply_rule_on_other: + if pr_doc.apply_rule_on_other and other_items: apply_on = frappe.scrub(pr_doc.apply_rule_on_other) apply_on_data.append(pr_doc.get("other_" + apply_on)) + else: + for d in pr_doc.get(pricing_rule_apply_on): + if apply_on == "item_group": + apply_on_data.extend(get_child_item_groups(d.get(apply_on))) + else: + apply_on_data.append(d.get(apply_on)) return list(set(apply_on_data)) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 938de63f33..8686cb5cc0 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -572,6 +572,11 @@ class AccountsController(TransactionBase): # if user changed the discount percentage then set user's discount percentage ? if pricing_rule_args.get("price_or_product_discount") == "Price": item.set("pricing_rules", pricing_rule_args.get("pricing_rules")) + if pricing_rule_args.get("apply_rule_on_other_items"): + other_items = json.loads(pricing_rule_args.get("apply_rule_on_other_items")) + if other_items and item.item_code not in other_items: + return + item.set("discount_percentage", pricing_rule_args.get("discount_percentage")) item.set("discount_amount", pricing_rule_args.get("discount_amount")) if pricing_rule_args.get("pricing_rule_for") == "Rate": diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index c0a8c9e088..c17610b58a 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1492,7 +1492,17 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe frappe.model.set_value(child.doctype, child.name, "rate", value); } + if (key === "pricing_rules") { + frappe.model.set_value(child.doctype, child.name, key, value); + } + if (key !== "free_item_data") { + if (child.apply_rule_on_other_items && JSON.parse(child.apply_rule_on_other_items).length) { + if (!in_list(JSON.parse(child.apply_rule_on_other_items), child.item_code)) { + continue; + } + } + frappe.model.set_value(child.doctype, child.name, key, value); } } @@ -1510,11 +1520,11 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe this.remove_pricing_rule(frappe.get_doc(child.doctype, child.name)); } - if (child.free_item_data.length > 0) { + if (child.free_item_data && child.free_item_data.length > 0) { this.apply_product_discount(child); } - if (child.apply_rule_on_other_items) { + if (child.apply_rule_on_other_items && JSON.parse(child.apply_rule_on_other_items).length) { items_rule_dict[child.name] = child; } } @@ -1530,11 +1540,11 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe for(var k in args) { let data = args[k]; - if (data && data.apply_rule_on_other_items) { + if (data && data.apply_rule_on_other_items && JSON.parse(data.apply_rule_on_other_items)) { me.frm.doc.items.forEach(d => { - if (in_list(data.apply_rule_on_other_items, d[data.apply_rule_on])) { + if (in_list(JSON.parse(data.apply_rule_on_other_items), d[data.apply_rule_on])) { for(var k in data) { - if (in_list(fields, k) && data[k] && (data.price_or_product_discount === 'price' || k === 'pricing_rules')) { + if (in_list(fields, k) && data[k] && (data.price_or_product_discount === 'Price' || k === 'pricing_rules')) { frappe.model.set_value(d.doctype, d.name, k, data[k]); } }