mirror of
https://github.com/frappe/erpnext.git
synced 2026-04-29 19:48:27 +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
|
||||
# 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(
|
||||
@@ -79,17 +89,17 @@ def get_loyalty_program_details_with_points(
|
||||
):
|
||||
lp_details = get_loyalty_program_details(customer, loyalty_program, company=company, silent=silent)
|
||||
loyalty_program = frappe.get_doc("Loyalty Program", loyalty_program)
|
||||
lp_details.update(
|
||||
get_loyalty_details(customer, loyalty_program.name, expiry_date, company, include_expired_entry)
|
||||
loyalty_details = get_loyalty_details(
|
||||
customer, loyalty_program.name, expiry_date, company, include_expired_entry
|
||||
)
|
||||
lp_details.update(loyalty_details)
|
||||
|
||||
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 + 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.collection_factor = d.collection_factor
|
||||
else:
|
||||
|
||||
@@ -7,6 +7,7 @@ import frappe
|
||||
from frappe.utils import cint, flt, getdate, today
|
||||
|
||||
from erpnext.accounts.doctype.loyalty_program.loyalty_program import (
|
||||
get_loyalty_details,
|
||||
get_loyalty_program_details_with_points,
|
||||
)
|
||||
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(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.loyalty_points, earned_points)
|
||||
|
||||
@@ -79,6 +81,7 @@ class TestLoyaltyProgram(unittest.TestCase):
|
||||
si_original = create_sales_invoice_record()
|
||||
si_original.insert()
|
||||
si_original.submit()
|
||||
customer.reload()
|
||||
|
||||
earned_points = get_points_earned(si_original)
|
||||
|
||||
@@ -101,8 +104,8 @@ class TestLoyaltyProgram(unittest.TestCase):
|
||||
si_redeem.loyalty_points = earned_points
|
||||
si_redeem.insert()
|
||||
si_redeem.submit()
|
||||
customer.reload()
|
||||
|
||||
customer = frappe.get_doc("Customer", {"customer_name": "Test Loyalty Customer"})
|
||||
earned_after_redemption = get_points_earned(si_redeem)
|
||||
|
||||
lpe_redeem = frappe.get_doc(
|
||||
@@ -197,6 +200,70 @@ class TestLoyaltyProgram(unittest.TestCase):
|
||||
for d in company_wise_info:
|
||||
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_returned_amount():
|
||||
@@ -285,7 +352,7 @@ def create_records():
|
||||
"company": "_Test Company",
|
||||
"cost_center": "Main - _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()
|
||||
|
||||
@@ -316,6 +383,7 @@ def create_records():
|
||||
"cost_center": "Main - _TC",
|
||||
"expense_account": "Loyalty - _TC",
|
||||
"collection_rules": [
|
||||
{"tier_name": "Bronze", "collection_factor": 1000, "min_spent": 0},
|
||||
{"tier_name": "Silver", "collection_factor": 1000, "min_spent": 10000},
|
||||
{"tier_name": "Gold", "collection_factor": 1000, "min_spent": 19000},
|
||||
],
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
{
|
||||
"fieldname": "min_spent",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Minimum Total Spent"
|
||||
},
|
||||
{
|
||||
@@ -37,7 +38,7 @@
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-03-27 13:10:03.536071",
|
||||
"modified": "2024-09-05 07:41:25.694041",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Loyalty Program Collection",
|
||||
|
||||
@@ -1776,7 +1776,8 @@ class SalesInvoice(SellingController):
|
||||
loyalty_program=self.loyalty_program,
|
||||
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):
|
||||
from frappe.query_builder.functions import Sum
|
||||
|
||||
Reference in New Issue
Block a user