From 5899d98077195ef61e9adc9736f4eaf79ab311f3 Mon Sep 17 00:00:00 2001 From: Zarrar Date: Wed, 11 Jul 2018 14:35:43 +0530 Subject: [PATCH] [ Major ] Loyalty Program Fixes (#14863) * fix checking of customer_group & territory of customer in loyalty program * fetch and set applicable loyalty program - in customer.py if found 1 program, set it or show a message to set it manually - in sales invoice, if found 1 program for selected customer with no program set, set it else open a dialog with applicable options * removed disabled field, added from_date & to_date * loyalty program section made collapsible, added redeem check in it * setting loyalty program improvised, manual selection if multiple found * get_query added, amount calculation updated * args passed rectified for expiry_date * get loyalty_points logic improv, redemption_details logic added * improv based on from/to date and other rectification * code rectified based on different scenarios - is_return, cancel, make loyalty points entry improv --- .../loyalty_point_entry.py | 27 +- .../loyalty_program/loyalty_program.json | 243 +++++++++--------- .../loyalty_program/loyalty_program.py | 44 ++-- .../doctype/sales_invoice/sales_invoice.js | 80 +++++- .../doctype/sales_invoice/sales_invoice.json | 76 +++--- .../doctype/sales_invoice/sales_invoice.py | 64 ++++- erpnext/selling/doctype/customer/customer.py | 37 ++- .../page/point_of_sale/point_of_sale.js | 2 +- 8 files changed, 341 insertions(+), 232 deletions(-) diff --git a/erpnext/accounts/doctype/loyalty_point_entry/loyalty_point_entry.py b/erpnext/accounts/doctype/loyalty_point_entry/loyalty_point_entry.py index 37fce0b856..f0dd887770 100644 --- a/erpnext/accounts/doctype/loyalty_point_entry/loyalty_point_entry.py +++ b/erpnext/accounts/doctype/loyalty_point_entry/loyalty_point_entry.py @@ -13,15 +13,22 @@ class LoyaltyPointEntry(Document): pass -def get_loyalty_point_entries(customer, loyalty_program, expiry_date=None, company=None): +def get_loyalty_point_entries(customer, loyalty_program, company, expiry_date=None): if not expiry_date: date = today() - args_list = [customer, loyalty_program, expiry_date] - condition = '' - if company: - condition = " and company=%s " - args_list.append(company) - loyalty_point_details = frappe.db.sql('''select name, loyalty_points, expiry_date, loyalty_program_tier - from `tabLoyalty Point Entry` where customer=%s and loyalty_program=%s and expiry_date>=%s and loyalty_points>0 - {condition} order by expiry_date'''.format(condition=condition), tuple(args_list), as_dict=1) - return loyalty_point_details + + return frappe.db.sql(''' + select name, loyalty_points, expiry_date, loyalty_program_tier + from `tabLoyalty Point Entry` + where customer=%s and loyalty_program=%s + and expiry_date>=%s and loyalty_points>0 and company=%s + order by expiry_date + ''', (customer, loyalty_program, expiry_date, company), as_dict=1) + +def get_redemption_details(customer, loyalty_program, company): + return frappe._dict(frappe.db.sql(''' + select redeem_against, sum(loyalty_points) + from `tabLoyalty Point Entry` + where customer=%s and loyalty_program=%s and loyalty_points<0 and company=%s + group by redeem_against + ''', (customer, loyalty_program, company))) diff --git a/erpnext/accounts/doctype/loyalty_program/loyalty_program.json b/erpnext/accounts/doctype/loyalty_program/loyalty_program.json index 4536a7a289..a553faa394 100644 --- a/erpnext/accounts/doctype/loyalty_program/loyalty_program.json +++ b/erpnext/accounts/doctype/loyalty_program/loyalty_program.json @@ -15,128 +15,7 @@ "fields": [ { "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "disabled", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Disabled", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_2", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "auto_opt_in", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Auto Opt In (For all customers)", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_2", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -164,10 +43,11 @@ "search_index": 0, "set_only_once": 0, "translatable": 0, - "unique": 0 + "unique": 1 }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -200,6 +80,72 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "description": "", + "fieldname": "from_date", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "From Date", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "to_date", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "To Date", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -230,6 +176,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -262,6 +209,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -294,6 +242,39 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "auto_opt_in", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Auto Opt In (For all customers)", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -325,6 +306,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -357,6 +339,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -388,6 +371,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -420,6 +404,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -451,6 +436,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -481,6 +467,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -513,6 +500,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -545,6 +533,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -577,6 +566,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -608,6 +598,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -648,7 +639,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-03-21 10:20:25.468206", + "modified": "2018-07-10 19:15:24.994385", "modified_by": "Administrator", "module": "Accounts", "name": "Loyalty Program", @@ -657,7 +648,6 @@ "permissions": [ { "amend": 0, - "apply_user_permissions": 0, "cancel": 0, "create": 1, "delete": 1, @@ -683,5 +673,6 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1, - "track_seen": 0 + "track_seen": 0, + "track_views": 0 } \ No newline at end of file diff --git a/erpnext/accounts/doctype/loyalty_program/loyalty_program.py b/erpnext/accounts/doctype/loyalty_program/loyalty_program.py index 593e426531..96eda7fce2 100644 --- a/erpnext/accounts/doctype/loyalty_program/loyalty_program.py +++ b/erpnext/accounts/doctype/loyalty_program/loyalty_program.py @@ -16,14 +16,17 @@ class LoyaltyProgram(Document): def get_loyalty_details(customer, loyalty_program, expiry_date=None, company=None): if not expiry_date: expiry_date = today() - args_list = [customer, loyalty_program, expiry_date] + + args_list = [customer, loyalty_program, expiry_date, expiry_date] condition = '' if company: condition = " and company=%s " args_list.append(company) loyalty_point_details = frappe.db.sql('''select sum(loyalty_points) as loyalty_points, sum(purchase_amount) as total_spent from `tabLoyalty Point Entry` - where customer=%s and loyalty_program=%s and (expiry_date>=%s) {condition} + where customer=%s and loyalty_program=%s + and expiry_date>=%s and posting_date <= %s + {condition} group by customer'''.format(condition=condition), tuple(args_list), as_dict=1) if loyalty_point_details: return loyalty_point_details[0] @@ -33,35 +36,28 @@ def get_loyalty_details(customer, loyalty_program, expiry_date=None, company=Non @frappe.whitelist() def get_loyalty_program_details(customer, loyalty_program=None, expiry_date=None, company=None, silent=False): lp_details = frappe._dict() - customer_loyalty_program = frappe.db.get_value("Customer", customer, "loyalty_program") - - if not (customer_loyalty_program or silent): - frappe.throw(_("Customer isn't enrolled in any Loyalty Program")) - elif silent and not customer_loyalty_program: - return frappe._dict({"loyalty_program": None}) - - if loyalty_program and loyalty_program != customer_loyalty_program: - frappe.throw(_("Customer isn't enrolled in this Loyalty Program")) if not loyalty_program: - loyalty_program = customer_loyalty_program + loyalty_program = frappe.db.get_value("Customer", customer, "loyalty_program") + + if not (loyalty_program or silent): + frappe.throw(_("Customer isn't enrolled in any Loyalty Program")) + elif silent and not loyalty_program: + return frappe._dict({"loyalty_program": None}) + if not company: company = frappe.db.get_default("company") or frappe.get_all("Company")[0].name - lp_details.update(get_loyalty_details(customer, loyalty_program, expiry_date, company)) + loyalty_program = frappe.get_doc("Loyalty Program", loyalty_program) + lp_details.update({"loyalty_program": loyalty_program.name}) + lp_details.update(loyalty_program.as_dict()) - lp_details.update({"loyalty_program": loyalty_program}) - loyalty_program = frappe.get_doc("Loyalty Program", lp_details.loyalty_program) + lp_details.update(get_loyalty_details(customer, loyalty_program.name, expiry_date, company)) - lp_details.expiry_duration = loyalty_program.expiry_duration - lp_details.conversion_factor = loyalty_program.conversion_factor - lp_details.expense_account = loyalty_program.expense_account - lp_details.cost_center = loyalty_program.cost_center - lp_details.company = loyalty_program.company - - tier_spent_level = sorted([d.as_dict() for d in loyalty_program.collection_rules], key=lambda rule:rule.min_spent, reverse=True) + tier_spent_level = sorted([d.as_dict() for d in loyalty_program.collection_rules], + key=lambda rule:rule.min_spent, reverse=True) for i, d in enumerate(tier_spent_level): - if i==0 or lp_details.total_spent < d.min_spent: + if i == 0 or lp_details.total_spent < d.min_spent: lp_details.tier_name = d.tier_name lp_details.collection_factor = d.collection_factor else: @@ -122,4 +118,4 @@ def validate_loyalty_points(ref_doc, points_to_redeem): ref_doc.loyalty_redemption_cost_center = loyalty_program_details.cost_center elif ref_doc.doctype == "Sales Order": - return loyalty_amount \ No newline at end of file + return loyalty_amount diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 5f04f8b4bd..d3efb7021c 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -230,6 +230,20 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte }, function() { me.apply_pricing_rule(); }); + + if(this.frm.doc.customer) { + frappe.call({ + "method": "erpnext.accounts.doctype.sales_invoice.sales_invoice.get_loyalty_programs", + "args": { + "customer": this.frm.doc.customer + }, + callback: function(r) { + if(r.message) { + select_loyalty_program(me.frm, r.message); + } + } + }); + } }, make_inter_company_invoice: function() { @@ -530,10 +544,6 @@ cur_frm.set_query("asset", "items", function(doc, cdt, cdn) { }); frappe.ui.form.on('Sales Invoice', { - refresh: function(frm) { - frm.add_fetch('customer', 'loyalty_program', 'loyalty_program'); - }, - setup: function(frm){ frm.custom_make_buttons = { @@ -598,6 +608,24 @@ frappe.ui.form.on('Sales Invoice', { } }; }); + + // set get_query for loyalty redemption account + frm.fields_dict["loyalty_redemption_account"].get_query = function() { + return { + filters:{ + "company": frm.doc.company + } + } + }; + + // set get_query for loyalty redemption cost center + frm.fields_dict["loyalty_redemption_cost_center"].get_query = function() { + return { + filters:{ + "company": frm.doc.company + } + } + }; }, //When multiple companies are set up. in case company name is changed set default company address company:function(frm){ @@ -661,18 +689,15 @@ frappe.ui.form.on('Sales Invoice', { method: "erpnext.accounts.doctype.loyalty_program.loyalty_program.get_loyalty_program_details", args: { "customer": frm.doc.customer, - "till_date": frm.doc.posting_date, + "loyalty_program": frm.doc.loyalty_program, + "expiry_date": frm.doc.posting_date, "company": frm.doc.company }, callback: function(r) { if (r) { - frm.set_value("loyalty_program", r.message.loyalty_program); frm.set_value("loyalty_redemption_account", r.message.expense_account); frm.set_value("loyalty_redemption_cost_center", r.message.cost_center); frm.redemption_conversion_factor = r.message.conversion_factor; - // let max_loyalty_points = parseInt((frm.doc.grand_total-frm.doc.total_advance)/r.message.conversion_factor); - // let redeemable_points = max_loyalty_points > r.message.loyalty_points ? r.message.loyalty_points : max_loyalty_points; - // frm.set_value("loyalty_points", redeemable_points); } } }); @@ -682,10 +707,10 @@ frappe.ui.form.on('Sales Invoice', { set_loyalty_points: function(frm) { if (frm.redemption_conversion_factor) { let loyalty_amount = flt(frm.redemption_conversion_factor*flt(frm.doc.loyalty_points), precision("loyalty_amount")); - var remaining_amount = flt(frm.doc.grand_total - frm.doc.total_advance) + var remaining_amount = flt(frm.doc.grand_total) - flt(frm.doc.total_advance) - flt(frm.doc.write_off_amount); if (frm.doc.grand_total && (remaining_amount < loyalty_amount)) { - let redeemable_amount = parseInt(remaining_amount/frm.redemption_conversion_factor); - frappe.throw(__("You can only redeem max {0} points in this order.",[redeemable_amount])); + let redeemable_points = parseInt(remaining_amount/frm.redemption_conversion_factor); + frappe.throw(__("You can only redeem max {0} points in this order.",[redeemable_points])); } frm.set_value("loyalty_amount", loyalty_amount); } @@ -729,3 +754,34 @@ var calculate_total_billing_amount = function(frm) { refresh_field('total_billing_amount') } + +var select_loyalty_program = function(frm, loyalty_programs) { + var dialog = new frappe.ui.Dialog({ + title: __("Select Loyalty Program"), + fields: [ + { + "label": __("Loyalty Program"), + "fieldname": "loyalty_program", + "fieldtype": "Select", + "options": loyalty_programs, + "default": loyalty_programs[0] + } + ] + }); + + dialog.set_primary_action(__("Set"), function() { + dialog.hide(); + return frappe.call({ + method: "frappe.client.set_value", + args: { + doctype: "Customer", + name: frm.doc.customer, + fieldname: "loyalty_program", + value: dialog.get_value("loyalty_program"), + }, + callback: function(r) { } + }); + }); + + dialog.show(); +} diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index 55921c3ada..7a9e7d0084 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -381,38 +381,6 @@ "translatable": 0, "unique": 0 }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "redeem_loyalty_points", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Redeem Loyalty Points", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -2460,10 +2428,10 @@ "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, - "collapsible": 0, + "collapsible": 1, "collapsible_depends_on": "", "columns": 0, - "depends_on": "redeem_loyalty_points", + "depends_on": "", "fieldname": "loyalty_points_redemption", "fieldtype": "Section Break", "hidden": 0, @@ -2496,6 +2464,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "depends_on": "redeem_loyalty_points", "fieldname": "loyalty_points", "fieldtype": "Int", "hidden": 0, @@ -2528,6 +2497,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "depends_on": "redeem_loyalty_points", "fieldname": "loyalty_amount", "fieldtype": "Currency", "hidden": 0, @@ -2560,6 +2530,38 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fieldname": "redeem_loyalty_points", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Redeem Loyalty Points", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 1, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, "fieldname": "column_break_77", "fieldtype": "Column Break", "hidden": 0, @@ -2591,6 +2593,8 @@ "bold": 0, "collapsible": 0, "columns": 0, + "depends_on": "", + "fetch_from": "customer.loyalty_program", "fieldname": "loyalty_program", "fieldtype": "Link", "hidden": 0, @@ -2624,6 +2628,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "depends_on": "redeem_loyalty_points", "fieldname": "loyalty_redemption_account", "fieldtype": "Link", "hidden": 0, @@ -2657,6 +2662,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "depends_on": "redeem_loyalty_points", "fieldname": "loyalty_redemption_cost_center", "fieldtype": "Link", "hidden": 0, @@ -5376,7 +5382,7 @@ "istable": 0, "max_attachments": 0, "menu_index": 0, - "modified": "2018-07-06 18:15:09.600814", + "modified": "2018-07-10 19:28:04.351108", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 92c6ec4837..f6459837ad 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -163,6 +163,7 @@ class SalesInvoice(SellingController): elif self.is_return and self.return_against and self.loyalty_program: against_si_doc = frappe.get_doc("Sales Invoice", self.return_against) against_si_doc.delete_loyalty_point_entry() + against_si_doc.make_loyalty_point_entry() if self.redeem_loyalty_points and self.loyalty_points: self.apply_loyalty_points() @@ -209,6 +210,7 @@ class SalesInvoice(SellingController): self.delete_loyalty_point_entry() elif self.is_return and self.return_against and self.loyalty_program: against_si_doc = frappe.get_doc("Sales Invoice", self.return_against) + against_si_doc.delete_loyalty_point_entry() against_si_doc.make_loyalty_point_entry() unlink_inter_company_invoice(self.doctype, self.name, self.inter_company_invoice_reference) @@ -967,24 +969,28 @@ class SalesInvoice(SellingController): # collection of the loyalty points, create the ledger entry for that. def make_loyalty_point_entry(self): - loyalty_program_details = get_loyalty_program_details(self.customer, company=self.company) - if loyalty_program_details: - points_earned = int(self.grand_total/loyalty_program_details.collection_factor) + lp_details = get_loyalty_program_details(self.customer, company=self.company, + loyalty_program=self.loyalty_program, expiry_date=self.posting_date) + if lp_details and getdate(lp_details.from_date) <= getdate(self.posting_date) and \ + (not lp_details.to_date or getdate(lp_details.to_date) >= getdate(self.posting_date)): + returned_amount = self.get_returned_amount() + eligible_amount = flt(self.grand_total) - cint(self.loyalty_amount) - returned_amount + points_earned = cint(eligible_amount/lp_details.collection_factor) doc = frappe.get_doc({ "doctype": "Loyalty Point Entry", "company": self.company, - "loyalty_program": loyalty_program_details.loyalty_program, - "loyalty_program_tier": loyalty_program_details.tier_name, + "loyalty_program": lp_details.loyalty_program, + "loyalty_program_tier": lp_details.tier_name, "customer": self.customer, "sales_invoice": self.name, "loyalty_points": points_earned, - "purchase_amount": self.grand_total, - "expiry_date": add_days(self.posting_date, loyalty_program_details.expiry_duration), + "purchase_amount": eligible_amount, + "expiry_date": add_days(self.posting_date, lp_details.expiry_duration), "posting_date": self.posting_date }) doc.flags.ignore_permissions = 1 doc.save() - # frappe.db.set_value("Customer", self.customer, "loyalty_program_tier", loyalty_program_details.tier_name) + frappe.db.set_value("Customer", self.customer, "loyalty_program_tier", lp_details.tier_name) # valdite the redemption and then delete the loyalty points earned on cancel of the invoice def delete_loyalty_point_entry(self): @@ -998,19 +1004,33 @@ class SalesInvoice(SellingController): First cancel the Sales Invoice No {0}''').format(invoice_list)) else: frappe.db.sql('''delete from `tabLoyalty Point Entry` where sales_invoice=%s''', (self.name)) + # Set loyalty program + lp_details = get_loyalty_program_details(self.customer, company=self.company, + loyalty_program=self.loyalty_program, expiry_date=self.posting_date) + frappe.db.set_value("Customer", self.customer, "loyalty_program_tier", lp_details.tier_name) + + def get_returned_amount(self): + returned_amount = frappe.db.sql(""" + select sum(grand_total) + from `tabSales Invoice` + where docstatus=1 and is_return=1 and ifnull(return_against, '')=%s + """, self.name) + return flt(returned_amount[0][0]) if returned_amount else 0 # redeem the loyalty points. def apply_loyalty_points(self): from erpnext.accounts.doctype.loyalty_point_entry.loyalty_point_entry \ - import get_loyalty_point_entries - loyalty_point_entries = get_loyalty_point_entries(self.customer, self.loyalty_program, self.posting_date, self.company) + import get_loyalty_point_entries, get_redemption_details + loyalty_point_entries = get_loyalty_point_entries(self.customer, self.loyalty_program, self.company, self.posting_date) + redemption_details = get_redemption_details(self.customer, self.loyalty_program, self.company) - points_to_redeem = self.loyalty_amount + points_to_redeem = self.loyalty_points for lp_entry in loyalty_point_entries: - if lp_entry.loyalty_points > points_to_redeem: + available_points = lp_entry.loyalty_points - redemption_details.get(lp_entry.name) + if available_points > points_to_redeem: redeemed_points = points_to_redeem else: - redeemed_points = lp_entry.loyalty_points + redeemed_points = available_points doc = frappe.get_doc({ "doctype": "Loyalty Point Entry", "company": self.company, @@ -1019,7 +1039,7 @@ class SalesInvoice(SellingController): "customer": self.customer, "sales_invoice": self.name, "redeem_against": lp_entry.name, - "loyalty_points": -(redeemed_points), + "loyalty_points": -1*redeemed_points, "purchase_amount": self.grand_total, "expiry_date": lp_entry.expiry_date, "posting_date": self.posting_date @@ -1312,3 +1332,19 @@ def make_inter_company_invoice(doctype, source_name, target_doc=None): }, target_doc, set_missing_values) return doclist + +@frappe.whitelist() +def get_loyalty_programs(customer): + ''' sets applicable loyalty program to the customer or returns a list of applicable programs ''' + from erpnext.selling.doctype.customer.customer import get_loyalty_programs + + customer = frappe.get_doc('Customer', customer) + if customer.loyalty_program: return + + lp_details = get_loyalty_programs(customer) + + if len(lp_details) == 1: + frappe.db.set(customer, 'loyalty_program', lp_details[0]) + return [] + else: + return lp_details diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index 1e63e592b1..595180b6a3 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -6,7 +6,7 @@ import frappe from frappe.model.naming import set_name_by_naming_series from frappe import _, msgprint, throw import frappe.defaults -from frappe.utils import flt, cint, cstr +from frappe.utils import flt, cint, cstr, today from frappe.desk.reportview import build_match_conditions, get_filters_cond from erpnext.utilities.transaction_base import TransactionBase from erpnext.accounts.party import validate_party_accounts, get_dashboard_info, get_timeline_data # keep this @@ -188,16 +188,33 @@ class Customer(TransactionBase): frappe.db.set(self, "customer_name", newdn) def set_loyalty_program(self): - if not self.loyalty_program: - loyalty_programs = frappe.get_all("Loyalty Program", fields=["name", "customer_group", - "customer_territory"], filters={"auto_opt_in": 1, "disabled": 0}) - from frappe.desk.treeview import get_children - for loyalty_program in loyalty_programs: - customer_groups = get_children("Customer Group", loyalty_program.customer_group, ) - if self.customer_group in customer_groups and\ - self.territory in get_children("Territory", loyalty_program.customer_territory): - self.loyalty_program = loyalty_program.name + if self.loyalty_program: return + loyalty_program = get_loyalty_programs(self) + if not loyalty_program: return + if len(loyalty_program) == 1: + self.loyalty_program = loyalty_program[0] + else: + frappe.msgprint(_("Multiple Loyalty Program found for the Customer. Please select manually.")) +@frappe.whitelist() +def get_loyalty_programs(doc): + ''' returns applicable loyalty programs for a customer ''' + from frappe.desk.treeview import get_children + + lp_details = [] + loyalty_programs = frappe.get_all("Loyalty Program", + fields=["name", "customer_group", "customer_territory"], + filters={"auto_opt_in": 1, "from_date": ["<=", today()], + "ifnull(to_date, '2500-01-01')": [">=", today()]}) + + for loyalty_program in loyalty_programs: + customer_groups = [d.value for d in get_children("Customer Group", loyalty_program.customer_group)] + customer_territories = [d.value for d in get_children("Territory", loyalty_program.customer_territory)] + if (not loyalty_program.customer_group or doc.customer_group in customer_groups)\ + and (not loyalty_program.customer_territory or doc.territory in customer_territories): + lp_details.append(loyalty_program.name) + + return lp_details def get_customer_list(doctype, txt, searchfield, start, page_len, filters=None): if frappe.db.get_default("cust_master_name") == "Customer Name": diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.js b/erpnext/selling/page/point_of_sale/point_of_sale.js index 294b2bad7c..d75bf619c7 100644 --- a/erpnext/selling/page/point_of_sale/point_of_sale.js +++ b/erpnext/selling/page/point_of_sale/point_of_sale.js @@ -129,7 +129,7 @@ erpnext.pos.PointOfSale = class PointOfSale { method: "erpnext.accounts.doctype.loyalty_program.loyalty_program.get_loyalty_program_details", args: { "customer": me.frm.doc.customer, - "till_date": me.frm.doc.posting_date, + "expiry_date": me.frm.doc.posting_date, "company": me.frm.doc.company, "silent": true },