[ Improv ] Loyalty Program more fixes and test case (#14888)

* remove console statements

* set account & cost center for redemption

* add test case for single/multi tier & is_return scenario

* reset tier when changing loyalty program

* make loyalty fields non copy type

* fix test case - delete si after every test to avoid interference
This commit is contained in:
Zarrar 2018-07-16 18:11:53 +05:30 committed by Nabin Hait
parent 6dfc165193
commit 26c9b94cc6
7 changed files with 297 additions and 20 deletions

View File

@ -5,6 +5,262 @@ from __future__ import unicode_literals
import frappe
import unittest
from frappe.utils import today, cint, flt, getdate
from erpnext.accounts.doctype.loyalty_program.loyalty_program import get_loyalty_program_details
class TestLoyaltyProgram(unittest.TestCase):
pass
@classmethod
def setUpClass(self):
# create relevant item, customer, loyalty program, etc
create_records()
def test_loyalty_points_earned_single_tier(self):
# create a new sales invoice
si_original = create_sales_invoice_record()
si_original.insert()
si_original.submit()
customer = frappe.get_doc('Customer', {"customer_name": "Test Loyalty Customer"})
earned_points = get_points_earned(si_original)
lpe = frappe.get_doc('Loyalty Point Entry', {'sales_invoice': si_original.name, 'customer': si_original.customer})
self.assertEqual(si_original.get('loyalty_program'), customer.loyalty_program)
self.assertEqual(lpe.get('loyalty_program_tier'), customer.loyalty_program_tier)
self.assertEqual(lpe.loyalty_points, earned_points)
# add redemption point
si_redeem = create_sales_invoice_record()
si_redeem.redeem_loyalty_points = 1
si_redeem.loyalty_points = earned_points
si_redeem.insert()
si_redeem.submit()
earned_after_redemption = get_points_earned(si_redeem)
lpe_redeem = frappe.get_doc('Loyalty Point Entry', {'sales_invoice': si_redeem.name, 'redeem_against': lpe.name})
lpe_earn = frappe.get_doc('Loyalty Point Entry', {'sales_invoice': si_redeem.name, 'name': ['!=', lpe_redeem.name]})
self.assertEqual(lpe_earn.loyalty_points, earned_after_redemption)
self.assertEqual(lpe_redeem.loyalty_points, (-1*earned_points))
# cancel and delete
for d in [si_redeem, si_original]:
d.cancel()
frappe.delete_doc('Sales Invoice', d.name)
def test_loyalty_points_earned_multiple_tier(self):
# assign multiple tier program to the customer
customer = frappe.get_doc('Customer', {"customer_name": "Test Loyalty Customer"})
customer.loyalty_program = frappe.get_doc('Loyalty Program', {'loyalty_program_name': 'Test Multiple Loyalty'}).name
customer.save()
# create a new sales invoice
si_original = create_sales_invoice_record()
si_original.insert()
si_original.submit()
earned_points = get_points_earned(si_original)
lpe = frappe.get_doc('Loyalty Point Entry', {'sales_invoice': si_original.name, 'customer': si_original.customer})
self.assertEqual(si_original.get('loyalty_program'), customer.loyalty_program)
self.assertEqual(lpe.get('loyalty_program_tier'), customer.loyalty_program_tier)
self.assertEqual(lpe.loyalty_points, earned_points)
# add redemption point
si_redeem = create_sales_invoice_record()
si_redeem.redeem_loyalty_points = 1
si_redeem.loyalty_points = earned_points
si_redeem.insert()
si_redeem.submit()
customer = frappe.get_doc('Customer', {"customer_name": "Test Loyalty Customer"})
earned_after_redemption = get_points_earned(si_redeem)
lpe_redeem = frappe.get_doc('Loyalty Point Entry', {'sales_invoice': si_redeem.name, 'redeem_against': lpe.name})
lpe_earn = frappe.get_doc('Loyalty Point Entry', {'sales_invoice': si_redeem.name, 'name': ['!=', lpe_redeem.name]})
self.assertEqual(lpe_earn.loyalty_points, earned_after_redemption)
self.assertEqual(lpe_redeem.loyalty_points, (-1*earned_points))
self.assertEqual(lpe_earn.loyalty_program_tier, customer.loyalty_program_tier)
# cancel and delete
for d in [si_redeem, si_original]:
d.cancel()
frappe.delete_doc('Sales Invoice', d.name)
def test_cancel_sales_invoice(self):
''' cancelling the sales invoice should cancel the earned points'''
# create a new sales invoice
si = create_sales_invoice_record()
si.insert()
si.submit()
lpe = frappe.get_doc('Loyalty Point Entry', {'sales_invoice': si.name, 'customer': si.customer})
self.assertEqual(True, not (lpe is None))
# cancelling sales invoice
si.cancel()
lpe = frappe.db.exists('Loyalty Point Entry', lpe.name)
self.assertEqual(True, (lpe is None))
def test_sales_invoice_return(self):
# create a new sales invoice
si_original = create_sales_invoice_record(2)
si_original.conversion_rate = flt(1)
si_original.insert()
si_original.submit()
earned_points = get_points_earned(si_original)
lpe_original = frappe.get_doc('Loyalty Point Entry', {'sales_invoice': si_original.name, 'customer': si_original.customer})
self.assertEqual(lpe_original.loyalty_points, earned_points)
# create sales invoice return
si_return = create_sales_invoice_record(-1)
si_return.conversion_rate = flt(1)
si_return.is_return = 1
si_return.return_against = si_original.name
si_return.insert()
si_return.submit()
# fetch original invoice again as its status would have been updated
si_original = frappe.get_doc('Sales Invoice', lpe_original.sales_invoice)
earned_points = get_points_earned(si_original)
lpe_after_return = frappe.get_doc('Loyalty Point Entry', {'sales_invoice': si_original.name, 'customer': si_original.customer})
self.assertEqual(lpe_after_return.loyalty_points, earned_points)
self.assertEqual(True, (lpe_original.loyalty_points > lpe_after_return.loyalty_points))
# cancel and delete
for d in [si_return, si_original]:
try:
d.cancel()
except frappe.TimestampMismatchError:
frappe.get_doc('Sales Invoice', d.name).cancel()
frappe.delete_doc('Sales Invoice', d.name)
def get_points_earned(self):
def get_returned_amount():
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 abs(flt(returned_amount[0][0])) if returned_amount else 0
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 = get_returned_amount()
eligible_amount = flt(self.grand_total) - cint(self.loyalty_amount) - returned_amount
points_earned = cint(eligible_amount/lp_details.collection_factor)
return points_earned or 0
def create_sales_invoice_record(qty=1):
# return sales invoice doc object
return frappe.get_doc({
"doctype": "Sales Invoice",
"customer": frappe.get_doc('Customer', {"customer_name": "Test Loyalty Customer"}).name,
"company": '_Test Company',
"due_date": today(),
"posting_date": today(),
"taxes_and_charges": "",
"debit_to": "Debtors - _TC",
"taxes": [],
"items": [{
'doctype': 'Sales Invoice Item',
'item_code': frappe.get_doc('Item', {'item_name': 'Loyal Item'}).name,
'qty': qty,
'income_account': 'Sales - _TC',
'cost_center': 'Main - _TC',
'expense_account': 'Cost of Goods Sold - _TC'
}]
})
def create_records():
# create a new loyalty Account
frappe.get_doc({
"doctype": "Account",
"account_name": "Loyalty",
"parent_account": "Direct Expenses - _TC",
"company": "_Test Company",
"is_group": 0,
"account_type": "Expense Account",
}).insert()
# create a new loyalty program Single tier
frappe.get_doc({
"doctype": "Loyalty Program",
"loyalty_program_name": "Test Single Loyalty",
"auto_opt_in": 1,
"from_date": today(),
"loyalty_program_type": "Single Tier Program",
"conversion_factor": 1,
"expiry_duration": 10,
"company": "_Test Company",
"cost_center": "Main - _TC",
"expense_account": "Loyalty - _TC",
"collection_rules": [{
'tier_name': 'Silver',
'collection_factor': 1000,
'min_spent': 1000
}]
}).insert()
# create a new customer
frappe.get_doc({
"customer_group": "_Test Customer Group",
"customer_name": "Test Loyalty Customer",
"customer_type": "Individual",
"doctype": "Customer",
"territory": "_Test Territory"
}).insert()
# create a new loyalty program Multiple tier
frappe.get_doc({
"doctype": "Loyalty Program",
"loyalty_program_name": "Test Multiple Loyalty",
"auto_opt_in": 1,
"from_date": today(),
"loyalty_program_type": "Multiple Tier Program",
"conversion_factor": 1,
"expiry_duration": 10,
"company": "_Test Company",
"cost_center": "Main - _TC",
"expense_account": "Loyalty - _TC",
"collection_rules": [
{
'tier_name': 'Silver',
'collection_factor': 1000,
'min_spent': 10000
},
{
'tier_name': 'Gold',
'collection_factor': 1000,
'min_spent': 19000
}
]
}).insert()
# create an item
item = frappe.get_doc({
"doctype": "Item",
"item_code": "Loyal Item",
"item_name": "Loyal Item",
"item_group": "All Item Groups",
"company": "_Test Company",
"is_stock_item": 1,
"opening_stock": 100,
"valuation_rate": 10000,
}).insert()
# create item price
frappe.get_doc({
"doctype": "Item Price",
"price_list": "Standard Selling",
"item_code": item.item_code,
"price_list_rate": 10000
}).insert()

View File

@ -36,7 +36,6 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
},
refresh: function(doc, dt, dn) {
console.log("triggered the SalesInvoiceController");
this._super();
if(cur_frm.msgbox && cur_frm.msgbox.$wrapper.is(":visible")) {
// hide new msgbox
@ -389,7 +388,6 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
},
loyalty_amount: function(){
console.log("triggered the loyalty amount");
this.calculate_outstanding_amount();
this.frm.refresh_field("outstanding_amount");
this.frm.refresh_field("paid_amount");

View File

@ -2476,7 +2476,7 @@
"in_standard_filter": 0,
"label": "Loyalty Points",
"length": 0,
"no_copy": 0,
"no_copy": 1,
"permlevel": 0,
"precision": "",
"print_hide": 1,
@ -2509,7 +2509,7 @@
"in_standard_filter": 0,
"label": "Loyalty Amount",
"length": 0,
"no_copy": 0,
"no_copy": 1,
"permlevel": 0,
"precision": "",
"print_hide": 1,
@ -2541,7 +2541,7 @@
"in_standard_filter": 0,
"label": "Redeem Loyalty Points",
"length": 0,
"no_copy": 0,
"no_copy": 1,
"permlevel": 0,
"precision": "",
"print_hide": 1,
@ -2606,7 +2606,7 @@
"in_standard_filter": 0,
"label": "Loyalty Program",
"length": 0,
"no_copy": 0,
"no_copy": 1,
"options": "Loyalty Program",
"permlevel": 0,
"precision": "",
@ -2640,7 +2640,7 @@
"in_standard_filter": 0,
"label": "Redemption Account",
"length": 0,
"no_copy": 0,
"no_copy": 1,
"options": "Account",
"permlevel": 0,
"precision": "",
@ -2674,7 +2674,7 @@
"in_standard_filter": 0,
"label": "Redemption Cost Center",
"length": 0,
"no_copy": 0,
"no_copy": 1,
"options": "Cost Center",
"permlevel": 0,
"precision": "",
@ -2735,8 +2735,8 @@
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Apply Additional Discount On",
@ -5382,7 +5382,7 @@
"istable": 0,
"max_attachments": 0,
"menu_index": 0,
"modified": "2018-07-10 19:28:04.351108",
"modified": "2018-07-12 13:16:20.918322",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",

View File

@ -95,6 +95,10 @@ class SalesInvoice(SellingController):
if self._action != 'submit' and self.update_stock and not self.is_return:
set_batch_nos(self, 'warehouse', True)
if self.redeem_loyalty_points:
lp = frappe.get_doc('Loyalty Program', self.loyalty_program)
self.loyalty_redemption_account = lp.expense_account if not self.loyalty_redemption_account else self.loyalty_redemption_account
self.loyalty_redemption_cost_center = lp.cost_center if not self.loyalty_redemption_cost_center else self.loyalty_redemption_cost_center
self.set_against_income_account()
self.validate_c_form()
@ -1015,7 +1019,7 @@ class SalesInvoice(SellingController):
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
return abs(flt(returned_amount[0][0])) if returned_amount else 0
# redeem the loyalty points.
def apply_loyalty_points(self):
@ -1026,7 +1030,7 @@ class SalesInvoice(SellingController):
points_to_redeem = self.loyalty_points
for lp_entry in loyalty_point_entries:
available_points = lp_entry.loyalty_points - redemption_details.get(lp_entry.name)
available_points = lp_entry.loyalty_points - flt(redemption_details.get(lp_entry.name))
if available_points > points_to_redeem:
redeemed_points = points_to_redeem
else:

View File

@ -77,6 +77,12 @@ frappe.ui.form.on("Customer", {
}
},
loyalty_program: function(frm) {
if(frm.doc.loyalty_program) {
frm.set_value('loyalty_program_tier', null);
}
},
refresh: function(frm) {
if(frappe.defaults.get_default("cust_master_name")!="Naming Series") {
frm.toggle_display("naming_series", false);

View File

@ -1493,10 +1493,12 @@
"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,
@ -1512,7 +1514,7 @@
"in_standard_filter": 0,
"label": "Loyalty Program",
"length": 0,
"no_copy": 0,
"no_copy": 1,
"options": "Loyalty Program",
"permlevel": 0,
"precision": "",
@ -1524,16 +1526,18 @@
"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": "loyalty_program_tier",
"fieldtype": "Read Only",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@ -1543,21 +1547,23 @@
"in_standard_filter": 0,
"label": "Loyalty Program Tier",
"length": 0,
"no_copy": 0,
"no_copy": 1,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"read_only": 1,
"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": 1,
@ -1768,7 +1774,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-06-27 12:12:30.677834",
"modified": "2018-07-12 13:20:13.203294",
"modified_by": "Administrator",
"module": "Selling",
"name": "Customer",
@ -1955,5 +1961,6 @@
"sort_order": "ASC",
"title_field": "customer_name",
"track_changes": 1,
"track_seen": 0
"track_seen": 0,
"track_views": 0
}

View File

@ -62,6 +62,12 @@ class Customer(TransactionBase):
self.set_loyalty_program()
self.check_customer_group_change()
# set loyalty program tier
if frappe.db.exists('Customer', self.name):
customer = frappe.get_doc('Customer', self.name)
if self.loyalty_program == customer.loyalty_program and not self.loyalty_program_tier:
self.loyalty_program_tier = customer.loyalty_program_tier
def check_customer_group_change(self):
frappe.flags.customer_group_changed = False