[FIX] sale_loyalty,pos_loyalty: fix discountable on specific products

Prior to this commit the discountable amount on specific products was
not computed correctly when using fixed price discounts as it did not
take the taxes into account.
They will now be taken into account and result in the correct discount.

TaskId-2834863

closes odoo/odoo#91202

X-original-commit: 669f4cca94af0c022318f6cb508c1c197aae0846
Signed-off-by: Yannick Tivisse (yti) <yti@odoo.com>
Signed-off-by: William Braeckman (wbr) <wbr@odoo.com>
This commit is contained in:
William Braeckman
2022-05-12 14:51:10 +02:00
parent 61569e9bf7
commit bbad356f13
3 changed files with 122 additions and 9 deletions
+12 -4
View File
@@ -976,14 +976,18 @@ const PosLoyaltyOrder = (Order) => class PosLoyaltyOrder extends Order {
}
} else {
const nonCommonLines = discountedLines.filter((line) => !linesToDiscount.includes(line));
let discountedAmount = Math.abs(lines.reduce((sum, line) => sum + line.get_price_with_tax(), 0));
const discountedAmounts = lines.reduce((map, line) => {
map[line.get_taxes().map((t) => t.id)];
return map;
}, {});
const process = (line) => {
if (!discountedAmount || line.reward_id) {
const key = line.get_taxes().map((t) => t.id);
if (!discountedAmounts[key] || line.reward_id) {
return;
}
const remaining = remainingAmountPerLine[line.cid];
const consumed = Math.min(remaining, discountedAmount);
discountedAmount -= consumed;
const consumed = Math.min(remaining, discountedAmounts[key]);
discountedAmounts[key] -= consumed;
remainingAmountPerLine[line.cid] -= consumed;
}
nonCommonLines.forEach(process);
@@ -1071,6 +1075,10 @@ const PosLoyaltyOrder = (Order) => class PosLoyaltyOrder extends Order {
}
const discountFactor = discountable ? Math.min(1, (maxDiscount / discountable)) : 1;
const result = Object.entries(discountablePerTax).reduce((lst, entry) => {
// Ignore 0 price lines
if (!entry[1]) {
return;
}
const taxIds = entry[0] === '' ? [] : entry[0].split(',').map((str) => parseInt(str));
lst.push({
product: discountProduct,
+10 -5
View File
@@ -241,16 +241,21 @@ class SaleOrder(models.Model):
common_lines = discounted_lines & lines_to_discount
if line_reward.discount_mode == 'percent':
for line in discounted_lines:
remaining_amount_per_line[line] *= (1 - line_reward.discount/100)
if line_reward.discount_applicability == 'cheapest':
remaining_amount_per_line[line] *= (1 - line_reward.discount / 100 / line.product_uom_qty)
else:
remaining_amount_per_line[line] *= (1 - line_reward.discount / 100)
else:
non_common_lines = discounted_lines - lines_to_discount
discounted_amount = abs(sum(lines.mapped('price_total')))
# Fixed prices are per tax
discounted_amounts = {line.tax_id: abs(line.price_total) for line in lines}
for line in itertools.chain(non_common_lines, common_lines):
discounted_amount = discounted_amounts[line.tax_id]
if discounted_amount == 0:
break
continue
remaining = remaining_amount_per_line[line]
consumed = min(remaining, discounted_amount)
discounted_amount -= consumed
discounted_amounts[line.tax_id] -= consumed
remaining_amount_per_line[line] -= consumed
discountable = 0
@@ -331,7 +336,7 @@ class SaleOrder(models.Model):
'points_cost': 0,
'reward_identifier_code': reward_code,
'tax_id': [(Command.CLEAR, 0, 0)] + [(Command.LINK, tax.id, False) for tax in mapped_taxes[tax]]
} for tax, price in discountable_per_tax.items()}
} for tax, price in discountable_per_tax.items() if price}
# We only assign the point cost to one line to avoid counting the cost multiple times
if reward_dict:
reward_dict[next(iter(reward_dict))]['points_cost'] = point_cost
@@ -1461,3 +1461,103 @@ class TestSaleCouponProgramNumbers(TestSaleCouponCommon):
self.assertEqual(order.amount_total, 18, "The total should be 9$.")
self._auto_rewards(order, programs)
self.assertEqual(order.amount_total, 9, "The total should be 9$.")
def test_specific_discount_multiple_taxes(self):
# Check the following setup
# Product A 10$ 10% tva excl
# Product B 10$ 20% tva excl
# Program A -100% on product A
# Program B -5$ fixed on both products
# Applying both programs in a different order should result in a different
# outcome since discountable amounts are computed per tax
# Applying program A before B should yield a better final price
tax_10pc_excl, tax_20pc_excl = self.env['account.tax'].create([
{
'name': "10% Tax excl",
'amount_type': 'percent',
'amount': 10,
'type_tax_use': 'sale',
},
{
'name': "20% Tax excl",
'amount_type': 'percent',
'amount': 20,
'type_tax_use': 'sale',
},
])
product_a, product_b = self.env['product.product'].create([
{
'name': 'Product A',
'list_price': 10,
'sale_ok': True,
'taxes_id': [(6, 0, [tax_10pc_excl.id])],
},
{
'name': 'Product B',
'list_price': 10,
'sale_ok': True,
'taxes_id': [(6, 0, [tax_20pc_excl.id])],
},
])
program_a, program_b = self.env['loyalty.program'].create([
{
'name': '-100% on A',
'trigger': 'auto',
'program_type': 'promotion',
'applies_on': 'current',
'rule_ids': [(0, 0, {
})],
'reward_ids': [(0, 0, {
'reward_type': 'discount',
'discount': 100,
'discount_mode': 'percent',
'discount_applicability': 'specific',
'discount_product_ids': product_a,
'required_points': 1,
})],
},
{
'name': '-5 USD on [A, B]',
'trigger': 'auto',
'program_type': 'promotion',
'applies_on': 'current',
'rule_ids': [(0, 0, {
})],
'reward_ids': [(0, 0, {
'reward_type': 'discount',
'discount': 5,
'discount_mode': 'per_point',
'discount_applicability': 'specific',
'discount_product_ids': product_a | product_b,
'required_points': 1,
})],
},
])
order = self.empty_order
self.env['sale.order.line'].create([
{
'product_id': product_a.id,
'name': 'Product A',
'product_uom_qty': 1,
'order_id': order.id,
},
{
'product_id': product_b.id,
'name': 'Product B',
'product_uom_qty': 1,
'order_id': order.id,
},
])
self._auto_rewards(order, program_a)
self.assertEqual(order.amount_total, 12, 'Total should be 12$')
self._auto_rewards(order, program_b)
self.assertAlmostEqual(order.amount_total, 7, 0, 'Total should be 7$')
# Now the order way around
order.order_line.filtered('reward_id').unlink()
self._auto_rewards(order, program_b)
self.assertAlmostEqual(order.amount_total, 18, 0, 'Total should be 18$')
self._auto_rewards(order, program_a)
# We essentially create a discount of -100% off of an already discounted product
# (11 - 2.4) = 8.6$ discount ~
self.assertAlmostEqual(order.amount_total, 9.4, 1, 'Total should be 9.4$')