[FIX] sale_loyalty: skip paid calc if ewallet type

**Current behavior:**
Creating an ewallet loyalty program and removing the default
value for `trigger_product_ids` without adding another in its
place will result in any ewallet created using that program to
not have its balance deducted after being used in a sale order.

**Expected behavior:**
The default product should not be required here, nor should any
product, seeing as there is no `required` constraint on this
field nor any related ones.

**Steps to reproduce:**
1. Create an ewallet loyalty program and remove the Top-up
     ewallet product

2. Create an ewallet, give it some balance, and use it in an
     order

3. Check the balance of the wallet to see the erroneous behavior

**Cause of the issue:**
In the _program_check_compute_points() method of `sale.order` in
`sale_loyalty`, the conditional block:

`if not products_per_rule.get(rule):
    continue`

will normally prevent rules belonging to ewallet program types
from going further in the method because they have the default
ewallet top-up product in their domain. When the continue is not
reached, they will reach this line:

`amount_paid = sum(max(0, line.price_total) for
    line in order_lines if line.product_id in rule_products)`

which will end up offsetting the actual subtraction of a SOLs
`points_cost` from an ewallet `loyalty.card`'s balance.

**Fix:**
Add another check prior to the calculation of order points which
prevents ewallet coupons without any top-up products from
reaching the problematic code. If an ewallet program doesn't
have any `trigger_product_ids`, it shouldn't ever need to
perform such calculations.

opw-3756134

closes odoo/odoo#159957

X-original-commit: 695b3a687a3d160451339a675aab8986682389eb
Signed-off-by: Vincent Ethan <etvi@odoo.com>
This commit is contained in:
Ethan Vincent
2024-04-05 07:20:46 +00:00
parent 9e0e04eeaf
commit a11bca1810
2 changed files with 92 additions and 44 deletions
+3
View File
@@ -881,6 +881,9 @@ class SaleOrder(models.Model):
rule_points = []
program_result = result.setdefault(program, dict())
for rule in program.rule_ids:
# prevent bottomless ewallet spending
if program.program_type == 'ewallet' and not program.trigger_product_ids:
break
if rule.mode == 'with_code' and rule not in self.code_enabled_rule_ids:
continue
code_matched = True
+89 -44
View File
@@ -26,40 +26,52 @@ class TestLoyalty(TestSaleCouponCommon):
'taxes_id': [(6, 0, [])],
})
cls.ewallet_program = cls.env['loyalty.program'].create({
'name': 'eWallet Program',
'program_type': 'ewallet',
'trigger': 'auto',
'applies_on': 'future',
'reward_ids': [Command.create({
'reward_type': 'discount',
'discount_mode': 'per_point',
'discount': 1,
})],
'rule_ids': [Command.create({
'reward_point_amount': '1',
'reward_point_mode': 'money',
'product_ids': cls.env.ref('loyalty.ewallet_product_50'),
})],
'trigger_product_ids': cls.env.ref('loyalty.ewallet_product_50'),
})
cls.ewallet = cls.env['loyalty.card'].create({
'program_id': cls.ewallet_program.id,
'partner_id': cls.partner_a.id,
'points': 10,
})
cls.ewallet_program.coupon_ids = [Command.set([cls.ewallet.id])]
cls.user_salemanager = new_test_user(cls.env, login='user_salemanager', groups='sales_team.group_sale_manager')
def test_nominative_programs(self):
loyalty_program, ewallet_program = self.env['loyalty.program'].create([
{
'name': 'Loyalty Program',
'program_type': 'loyalty',
'trigger': 'auto',
'applies_on': 'both',
'rule_ids': [(0, 0, {
'reward_point_mode': 'unit',
'reward_point_amount': 1,
'product_ids': [self.product_a.id],
})],
'reward_ids': [(0, 0, {
'reward_type': 'discount',
'discount': 1.5,
'discount_mode': 'per_point',
'discount_applicability': 'order',
'required_points': 3,
})],
},
{
'name': 'eWallet Program',
'program_type': 'ewallet',
'applies_on': 'future',
'trigger': 'auto',
'rule_ids': [(0, 0, {
'reward_point_mode': 'money',
'reward_point_amount': 10,
})],
'reward_ids': [(0, 0, {})],
}
])
loyalty_program = self.env['loyalty.program'].create({
'name': 'Loyalty Program',
'program_type': 'loyalty',
'trigger': 'auto',
'applies_on': 'both',
'rule_ids': [(0, 0, {
'reward_point_mode': 'unit',
'reward_point_amount': 1,
'product_ids': [self.product_a.id],
})],
'reward_ids': [(0, 0, {
'reward_type': 'discount',
'discount': 1.5,
'discount_mode': 'per_point',
'discount_applicability': 'order',
'required_points': 3,
})],
})
order = self.env['sale.order'].create({
'partner_id': self.partner_a.id,
@@ -68,18 +80,13 @@ class TestLoyalty(TestSaleCouponCommon):
claimable_rewards = order._get_claimable_rewards()
# Should be empty since we do not have any coupon created yet
self.assertFalse(claimable_rewards, "No program should be applicable")
loyalty_card, ewallet_coupon = self.env['loyalty.card'].create([
{
'program_id': loyalty_program.id,
'partner_id': self.partner_a.id,
'points': 10,
},
{
'program_id': ewallet_program.id,
'partner_id': self.partner_a.id,
'points': 0,
},
])
loyalty_card = self.env['loyalty.card'].create({
'program_id': loyalty_program.id,
'partner_id': self.partner_a.id,
'points': 10,
})
self.ewallet.points = 0
order.write({
'order_line': [(0, 0, {
'product_id': self.product_a.id,
@@ -96,7 +103,7 @@ class TestLoyalty(TestSaleCouponCommon):
"Can only use a whole number of required points",
)
self.assertEqual(vals[0]['points_cost'], 9, "Use maximum available points for the reward")
ewallet_coupon.points = 50
self.ewallet.points = 50
order._update_programs_and_rewards()
claimable_rewards = order._get_claimable_rewards()
self.assertEqual(len(claimable_rewards), 2, "Now that the ewallet has some points they should both be applicable.")
@@ -605,3 +612,41 @@ class TestLoyalty(TestSaleCouponCommon):
self._claim_reward(order, loyalty_program)
msg = "100% discount on order should reduce total amount to 0"
self.assertEqual(order.amount_total, 0, msg=msg)
def test_ewallet_program_without_trigger_product(self):
self.ewallet_program.trigger_product_ids = [Command.clear()]
self.ewallet.points = 1000
order = self.env['sale.order'].create({
'partner_id': self.partner_a.id,
'order_line': [Command.create({
'product_id': self.product_a.id,
'points_cost': 100,
'product_uom_qty': 1,
})],
})
order._update_programs_and_rewards()
self._claim_reward(order, self.ewallet_program, coupon=self.ewallet)
order.action_confirm()
self.assertEqual(self.ewallet.points, 900)
def test_ewallet_applied_ewallet_topup_in_order(self):
self.ewallet.points = 10
order = self.env['sale.order'].create({
'partner_id': self.partner_a.id,
'order_line': [Command.create({
'product_id': self.product_a.id,
'points_cost': 100,
'product_uom_qty': 1,
}), Command.create({
'product_id': self.env.ref('loyalty.ewallet_product_50').id,
'product_uom_qty': 1,
})],
})
order._update_programs_and_rewards()
self._claim_reward(order, self.ewallet_program, coupon=self.ewallet)
order.action_confirm()
self.assertEqual(self.ewallet.points, 50)