Skip to content

Commit

Permalink
feat(pricing rule): free qty rounding and recursion qty (#32577)
Browse files Browse the repository at this point in the history
Option to specify recursion start qty and repeating qty

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
(cherry picked from commit 1d83fb2)
  • Loading branch information
rtdany10 authored and mergify[bot] committed Nov 1, 2022
1 parent 6af43ba commit 9b66020
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 4 deletions.
27 changes: 26 additions & 1 deletion erpnext/accounts/doctype/pricing_rule/pricing_rule.json
Expand Up @@ -52,7 +52,10 @@
"free_item_rate",
"column_break_42",
"free_item_uom",
"round_free_qty",
"is_recursive",
"recurse_for",
"apply_recursion_over",
"section_break_23",
"valid_from",
"valid_upto",
Expand Down Expand Up @@ -578,12 +581,34 @@
"fieldtype": "Select",
"label": "Naming Series",
"options": "PRLE-.####"
},
{
"default": "0",
"fieldname": "round_free_qty",
"fieldtype": "Check",
"label": "Round Free Qty"
},
{
"depends_on": "is_recursive",
"description": "Give free item for every N quantity",
"fieldname": "recurse_for",
"fieldtype": "Float",
"label": "Recurse Every (As Per Transaction UOM)",
"mandatory_depends_on": "is_recursive"
},
{
"default": "0",
"depends_on": "is_recursive",
"description": "Qty for which recursion isn't applicable.",
"fieldname": "apply_recursion_over",
"fieldtype": "Float",
"label": "Apply Recursion Over (As Per Transaction UOM)"
}
],
"icon": "fa fa-gift",
"idx": 1,
"links": [],
"modified": "2022-09-16 16:00:38.356266",
"modified": "2022-10-13 19:05:35.056304",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Pricing Rule",
Expand Down
13 changes: 13 additions & 0 deletions erpnext/accounts/doctype/pricing_rule/pricing_rule.py
Expand Up @@ -24,6 +24,7 @@ def validate(self):
self.validate_applicable_for_selling_or_buying()
self.validate_min_max_amt()
self.validate_min_max_qty()
self.validate_recursion()
self.cleanup_fields_value()
self.validate_rate_or_discount()
self.validate_max_discount()
Expand Down Expand Up @@ -109,6 +110,18 @@ def validate_min_max_amt(self):
if self.min_amt and self.max_amt and flt(self.min_amt) > flt(self.max_amt):
throw(_("Min Amt can not be greater than Max Amt"))

def validate_recursion(self):
if self.price_or_product_discount != "Product":
return
if self.free_item or self.same_item:
if flt(self.recurse_for) <= 0:
self.recurse_for = 1
if self.is_recursive:
if flt(self.apply_recursion_over) > flt(self.min_qty):
throw(_("Min Qty should be greater than Recurse Over Qty"))
if flt(self.apply_recursion_over) < 0:
throw(_("Recurse Over Qty cannot be less than 0"))

def cleanup_fields_value(self):
for logic_field in ["apply_on", "applicable_for", "rate_or_discount"]:
fieldname = frappe.scrub(self.get(logic_field) or "")
Expand Down
39 changes: 39 additions & 0 deletions erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py
Expand Up @@ -943,6 +943,45 @@ def test_pricing_rule_for_other_items_cond_with_amount(self):
si.delete()
rule.delete()

def test_pricing_rule_for_product_free_item_rounded_qty_and_recursion(self):
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule")
test_record = {
"doctype": "Pricing Rule",
"title": "_Test Pricing Rule",
"apply_on": "Item Code",
"currency": "USD",
"items": [
{
"item_code": "_Test Item",
}
],
"selling": 1,
"rate": 0,
"min_qty": 3,
"max_qty": 7,
"price_or_product_discount": "Product",
"same_item": 1,
"free_qty": 1,
"round_free_qty": 1,
"is_recursive": 1,
"recurse_for": 2,
"company": "_Test Company",
}
frappe.get_doc(test_record.copy()).insert()

# With pricing rule
so = make_sales_order(item_code="_Test Item", qty=5)
so.load_from_db()
self.assertEqual(so.items[1].is_free_item, 1)
self.assertEqual(so.items[1].item_code, "_Test Item")
self.assertEqual(so.items[1].qty, 2)

so = make_sales_order(item_code="_Test Item", qty=7)
so.load_from_db()
self.assertEqual(so.items[1].is_free_item, 1)
self.assertEqual(so.items[1].item_code, "_Test Item")
self.assertEqual(so.items[1].qty, 4)


test_dependencies = ["Campaign"]

Expand Down
8 changes: 6 additions & 2 deletions erpnext/accounts/doctype/pricing_rule/utils.py
Expand Up @@ -627,9 +627,13 @@ def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None):

qty = pricing_rule.free_qty or 1
if pricing_rule.is_recursive:
transaction_qty = args.get("qty") if args else doc.total_qty
transaction_qty = (
args.get("qty") if args else doc.total_qty
) - pricing_rule.apply_recursion_over
if transaction_qty:
qty = flt(transaction_qty) * qty
qty = flt(transaction_qty) * qty / pricing_rule.recurse_for
if pricing_rule.round_free_qty:
qty = round(qty)

free_item_data_args = {
"item_code": free_item,
Expand Down
3 changes: 2 additions & 1 deletion erpnext/public/js/controllers/transaction.js
Expand Up @@ -1404,7 +1404,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
if (!r.exc && r.message) {
me._set_values_for_item_list(r.message);
if(item) me.set_gross_profit(item);
if(me.frm.doc.apply_discount_on) me.frm.trigger("apply_discount_on")
if (me.frm.doc.apply_discount_on) me.frm.trigger("apply_discount_on")
}
}
});
Expand Down Expand Up @@ -1577,6 +1577,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
for (let key in pr_row) {
row_to_modify[key] = pr_row[key];
}
this.frm.script_manager.copy_from_first_row("items", row_to_modify, ["expense_account", "income_account"]);
});

// free_item_data is a temporary variable
Expand Down

0 comments on commit 9b66020

Please sign in to comment.