Skip to content
Snippets Groups Projects
Commit 609abfa3 authored by prro-odoo's avatar prro-odoo
Browse files

[FIX] sale_coupon: allow changing reward line quantity


How to reproduce the problem:
- Install the sale_coupon module
- Sales -> Products -> Promotion Programs
- Create a promotion with "Fixed Amount" discount, "Apply on current
order", with Based on Products as "Name = Large Cabinet"
- Create a SO with 3 Large Cabinet.
- Modify the quantity on the new reward line: the price unit became 0.

A client should be able to manually change the discount quantity.
That was not possible before due to the fact that the reward products of
a promotion are saved in the DB with a list price of 0.
As the reward generation handles the job of computing the correct
unit price on a reward SOL, that unit price is simply re-used when
the quantity is changed.

Also corrects the case where a Free Product is different from the
Rule Product(s): with the promotion
"Buy 1 Product A, get 1 free Product B", if the Sale Order has
3 Product A, for example, the promotion will offer only 1 Product B,
while it should offer 3.

opw-2522604

closes odoo/odoo#74724

Signed-off-by: default avatarRomain Derie <rdeodoo@users.noreply.github.com>
parent c14b17c4
Branches
Tags
No related merge requests found
......@@ -95,7 +95,8 @@ class SaleOrder(models.Model):
order_total = sum(order_lines.mapped('price_total')) - (program.reward_product_quantity * program.reward_product_id.lst_price)
reward_product_qty = min(reward_product_qty, order_total // program.rule_minimum_amount)
else:
reward_product_qty = min(program.reward_product_quantity, total_qty)
program_in_order = max_product_qty // program.rule_min_quantity
reward_product_qty = min(program.reward_product_quantity * program_in_order, total_qty)
reward_qty = min(int(int(max_product_qty / program.rule_min_quantity) * program.reward_product_quantity), reward_product_qty)
# Take the default taxes on the reward product, mapped with the fiscal position
......@@ -514,6 +515,13 @@ class SaleOrderLine(models.Model):
taxes = line.tax_id.filtered(lambda r: not line.company_id or r.company_id == line.company_id)
line.tax_id = fpos.map_tax(taxes, line.product_id, line.order_id.partner_shipping_id) if fpos else taxes
def _get_display_price(self, product):
# A product created from a promotion does not have a list_price.
# The price_unit of a reward order line is computed by the promotion, so it can be used directly
if self.is_reward_line:
return self.price_unit
return super()._get_display_price(product)
# Invalidation of `sale.coupon.program.order_count`
# `test_program_rules_validity_dates_and_uses`,
# Overriding modified is quite hardcore as you need to know how works the cache and the invalidation system,
......
......@@ -1082,3 +1082,67 @@ class TestSaleCouponProgramNumbers(TestSaleCouponCommon):
self.assertEqual(order.amount_total, 500.0, 'The price must be 500.0 since two conference chairs are free and the user only bought one')
self.assertEqual(order.order_line[2].price_total, -100.0, 'The last order line should apply a reduction of 100.0 since there is one conference chair that cost 100.0')
def test_program_free_product_different_than_rule_product_with_multiple_application(self):
order = self.empty_order
self.env['sale.order.line'].create({
'product_id': self.drawerBlack.id,
'product_uom_qty': 2.0,
'order_id': order.id,
})
sol_B = self.env['sale.order.line'].create({
'product_id': self.largeMeetingTable.id,
'product_uom_qty': 1.0,
'order_id': order.id,
})
order.recompute_coupon_lines()
self.assertEqual(len(order.order_line), 3, 'The order must contain 3 order lines: 1x for Black Drawer, 1x for Large Meeting Table and 1x for free Large Meeting Table')
self.assertEqual(order.amount_total, self.drawerBlack.list_price * 2, 'The price must be 50.0 since the Large Meeting Table is free: 2*25.00 (Black Drawer) + 1*40000.00 (Large Meeting Table) - 1*40000.00 (free Large Meeting Table)')
self.assertEqual(order.order_line.filtered(lambda x: x.is_reward_line).product_uom_qty, 1, "Only one free Large Meeting Table should be offered, as only one paid Large Meeting Table is in cart. You can't have more free product than paid product.")
sol_B.product_uom_qty = 2
order.recompute_coupon_lines()
self.assertEqual(len(order.order_line), 3, 'The order must contain 3 order lines: 1x for Black Drawer, 1x for Large Meeting Table and 1x for free Large Meeting Table')
self.assertEqual(order.amount_total, self.drawerBlack.list_price * 2, 'The price must be 50.0 since the 2 Large Meeting Table are free: 2*25.00 (Black Drawer) + 2*40000.00 (Large Meeting Table) - 2*40000.00 (free Large Meeting Table)')
self.assertEqual(order.order_line.filtered(lambda x: x.is_reward_line).product_uom_qty, 2, 'The 2 Large Meeting Table should be offered, as the promotion says 1 Black Drawer = 1 free Large Meeting Table and there are 2 Black Drawer')
def test_program_modify_reward_line_qty(self):
order = self.empty_order
product_F = self.env['product.product'].create({
'name': 'Product F',
'list_price': 100,
'sale_ok': True,
'taxes_id': [(6, 0, [])],
})
self.env['sale.coupon.program'].create({
'name': '1 Product F = 5$ discount',
'promo_code_usage': 'no_code_needed',
'reward_type': 'discount',
'discount_type': 'fixed_amount',
'discount_fixed_amount': 5,
'rule_products_domain': "[('id', 'in', [%s])]" % (product_F.id),
'active': True,
})
self.env['sale.order.line'].create({
'product_id': product_F.id,
'product_uom_qty': 2.0,
'order_id': order.id,
})
order.recompute_coupon_lines()
self.assertEqual(len(order.order_line), 2, 'The order must contain 2 order lines: 1x Product F and 1x 5$ discount')
self.assertEqual(order.amount_total, 195.0, 'The price must be 195.0 since there is a 5$ discount and 2x Product F')
self.assertEqual(order.order_line.filtered(lambda x: x.is_reward_line).product_uom_qty, 1, 'The reward line should have a quantity of 1 since Fixed Amount discounts apply only once per Sale Order')
order.order_line[1].product_uom_qty = 2
self.assertEqual(len(order.order_line), 2, 'The order must contain 2 order lines: 1x Product F and 1x 5$ discount')
self.assertEqual(order.amount_total, 190.0, 'The price must be 190.0 since there is now 2x 5$ discount and 2x Product F')
self.assertEqual(order.order_line.filtered(lambda x: x.is_reward_line).price_unit, -5, 'The discount unit price should still be -5 after the quantity was manually changed')
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment