[ 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
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 37fce0b..f0dd887 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 @@
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 4536a7a..a553faa 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 593e426..96eda7f 100644
--- a/erpnext/accounts/doctype/loyalty_program/loyalty_program.py
+++ b/erpnext/accounts/doctype/loyalty_program/loyalty_program.py
@@ -16,14 +16,17 @@
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 @@
@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 @@
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 5f04f8b..d3efb70 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
@@ -230,6 +230,20 @@
}, 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 @@
});
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 @@
}
};
});
+
+ // 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 @@
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 @@
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 @@
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 55921c3..7a9e7d0 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
@@ -388,38 +388,6 @@
"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_break1",
"fieldtype": "Column Break",
"hidden": 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 92c6ec4..f645983 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -163,6 +163,7 @@
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 @@
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 @@
# 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 @@
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 @@
"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 @@
}, 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 1e63e59..595180b 100644
--- a/erpnext/selling/doctype/customer/customer.py
+++ b/erpnext/selling/doctype/customer/customer.py
@@ -6,7 +6,7 @@
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 @@
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 294b2ba..d75bf61 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 @@
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
},