[ 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
This commit is contained in:
Zarrar 2018-07-11 14:35:43 +05:30 committed by Nabin Hait
parent 9a3b785a03
commit 5899d98077
8 changed files with 341 additions and 232 deletions

View File

@ -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)))

View File

@ -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
}

View File

@ -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
return loyalty_amount

View File

@ -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();
}

View File

@ -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",

View File

@ -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

View File

@ -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":

View File

@ -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
},