mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-01 19:29:10 +00:00
Merge pull request #41564 from blaggacao/fix/loyalty
fix: tiered loyalty program
This commit is contained in:
@@ -36,7 +36,17 @@ class LoyaltyProgram(Document):
|
|||||||
to_date: DF.Date | None
|
to_date: DF.Date | None
|
||||||
# end: auto-generated types
|
# end: auto-generated types
|
||||||
|
|
||||||
pass
|
def validate(self):
|
||||||
|
self.validate_lowest_tier()
|
||||||
|
|
||||||
|
def validate_lowest_tier(self):
|
||||||
|
tiers = sorted(self.collection_rules, key=lambda x: x.min_spent)
|
||||||
|
if tiers and tiers[0].min_spent != 0:
|
||||||
|
frappe.throw(
|
||||||
|
_(
|
||||||
|
"The lowest tier must have a minimum spent amount of 0. Customers need to be part of a tier as soon as they are enrolled in the program."
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_loyalty_details(
|
def get_loyalty_details(
|
||||||
@@ -79,17 +89,17 @@ def get_loyalty_program_details_with_points(
|
|||||||
):
|
):
|
||||||
lp_details = get_loyalty_program_details(customer, loyalty_program, company=company, silent=silent)
|
lp_details = get_loyalty_program_details(customer, loyalty_program, company=company, silent=silent)
|
||||||
loyalty_program = frappe.get_doc("Loyalty Program", loyalty_program)
|
loyalty_program = frappe.get_doc("Loyalty Program", loyalty_program)
|
||||||
lp_details.update(
|
loyalty_details = get_loyalty_details(
|
||||||
get_loyalty_details(customer, loyalty_program.name, expiry_date, company, include_expired_entry)
|
customer, loyalty_program.name, expiry_date, company, include_expired_entry
|
||||||
)
|
)
|
||||||
|
lp_details.update(loyalty_details)
|
||||||
|
|
||||||
tier_spent_level = sorted(
|
tier_spent_level = sorted(
|
||||||
[d.as_dict() for d in loyalty_program.collection_rules],
|
[d.as_dict() for d in loyalty_program.collection_rules],
|
||||||
key=lambda rule: rule.min_spent,
|
key=lambda rule: rule.min_spent,
|
||||||
reverse=True,
|
|
||||||
)
|
)
|
||||||
for i, d in enumerate(tier_spent_level):
|
for i, d in enumerate(tier_spent_level):
|
||||||
if i == 0 or (lp_details.total_spent + current_transaction_amount) <= d.min_spent:
|
if i == 0 or (lp_details.total_spent + current_transaction_amount) >= d.min_spent:
|
||||||
lp_details.tier_name = d.tier_name
|
lp_details.tier_name = d.tier_name
|
||||||
lp_details.collection_factor = d.collection_factor
|
lp_details.collection_factor = d.collection_factor
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import frappe
|
|||||||
from frappe.utils import cint, flt, getdate, today
|
from frappe.utils import cint, flt, getdate, today
|
||||||
|
|
||||||
from erpnext.accounts.doctype.loyalty_program.loyalty_program import (
|
from erpnext.accounts.doctype.loyalty_program.loyalty_program import (
|
||||||
|
get_loyalty_details,
|
||||||
get_loyalty_program_details_with_points,
|
get_loyalty_program_details_with_points,
|
||||||
)
|
)
|
||||||
from erpnext.accounts.party import get_dashboard_info
|
from erpnext.accounts.party import get_dashboard_info
|
||||||
@@ -38,6 +39,7 @@ class TestLoyaltyProgram(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(si_original.get("loyalty_program"), customer.loyalty_program)
|
self.assertEqual(si_original.get("loyalty_program"), customer.loyalty_program)
|
||||||
|
self.assertEqual(lpe.get("loyalty_program_tier"), "Bronce") # is always in the first tier
|
||||||
self.assertEqual(lpe.get("loyalty_program_tier"), customer.loyalty_program_tier)
|
self.assertEqual(lpe.get("loyalty_program_tier"), customer.loyalty_program_tier)
|
||||||
self.assertEqual(lpe.loyalty_points, earned_points)
|
self.assertEqual(lpe.loyalty_points, earned_points)
|
||||||
|
|
||||||
@@ -79,6 +81,7 @@ class TestLoyaltyProgram(unittest.TestCase):
|
|||||||
si_original = create_sales_invoice_record()
|
si_original = create_sales_invoice_record()
|
||||||
si_original.insert()
|
si_original.insert()
|
||||||
si_original.submit()
|
si_original.submit()
|
||||||
|
customer.reload()
|
||||||
|
|
||||||
earned_points = get_points_earned(si_original)
|
earned_points = get_points_earned(si_original)
|
||||||
|
|
||||||
@@ -101,8 +104,8 @@ class TestLoyaltyProgram(unittest.TestCase):
|
|||||||
si_redeem.loyalty_points = earned_points
|
si_redeem.loyalty_points = earned_points
|
||||||
si_redeem.insert()
|
si_redeem.insert()
|
||||||
si_redeem.submit()
|
si_redeem.submit()
|
||||||
|
customer.reload()
|
||||||
|
|
||||||
customer = frappe.get_doc("Customer", {"customer_name": "Test Loyalty Customer"})
|
|
||||||
earned_after_redemption = get_points_earned(si_redeem)
|
earned_after_redemption = get_points_earned(si_redeem)
|
||||||
|
|
||||||
lpe_redeem = frappe.get_doc(
|
lpe_redeem = frappe.get_doc(
|
||||||
@@ -197,6 +200,70 @@ class TestLoyaltyProgram(unittest.TestCase):
|
|||||||
for d in company_wise_info:
|
for d in company_wise_info:
|
||||||
self.assertTrue(d.get("loyalty_points"))
|
self.assertTrue(d.get("loyalty_points"))
|
||||||
|
|
||||||
|
@unittest.mock.patch("erpnext.accounts.doctype.loyalty_program.loyalty_program.get_loyalty_details")
|
||||||
|
def test_tier_selection(self, mock_get_loyalty_details):
|
||||||
|
# Create a new loyalty program with multiple tiers
|
||||||
|
loyalty_program = frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "Loyalty Program",
|
||||||
|
"loyalty_program_name": "Test Tier Selection",
|
||||||
|
"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": "Gold", "collection_factor": 1000, "min_spent": 20000},
|
||||||
|
{"tier_name": "Silver", "collection_factor": 1000, "min_spent": 10000},
|
||||||
|
{"tier_name": "Bronze", "collection_factor": 1000, "min_spent": 0},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
loyalty_program.insert()
|
||||||
|
|
||||||
|
# Test cases with different total_spent and current_transaction_amount combinations
|
||||||
|
test_cases = [
|
||||||
|
(0, 6000, "Bronze"),
|
||||||
|
(0, 15000, "Silver"),
|
||||||
|
(0, 25000, "Gold"),
|
||||||
|
(4000, 500, "Bronze"),
|
||||||
|
(8000, 3000, "Silver"),
|
||||||
|
(18000, 3000, "Gold"),
|
||||||
|
(22000, 5000, "Gold"),
|
||||||
|
]
|
||||||
|
|
||||||
|
for total_spent, current_transaction_amount, expected_tier in test_cases:
|
||||||
|
with self.subTest(total_spent=total_spent, current_transaction_amount=current_transaction_amount):
|
||||||
|
# Mock the get_loyalty_details function to update the total_spent
|
||||||
|
def side_effect(*args, **kwargs):
|
||||||
|
result = get_loyalty_details(*args, **kwargs)
|
||||||
|
result.update({"total_spent": total_spent})
|
||||||
|
return result
|
||||||
|
|
||||||
|
mock_get_loyalty_details.side_effect = side_effect
|
||||||
|
|
||||||
|
lp_details = get_loyalty_program_details_with_points(
|
||||||
|
"Test Loyalty Customer",
|
||||||
|
loyalty_program=loyalty_program.name,
|
||||||
|
company="_Test Company",
|
||||||
|
current_transaction_amount=current_transaction_amount,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Get the selected tier based on the current implementation
|
||||||
|
selected_tier = lp_details.tier_name
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
selected_tier,
|
||||||
|
expected_tier,
|
||||||
|
f"Expected tier {expected_tier} for total_spent {total_spent} and current_transaction_amount {current_transaction_amount}, but got {selected_tier}",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Clean up
|
||||||
|
loyalty_program.delete()
|
||||||
|
|
||||||
|
|
||||||
def get_points_earned(self):
|
def get_points_earned(self):
|
||||||
def get_returned_amount():
|
def get_returned_amount():
|
||||||
@@ -285,7 +352,7 @@ def create_records():
|
|||||||
"company": "_Test Company",
|
"company": "_Test Company",
|
||||||
"cost_center": "Main - _TC",
|
"cost_center": "Main - _TC",
|
||||||
"expense_account": "Loyalty - _TC",
|
"expense_account": "Loyalty - _TC",
|
||||||
"collection_rules": [{"tier_name": "Silver", "collection_factor": 1000, "min_spent": 1000}],
|
"collection_rules": [{"tier_name": "Bronce", "collection_factor": 1000, "min_spent": 0}],
|
||||||
}
|
}
|
||||||
).insert()
|
).insert()
|
||||||
|
|
||||||
@@ -316,6 +383,7 @@ def create_records():
|
|||||||
"cost_center": "Main - _TC",
|
"cost_center": "Main - _TC",
|
||||||
"expense_account": "Loyalty - _TC",
|
"expense_account": "Loyalty - _TC",
|
||||||
"collection_rules": [
|
"collection_rules": [
|
||||||
|
{"tier_name": "Bronze", "collection_factor": 1000, "min_spent": 0},
|
||||||
{"tier_name": "Silver", "collection_factor": 1000, "min_spent": 10000},
|
{"tier_name": "Silver", "collection_factor": 1000, "min_spent": 10000},
|
||||||
{"tier_name": "Gold", "collection_factor": 1000, "min_spent": 19000},
|
{"tier_name": "Gold", "collection_factor": 1000, "min_spent": 19000},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "min_spent",
|
"fieldname": "min_spent",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
|
"in_list_view": 1,
|
||||||
"label": "Minimum Total Spent"
|
"label": "Minimum Total Spent"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -37,7 +38,7 @@
|
|||||||
],
|
],
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-03-27 13:10:03.536071",
|
"modified": "2024-09-05 07:41:25.694041",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Loyalty Program Collection",
|
"name": "Loyalty Program Collection",
|
||||||
|
|||||||
@@ -1776,7 +1776,8 @@ class SalesInvoice(SellingController):
|
|||||||
loyalty_program=self.loyalty_program,
|
loyalty_program=self.loyalty_program,
|
||||||
include_expired_entry=True,
|
include_expired_entry=True,
|
||||||
)
|
)
|
||||||
frappe.db.set_value("Customer", self.customer, "loyalty_program_tier", lp_details.tier_name)
|
customer = frappe.get_doc("Customer", self.customer)
|
||||||
|
customer.db_set("loyalty_program_tier", lp_details.tier_name)
|
||||||
|
|
||||||
def get_returned_amount(self):
|
def get_returned_amount(self):
|
||||||
from frappe.query_builder.functions import Sum
|
from frappe.query_builder.functions import Sum
|
||||||
|
|||||||
Reference in New Issue
Block a user