diff --git a/addons/sale_coupon/data/sale_coupon_email_data.xml b/addons/sale_coupon/data/sale_coupon_email_data.xml index afe32281dce..9d385b9272c 100644 --- a/addons/sale_coupon/data/sale_coupon_email_data.xml +++ b/addons/sale_coupon/data/sale_coupon_email_data.xml @@ -26,10 +26,18 @@ ${object.program_id.discount_percentage} % - % if object.program_id.discount_apply_on == 'specific_product': -
- ${'on %s' % object.program_id.discount_specific_product_id.name} - + % if object.program_id.discount_apply_on == 'specific_products' +
+ % if len(object.program_id.discount_specific_product_ids) != 1 + % set display_specific_products = True + + on some products* + + % else + + ${'on %s' % object.program_id.discount_specific_product_ids.name} + + % endif % elif object.program_id.discount_apply_on == 'cheapest_product':
off on the cheapest product @@ -73,6 +81,11 @@ Valid for purchase above ${object.program_id.company_id.currency_id.symbol}${'%0.2f' % object.program_id.rule_minimum_amount |float}
% endif + % if display_specific_products + + *Valid for following products: ${', '.join(object.program_id.discount_specific_product_ids.mapped('name'))} +
+ % endif
Thank you,
diff --git a/addons/sale_coupon/models/sale_coupon_program.py b/addons/sale_coupon/models/sale_coupon_program.py index 8ce426ee98c..bbdb4807070 100644 --- a/addons/sale_coupon/models/sale_coupon_program.py +++ b/addons/sale_coupon/models/sale_coupon_program.py @@ -110,7 +110,7 @@ class SaleCouponProgram(models.Model): res = super(SaleCouponProgram, self).write(vals) reward_fields = [ 'reward_type', 'reward_product_id', 'discount_type', 'discount_percentage', - 'discount_apply_on', 'discount_specific_product_id', 'discount_fixed_amount' + 'discount_apply_on', 'discount_specific_product_ids', 'discount_fixed_amount' ] if any(field in reward_fields for field in vals): self.mapped('discount_line_product_id').write({'name': self[0].reward_id.display_name}) @@ -259,8 +259,8 @@ class SaleCouponProgram(models.Model): if program.reward_type == 'product' and \ not order.order_line.filtered(lambda line: line.product_id == program.reward_product_id): continue - elif program.reward_type == 'discount' and program.discount_apply_on == 'specific_product' and \ - not order.order_line.filtered(lambda line: line.product_id == program.discount_specific_product_id): + elif program.reward_type == 'discount' and program.discount_apply_on == 'specific_products' and \ + not order.order_line.filtered(lambda line: line.product_id in program.discount_specific_product_ids): continue programs |= program return programs diff --git a/addons/sale_coupon/models/sale_coupon_reward.py b/addons/sale_coupon/models/sale_coupon_reward.py index e7af055055d..57450c1d17d 100644 --- a/addons/sale_coupon/models/sale_coupon_reward.py +++ b/addons/sale_coupon/models/sale_coupon_reward.py @@ -33,12 +33,12 @@ class SaleCouponReward(models.Model): discount_apply_on = fields.Selection([ ('on_order', 'On Order'), ('cheapest_product', 'On Cheapest Product'), - ('specific_product', 'On Specific Product')], default="on_order", + ('specific_products', 'On Specific Products')], default="on_order", help="On Order - Discount on whole order\n" + "Cheapest product - Discount on cheapest product of the order\n" + - "Specific product - Discount on selected specific product") - discount_specific_product_id = fields.Many2one('product.product', string="Product", - help="Product that will be discounted if the discount is applied on a specific product") + "Specific products - Discount on selected specific products") + discount_specific_product_ids = fields.Many2many('product.product', string="Products", + help="Products that will be discounted if the discount is applied on specific products") discount_max_amount = fields.Float(default=0, help="Maximum amount of discount that can be provided") discount_fixed_amount = fields.Float(string="Fixed Amount", help='The discount in fixed amount') @@ -65,8 +65,11 @@ class SaleCouponReward(models.Model): reward_percentage = str(reward.discount_percentage) if reward.discount_apply_on == 'on_order': reward_string = _("%s%% discount on total amount" % (reward_percentage)) - elif reward.discount_apply_on == 'specific_product': - reward_string = _("%s%% discount on %s" % (reward_percentage, reward.discount_specific_product_id.name)) + elif reward.discount_apply_on == 'specific_products': + if len(reward.discount_specific_product_ids) > 1: + reward_string = _("%s%% discount on products" % (reward_percentage)) + else: + reward_string = _("%s%% discount on %s" % (reward_percentage, reward.discount_specific_product_ids.name)) elif reward.discount_apply_on == 'cheapest_product': reward_string = _("%s%% discount on cheapest product" % (reward_percentage)) elif reward.discount_type == 'fixed_amount': diff --git a/addons/sale_coupon/models/sale_order.py b/addons/sale_coupon/models/sale_order.py index 01183d35cbe..8a7a8913795 100644 --- a/addons/sale_coupon/models/sale_order.py +++ b/addons/sale_coupon/models/sale_order.py @@ -155,11 +155,11 @@ class SaleOrder(models.Model): 'is_reward_line': True, 'tax_id': [(4, tax.id, False) for tax in taxes], } - elif program.discount_apply_on in ['specific_product', 'on_order']: - if program.discount_apply_on == 'specific_product': + elif program.discount_apply_on in ['specific_products', 'on_order']: + if program.discount_apply_on == 'specific_products': # We should not exclude reward line that offer this product since we need to offer only the discount on the real paid product (regular product - free product) - free_product_lines = self.env['sale.coupon.program'].search([('reward_type', '=', 'product'), ('reward_product_id', '=', program.discount_specific_product_id.id)]).mapped('discount_line_product_id') - lines = lines.filtered(lambda x: x.product_id == program.discount_specific_product_id or x.product_id in free_product_lines) + free_product_lines = self.env['sale.coupon.program'].search([('reward_type', '=', 'product'), ('reward_product_id', 'in', program.discount_specific_product_ids.ids)]).mapped('discount_line_product_id') + lines = lines.filtered(lambda x: x.product_id in (program.discount_specific_product_ids | free_product_lines)) for line in lines: discount_line_amount = self._get_reward_values_discount_percentage_per_line(program, line) diff --git a/addons/sale_coupon/report/sale_coupon_report_templates.xml b/addons/sale_coupon/report/sale_coupon_report_templates.xml index 10f6fe32591..1bdd96ad56d 100644 --- a/addons/sale_coupon/report/sale_coupon_report_templates.xml +++ b/addons/sale_coupon/report/sale_coupon_report_templates.xml @@ -19,8 +19,14 @@

%

- - + + + + + + + + @@ -49,6 +55,11 @@ Valid for purchase above +

+ + *Valid for following products: + +


Barcode
diff --git a/addons/sale_coupon/tests/test_program_numbers.py b/addons/sale_coupon/tests/test_program_numbers.py index 9edc5d06989..31dc651118b 100644 --- a/addons/sale_coupon/tests/test_program_numbers.py +++ b/addons/sale_coupon/tests/test_program_numbers.py @@ -142,8 +142,8 @@ class TestSaleCouponProgramNumbers(TestSaleCouponCommon): 'discount_type': 'percentage', 'discount_percentage': 20.0, 'rule_minimum_amount': 320.00, - 'discount_apply_on': 'specific_product', - 'discount_specific_product_id': self.largeCabinet.id, + 'discount_apply_on': 'specific_products', + 'discount_specific_product_ids': [(6, 0, [self.largeCabinet.id])], }) order = self.empty_order self.largeCabinet.taxes_id = percent_tax @@ -377,8 +377,8 @@ class TestSaleCouponProgramNumbers(TestSaleCouponCommon): 'program_type': 'promotion_program', 'discount_type': 'percentage', 'discount_percentage': 20.0, - 'discount_apply_on': 'specific_product', - 'discount_specific_product_id': self.largeCabinet.id, + 'discount_apply_on': 'specific_products', + 'discount_specific_product_ids': [(6, 0, [self.largeCabinet.id])], }) order.recompute_coupon_lines() # Note: we have 7 regular Large Cabinets and 3 free Large Cabinets. We should then discount only 4 really paid Large Cabinets @@ -537,3 +537,83 @@ class TestSaleCouponProgramNumbers(TestSaleCouponCommon): generated_coupon = order.generated_coupon_ids self.assertEqual(len(generated_coupon), 1, "We should still have only 1 coupon as we now benefit again from the program but no need to create a new one (see next assert)") self.assertEqual(generated_coupon.state, 'reserved', "The coupon should be set back to reserved as we had already an expired one, no need to create a new one") + + def test_program_discount_on_multiple_specific_products(self): + """ Ensure a discount on multiple specific products is correctly computed. + - Simple: Discount must be applied on all the products set on the promotion + - Advanced: This discount must be split by different taxes + """ + order = self.empty_order + p_specific_products = self.env['sale.coupon.program'].create({ + 'name': '20% reduction on Conference Chair and Drawer Black in cart', + 'promo_code_usage': 'no_code_needed', + 'reward_type': 'discount', + 'program_type': 'promotion_program', + 'discount_type': 'percentage', + 'discount_percentage': 25.0, + 'discount_apply_on': 'specific_products', + 'discount_specific_product_ids': [(6, 0, [self.conferenceChair.id, self.drawerBlack.id])], + }) + + self.env['sale.order.line'].create({ + 'product_id': self.conferenceChair.id, + 'name': 'Conference Chair', + 'product_uom_qty': 4.0, + 'order_id': order.id, + }) + sol2 = self.env['sale.order.line'].create({ + 'product_id': self.drawerBlack.id, + 'name': 'Drawer Black', + 'product_uom_qty': 2.0, + 'order_id': order.id, + }) + + order.recompute_coupon_lines() + self.assertEqual(len(order.order_line.ids), 3, "Conference Chair + Drawer Black + 20% discount line") + # Name | Qty | price_unit | Tax | HTVA | TVAC | TVA | + # -------------------------------------------------------------------------------- + # Conference Chair | 4 | 16.50 | / | 66.00 | 66.00 | 0.00 + # Drawer Black | 2 | 25.00 | / | 50.00 | 50.00 | 0.00 + # 25% discount | 1 | -29.00 | / | -29.00 | -29.00 | 0.00 + # -------------------------------------------------------------------------------- + # TOTAL | 87.00 | 87.00 | 0.00 + self.assertEqual(order.amount_total, 87.00, "Total should be 87.00, see above comment") + + # remove Drawer Black case from promotion + p_specific_products.discount_specific_product_ids = [(6, 0, [self.conferenceChair.id])] + order.recompute_coupon_lines() + self.assertEqual(len(order.order_line.ids), 3, "Should still be Conference Chair + Drawer Black + 20% discount line") + # Name | Qty | price_unit | Tax | HTVA | TVAC | TVA | + # -------------------------------------------------------------------------------- + # Conference Chair | 4 | 16.50 | / | 66.00 | 66.00 | 0.00 + # Drawer Black | 2 | 25.00 | / | 50.00 | 50.00 | 0.00 + # 25% discount | 1 | -16.50 | / | -16.50 | -16.50 | 0.00 + # -------------------------------------------------------------------------------- + # TOTAL | 99.50 | 99.50 | 0.00 + self.assertEqual(order.amount_total, 99.50, "The 12.50 discount from the drawer black should be gone") + + # ========================================================================= + # PART 2: Same flow but with different taxes on products to ensure discount is split per VAT + # Add back Drawer Black in promotion + p_specific_products.discount_specific_product_ids = [(6, 0, [self.conferenceChair.id, self.drawerBlack.id])] + + percent_tax = self.env['account.tax'].create({ + 'name': "30% Tax", + 'amount_type': 'percent', + 'amount': 30, + 'price_include': True, + }) + sol2.tax_id = percent_tax + + order.recompute_coupon_lines() + self.assertEqual(len(order.order_line.ids), 4, "Conference Chair + Drawer Black + 20% on no TVA product (Conference Chair) + 20% on 15% tva product (Drawer Black)") + # Name | Qty | price_unit | Tax | HTVA | TVAC | TVA | + # -------------------------------------------------------------------------------- + # Conference Chair | 4 | 16.50 | / | 66.00 | 66.00 | 0.00 + # Drawer Black | 2 | 25.00 | 30% incl | 38.46 | 50.00 | 11.54 + # 25% discount | 1 | -16.50 | / | -16.50 | -16.50 | 0.00 + # 25% discount | 1 | -12.50 | 30% incl | -9.62 | -12.50 | -2.88 + # -------------------------------------------------------------------------------- + # TOTAL | 78.34 | 87.00 | 8.66 + self.assertEqual(order.amount_total, 87.00, "Total untaxed should be as per above comment") + self.assertEqual(order.amount_untaxed, 78.34, "Total with taxes should be as per above comment") diff --git a/addons/sale_coupon/views/sale_coupon_program_views.xml b/addons/sale_coupon/views/sale_coupon_program_views.xml index ffd15643321..f6c72b23e4d 100644 --- a/addons/sale_coupon/views/sale_coupon_program_views.xml +++ b/addons/sale_coupon/views/sale_coupon_program_views.xml @@ -60,7 +60,7 @@ - +