feat: recursive product discount
This commit is contained in:
parent
2c191c105f
commit
cd8422a840
@ -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",
|
||||||
|
@ -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')
|
||||||
|
@ -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 = []
|
||||||
|
@ -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):
|
||||||
|
@ -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",
|
||||||
|
@ -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:
|
||||||
|
@ -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) {
|
||||||
|
Loading…
Reference in New Issue
Block a user