feat: recursive product discount

This commit is contained in:
Rohit Waghchaure 2021-03-06 22:08:08 +05:30
parent 2c191c105f
commit cd8422a840
7 changed files with 90 additions and 46 deletions

View File

@ -44,6 +44,14 @@
"column_break_21", "column_break_21",
"min_amt", "min_amt",
"max_amt", "max_amt",
"product_discount_scheme_section",
"same_item",
"free_item",
"free_qty",
"free_item_rate",
"column_break_42",
"free_item_uom",
"is_recursive",
"section_break_23", "section_break_23",
"valid_from", "valid_from",
"valid_upto", "valid_upto",
@ -62,13 +70,6 @@
"discount_amount", "discount_amount",
"discount_percentage", "discount_percentage",
"for_price_list", "for_price_list",
"product_discount_scheme_section",
"same_item",
"free_item",
"free_qty",
"column_break_51",
"free_item_uom",
"free_item_rate",
"section_break_13", "section_break_13",
"threshold_percentage", "threshold_percentage",
"priority", "priority",
@ -459,10 +460,6 @@
"fieldtype": "Float", "fieldtype": "Float",
"label": "Qty" "label": "Qty"
}, },
{
"fieldname": "column_break_51",
"fieldtype": "Column Break"
},
{ {
"fieldname": "free_item_uom", "fieldname": "free_item_uom",
"fieldtype": "Link", "fieldtype": "Link",
@ -553,19 +550,33 @@
"fieldname": "promotional_scheme", "fieldname": "promotional_scheme",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Promotional Scheme", "label": "Promotional Scheme",
"options": "Promotional Scheme" "no_copy": 1,
"options": "Promotional Scheme",
"print_hide": 1,
"read_only": 1
}, },
{ {
"description": "Simple Python Expression, Example: territory != 'All Territories'", "description": "Simple Python Expression, Example: territory != 'All Territories'",
"fieldname": "condition", "fieldname": "condition",
"fieldtype": "Code", "fieldtype": "Code",
"label": "Condition" "label": "Condition"
},
{
"fieldname": "column_break_42",
"fieldtype": "Column Break"
},
{
"default": "0",
"description": "Discounts to be applied in sequential ranges like buy 1 get 1, buy 2 get 2, buy 3 get 3 and so on",
"fieldname": "is_recursive",
"fieldtype": "Check",
"label": "Is Recursive"
} }
], ],
"icon": "fa fa-gift", "icon": "fa fa-gift",
"idx": 1, "idx": 1,
"links": [], "links": [],
"modified": "2020-12-04 00:36:24.698219", "modified": "2021-03-06 22:01:24.840422",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Pricing Rule", "name": "Pricing Rule",

View File

@ -237,6 +237,7 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=Fa
"doctype": args.doctype, "doctype": args.doctype,
"has_margin": False, "has_margin": False,
"name": args.name, "name": args.name,
"free_item_data": [],
"parent": args.parent, "parent": args.parent,
"parenttype": args.parenttype, "parenttype": args.parenttype,
"child_docname": args.get('child_docname') "child_docname": args.get('child_docname')

View File

@ -367,7 +367,7 @@ def get_qty_and_rate_for_mixed_conditions(doc, pr_doc, args):
if items and doc.get("items"): if items and doc.get("items"):
for row in doc.get('items'): for row in doc.get('items'):
if row.get(apply_on) not in items: continue if (row.get(apply_on) or args.get(apply_on)) not in items: continue
if pr_doc.mixed_conditions: if pr_doc.mixed_conditions:
amt = args.get('qty') * args.get("price_list_rate") amt = args.get('qty') * args.get("price_list_rate")
@ -479,7 +479,7 @@ def apply_pricing_rule_on_transaction(doc):
doc.calculate_taxes_and_totals() doc.calculate_taxes_and_totals()
elif d.price_or_product_discount == 'Product': elif d.price_or_product_discount == 'Product':
item_details = frappe._dict({'parenttype': doc.doctype}) item_details = frappe._dict({'parenttype': doc.doctype, 'free_item_data': []})
get_product_discount_rule(d, item_details, doc=doc) get_product_discount_rule(d, item_details, doc=doc)
apply_pricing_rule_for_free_items(doc, item_details.free_item_data) apply_pricing_rule_for_free_items(doc, item_details.free_item_data)
doc.set_missing_values() doc.set_missing_values()
@ -508,9 +508,15 @@ def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None):
frappe.throw(_("Free item not set in the pricing rule {0}") frappe.throw(_("Free item not set in the pricing rule {0}")
.format(get_link_to_form("Pricing Rule", pricing_rule.name))) .format(get_link_to_form("Pricing Rule", pricing_rule.name)))
item_details.free_item_data = { qty = pricing_rule.free_qty or 1
if pricing_rule.is_recursive:
transaction_qty = args.get('qty') if args else doc.total_qty
if transaction_qty:
qty = flt(transaction_qty) * qty
free_item_data_args = {
'item_code': free_item, 'item_code': free_item,
'qty': pricing_rule.free_qty or 1, 'qty': qty,
'rate': pricing_rule.free_item_rate or 0, 'rate': pricing_rule.free_item_rate or 0,
'price_list_rate': pricing_rule.free_item_rate or 0, 'price_list_rate': pricing_rule.free_item_rate or 0,
'is_free_item': 1 'is_free_item': 1
@ -519,24 +525,26 @@ def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None):
item_data = frappe.get_cached_value('Item', free_item, ['item_name', item_data = frappe.get_cached_value('Item', free_item, ['item_name',
'description', 'stock_uom'], as_dict=1) 'description', 'stock_uom'], as_dict=1)
item_details.free_item_data.update(item_data) free_item_data_args.update(item_data)
item_details.free_item_data['uom'] = pricing_rule.free_item_uom or item_data.stock_uom free_item_data_args['uom'] = pricing_rule.free_item_uom or item_data.stock_uom
item_details.free_item_data['conversion_factor'] = get_conversion_factor(free_item, free_item_data_args['conversion_factor'] = get_conversion_factor(free_item,
item_details.free_item_data['uom']).get("conversion_factor", 1) free_item_data_args['uom']).get("conversion_factor", 1)
if item_details.get("parenttype") == 'Purchase Order': if item_details.get("parenttype") == 'Purchase Order':
item_details.free_item_data['schedule_date'] = doc.schedule_date if doc else today() free_item_data_args['schedule_date'] = doc.schedule_date if doc else today()
if item_details.get("parenttype") == 'Sales Order': if item_details.get("parenttype") == 'Sales Order':
item_details.free_item_data['delivery_date'] = doc.delivery_date if doc else today() free_item_data_args['delivery_date'] = doc.delivery_date if doc else today()
item_details.free_item_data.append(free_item_data_args)
def apply_pricing_rule_for_free_items(doc, pricing_rule_args, set_missing_values=False): def apply_pricing_rule_for_free_items(doc, pricing_rule_args, set_missing_values=False):
if pricing_rule_args.get('item_code'): if pricing_rule_args:
items = [d.item_code for d in doc.items items = tuple([d.item_code for d in doc.items if d.is_free_item])
if d.item_code == (pricing_rule_args.get("item_code")) and d.is_free_item]
if not items: for args in pricing_rule_args:
doc.append('items', pricing_rule_args) if not items or args.get('item_code') not in items:
doc.append('items', args)
def get_pricing_rule_items(pr_doc): def get_pricing_rule_items(pr_doc):
apply_on_data = [] apply_on_data = []

View File

@ -21,7 +21,7 @@ price_discount_fields = ['rate_or_discount', 'apply_discount_on', 'apply_discoun
'rate', 'discount_amount', 'discount_percentage', 'validate_applied_rule'] 'rate', 'discount_amount', 'discount_percentage', 'validate_applied_rule']
product_discount_fields = ['free_item', 'free_qty', 'free_item_uom', product_discount_fields = ['free_item', 'free_qty', 'free_item_uom',
'free_item_rate', 'same_item'] 'free_item_rate', 'same_item', 'is_recursive']
class PromotionalScheme(Document): class PromotionalScheme(Document):
def validate(self): def validate(self):

View File

@ -1,10 +1,12 @@
{ {
"actions": [],
"creation": "2019-03-24 14:48:59.649168", "creation": "2019-03-24 14:48:59.649168",
"doctype": "DocType", "doctype": "DocType",
"editable_grid": 1, "editable_grid": 1,
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [ "field_order": [
"disable", "disable",
"apply_multiple_pricing_rules",
"column_break_2", "column_break_2",
"rule_description", "rule_description",
"section_break_1", "section_break_1",
@ -25,7 +27,7 @@
"threshold_percentage", "threshold_percentage",
"column_break_15", "column_break_15",
"priority", "priority",
"apply_multiple_pricing_rules" "is_recursive"
], ],
"fields": [ "fields": [
{ {
@ -152,10 +154,19 @@
"fieldname": "apply_multiple_pricing_rules", "fieldname": "apply_multiple_pricing_rules",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Apply Multiple Pricing Rules" "label": "Apply Multiple Pricing Rules"
},
{
"default": "0",
"description": "Discounts to be applied in sequential ranges like buy 1 get 1, buy 2 get 2, buy 3 get 3 and so on",
"fieldname": "is_recursive",
"fieldtype": "Check",
"label": "Is Recursive"
} }
], ],
"index_web_pages_for_search": 1,
"istable": 1, "istable": 1,
"modified": "2019-07-21 00:00:56.674284", "links": [],
"modified": "2021-03-06 21:58:18.162346",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Promotional Scheme Product Discount", "name": "Promotional Scheme Product Discount",

View File

@ -111,7 +111,10 @@ class calculate_taxes_and_totals(object):
item.rate_with_margin, item.base_rate_with_margin = self.calculate_margin(item) item.rate_with_margin, item.base_rate_with_margin = self.calculate_margin(item)
if flt(item.rate_with_margin) > 0: if flt(item.rate_with_margin) > 0:
item.rate = flt(item.rate_with_margin * (1.0 - (item.discount_percentage / 100.0)), item.precision("rate")) item.rate = flt(item.rate_with_margin * (1.0 - (item.discount_percentage / 100.0)), item.precision("rate"))
item.discount_amount = item.rate_with_margin - item.rate if not item.discount_amount:
item.discount_amount = item.rate_with_margin - item.rate
elif not item.discount_percentage:
item.rate -= item.discount_amount
elif flt(item.price_list_rate) > 0: elif flt(item.price_list_rate) > 0:
item.discount_amount = item.price_list_rate - item.rate item.discount_amount = item.price_list_rate - item.rate
elif flt(item.price_list_rate) > 0 and not item.discount_amount: elif flt(item.price_list_rate) > 0 and not item.discount_amount:

View File

@ -576,7 +576,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
var d = locals[cdt][cdn]; var d = locals[cdt][cdn];
me.add_taxes_from_item_tax_template(d.item_tax_rate); me.add_taxes_from_item_tax_template(d.item_tax_rate);
if (d.free_item_data) { if (d.free_item_data) {
me.apply_product_discount(d.free_item_data); me.apply_product_discount(d);
} }
}, },
() => { () => {
@ -1163,7 +1163,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
calculate_stock_uom_rate: function(doc, cdt, cdn) { calculate_stock_uom_rate: function(doc, cdt, cdn) {
let item = frappe.get_doc(cdt, cdn); let item = frappe.get_doc(cdt, cdn);
item.stock_uom_rate = flt(item.rate)/flt(item.conversion_factor); item.stock_uom_rate = flt(item.rate)/flt(item.conversion_factor);
refresh_field("stock_uom_rate", item.name, item.parentfield); refresh_field("stock_uom_rate", item.name, item.parentfield);
}, },
service_stop_date: function(frm, cdt, cdn) { service_stop_date: function(frm, cdt, cdn) {
@ -1504,7 +1504,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
} }
if (d.free_item_data) { if (d.free_item_data) {
me.apply_product_discount(d.free_item_data); me.apply_product_discount(d);
} }
if (d.apply_rule_on_other_items) { if (d.apply_rule_on_other_items) {
@ -1538,20 +1538,30 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
} }
}, },
apply_product_discount: function(free_item_data) { apply_product_discount: function(args) {
const items = this.frm.doc.items.filter(d => (d.item_code == free_item_data.item_code const items = this.frm.doc.items.filter(d => (d.is_free_item)) || [];
&& d.is_free_item)) || [];
if (!items.length) { const exist_items = items.map(row => row.item_code);
let row_to_modify = frappe.model.add_child(this.frm.doc,
this.frm.doc.doctype + ' Item', 'items');
for (let key in free_item_data) { args.free_item_data.forEach(pr_row => {
row_to_modify[key] = free_item_data[key]; let row_to_modify = {};
if (!items || !in_list(exist_items, pr_row.item_code)) {
row_to_modify = frappe.model.add_child(this.frm.doc,
this.frm.doc.doctype + ' Item', 'items');
} else if(items) {
row_to_modify = items.filter(d => d.item_code === pr_row.item_code)[0];
} }
} if (items && items.length && free_item_data) {
items[0].qty = free_item_data.qty for (let key in pr_row) {
} row_to_modify[key] = pr_row[key];
}
});
// free_item_data is a temporary variable
args.free_item_data = '';
refresh_field('items');
}, },
apply_price_list: function(item, reset_plc_conversion) { apply_price_list: function(item, reset_plc_conversion) {