From 8a22cf862662f92c206749327126e2097663048d Mon Sep 17 00:00:00 2001 From: Romain Derie Date: Thu, 8 Oct 2020 08:12:59 +0000 Subject: [PATCH] [FIX] website_sale_coupon: correctly handle multiwebsite MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Steps to reproduce: - Create an automatic promotional program P for Website 2. - Place Order for Website 1. - Promotion is applied. Fixes https://github.com/odoo/odoo/issues/55121 Closes https://github.com/odoo/odoo/pull/57895 closes odoo/odoo#60151 X-original-commit: 8a5c53bbc771c0895c001a7f7f43591c5cde2ba6 Forward-port-of: https://github.com/odoo/enterprise/pull/13875 Signed-off-by: Jérémy Kersten (jke) Co-authored-by: Romain Derie Co-authored-by: Swapnesh Shah --- .../sale_coupon/tests/test_program_numbers.py | 68 ++++---- addons/website_sale_coupon/models/__init__.py | 1 + .../website_sale_coupon/models/sale_coupon.py | 13 ++ .../models/sale_coupon_program.py | 13 ++ addons/website_sale_coupon/tests/__init__.py | 1 + .../tests/test_sale_coupon_multiwebsite.py | 149 ++++++++++++++++++ 6 files changed, 206 insertions(+), 39 deletions(-) create mode 100644 addons/website_sale_coupon/models/sale_coupon.py create mode 100644 addons/website_sale_coupon/tests/test_sale_coupon_multiwebsite.py diff --git a/addons/sale_coupon/tests/test_program_numbers.py b/addons/sale_coupon/tests/test_program_numbers.py index 2adf58064e4..89ce03c3607 100644 --- a/addons/sale_coupon/tests/test_program_numbers.py +++ b/addons/sale_coupon/tests/test_program_numbers.py @@ -71,6 +71,16 @@ class TestSaleCouponProgramNumbers(TestSaleCouponCommon): 'reward_product_id': self.largeMeetingTable.id, 'rule_products_domain': '[["name","ilike","drawer black"]]', }) + self.discount_coupon_program = self.env['coupon.program'].create({ + 'name': '$100 coupon', + 'program_type': 'coupon_program', + 'reward_type': 'discount', + 'discount_type': 'fixed_amount', + 'discount_fixed_amount': 100, + 'active': True, + 'discount_apply_on': 'on_order', + 'rule_minimum_amount': 100.00, + }) def test_program_numbers_free_and_paid_product_qty(self): # These tests will focus on numbers (free product qty, SO total, reduction total..) @@ -571,18 +581,8 @@ class TestSaleCouponProgramNumbers(TestSaleCouponCommon): 'order_id': order.id, }) self.assertEqual(order.amount_total, 165.0, "The order amount is not correct") - discount_coupon_program = self.env['coupon.program'].create({ - 'name': '$100 coupon', - 'program_type': 'coupon_program', - 'reward_type': 'discount', - 'discount_type': 'fixed_amount', - 'discount_fixed_amount': 100, - 'active': True, - 'discount_apply_on': 'on_order', - 'rule_minimum_amount': 100.00, - }) - self.env['coupon.generate.wizard'].with_context(active_id=discount_coupon_program.id).create({}).generate_coupon() - coupon = discount_coupon_program.coupon_ids[0] + self.env['coupon.generate.wizard'].with_context(active_id=self.discount_coupon_program.id).create({}).generate_coupon() + coupon = self.discount_coupon_program.coupon_ids[0] self.env['sale.coupon.apply.code'].with_context(active_id=order.id).create({ 'coupon_code': coupon.code }).process_coupon() @@ -604,17 +604,17 @@ class TestSaleCouponProgramNumbers(TestSaleCouponCommon): }) self.assertEqual(order.amount_total, 165.0, "The order amount is not correct") - discount_promotion_program = self.env['coupon.program'].create({ - 'name': '$100 promotion program', - 'program_type': 'promotion_program', - 'promo_code_usage': 'code_needed', - 'promo_code': 'testpromo', - 'reward_type': 'discount', - 'discount_type': 'fixed_amount', - 'discount_fixed_amount': 100, - 'active': True, - 'discount_apply_on': 'on_order', - 'rule_minimum_amount': 100.00, + self.env['coupon.program'].create({ + 'name': '$100 promotion program', + 'program_type': 'promotion_program', + 'promo_code_usage': 'code_needed', + 'promo_code': 'testpromo', + 'reward_type': 'discount', + 'discount_type': 'fixed_amount', + 'discount_fixed_amount': 100, + 'active': True, + 'discount_apply_on': 'on_order', + 'rule_minimum_amount': 100.00, }) self.env['sale.coupon.apply.code'].with_context(active_id=order.id).create({ @@ -624,18 +624,8 @@ class TestSaleCouponProgramNumbers(TestSaleCouponCommon): order.recompute_coupon_lines() self.assertEqual(order.amount_total, 65.0, "The promotion program should not be removed after recomputation") - discount_coupon_program = self.env['coupon.program'].create({ - 'name': '$100 coupon', - 'program_type': 'coupon_program', - 'reward_type': 'discount', - 'discount_type': 'fixed_amount', - 'discount_fixed_amount': 100, - 'active': True, - 'discount_apply_on': 'on_order', - 'rule_minimum_amount': 100.00, - }) - self.env['coupon.generate.wizard'].with_context(active_id=discount_coupon_program.id).create({}).generate_coupon() - coupon = discount_coupon_program.coupon_ids[0] + self.env['coupon.generate.wizard'].with_context(active_id=self.discount_coupon_program.id).create({}).generate_coupon() + coupon = self.discount_coupon_program.coupon_ids[0] with self.assertRaises(UserError): self.env['sale.coupon.apply.code'].with_context(active_id=order.id).create({ 'coupon_code': coupon.code @@ -780,7 +770,7 @@ class TestSaleCouponProgramNumbers(TestSaleCouponCommon): 'lst_price': 118.0, }) - test10 = self.env['coupon.program'].create({ + self.env['coupon.program'].create({ 'name': '10% discount', 'promo_code_usage': 'no_code_needed', 'program_type': 'promotion_program', @@ -789,7 +779,7 @@ class TestSaleCouponProgramNumbers(TestSaleCouponCommon): 'rule_minimum_amount': 1500.0, 'rule_minimum_amount_tax_inclusion': 'tax_included', }) - test15 = self.env['coupon.program'].create({ + self.env['coupon.program'].create({ 'name': '15% discount', 'promo_code_usage': 'no_code_needed', 'program_type': 'promotion_program', @@ -798,7 +788,7 @@ class TestSaleCouponProgramNumbers(TestSaleCouponCommon): 'rule_minimum_amount': 1750.0, 'rule_minimum_amount_tax_inclusion': 'tax_included', }) - test20 = self.env['coupon.program'].create({ + self.env['coupon.program'].create({ 'name': '20% discount', 'promo_code_usage': 'no_code_needed', 'program_type': 'promotion_program', @@ -807,7 +797,7 @@ class TestSaleCouponProgramNumbers(TestSaleCouponCommon): 'rule_minimum_amount': 2000.0, 'rule_minimum_amount_tax_inclusion': 'tax_included', }) - test25 = self.env['coupon.program'].create({ + self.env['coupon.program'].create({ 'name': '25% discount', 'promo_code_usage': 'no_code_needed', 'program_type': 'promotion_program', diff --git a/addons/website_sale_coupon/models/__init__.py b/addons/website_sale_coupon/models/__init__.py index 28c756b4fed..3ac5d4890a1 100644 --- a/addons/website_sale_coupon/models/__init__.py +++ b/addons/website_sale_coupon/models/__init__.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. +from . import sale_coupon from . import sale_coupon_program from . import sale_order diff --git a/addons/website_sale_coupon/models/sale_coupon.py b/addons/website_sale_coupon/models/sale_coupon.py new file mode 100644 index 00000000000..0b8029ef873 --- /dev/null +++ b/addons/website_sale_coupon/models/sale_coupon.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import models + + +class SaleCoupon(models.Model): + _inherit = 'coupon.coupon' + + def _check_coupon_code(self, order): + if self.program_id.website_id and self.program_id.website_id != order.website_id: + return {'error': 'This coupon is not valid on this website.'} + return super()._check_coupon_code(order) diff --git a/addons/website_sale_coupon/models/sale_coupon_program.py b/addons/website_sale_coupon/models/sale_coupon_program.py index b0ab5bb8792..b55d2a0e566 100644 --- a/addons/website_sale_coupon/models/sale_coupon_program.py +++ b/addons/website_sale_coupon/models/sale_coupon_program.py @@ -21,3 +21,16 @@ class CouponProgram(models.Model): domain += program.website_id.website_domain() if self.search(domain): raise ValidationError(_('The program code must be unique by website!')) + + def _filter_programs_on_website(self, order): + return self.filtered(lambda program: not program.website_id or program.website_id.id == order.website_id.id) + + @api.model + def _filter_programs_from_common_rules(self, order, next_order=False): + programs = self._filter_programs_on_website(order) + return super(CouponProgram, programs)._filter_programs_from_common_rules(order, next_order) + + def _check_promo_code(self, order, coupon_code): + if self.website_id and self.website_id != order.website_id: + return {'error': 'This promo code is not valid on this website.'} + return super()._check_promo_code(order, coupon_code) diff --git a/addons/website_sale_coupon/tests/__init__.py b/addons/website_sale_coupon/tests/__init__.py index 9f48e6f73be..cc015ce47c9 100644 --- a/addons/website_sale_coupon/tests/__init__.py +++ b/addons/website_sale_coupon/tests/__init__.py @@ -1 +1,2 @@ +from . import test_sale_coupon_multiwebsite from . import test_shop_sale_coupon diff --git a/addons/website_sale_coupon/tests/test_sale_coupon_multiwebsite.py b/addons/website_sale_coupon/tests/test_sale_coupon_multiwebsite.py new file mode 100644 index 00000000000..4c50d5f8072 --- /dev/null +++ b/addons/website_sale_coupon/tests/test_sale_coupon_multiwebsite.py @@ -0,0 +1,149 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo.addons.sale_coupon.tests.test_program_numbers import TestSaleCouponProgramNumbers +from odoo.addons.website.tools import MockRequest +from odoo.exceptions import UserError +from odoo.tests import tagged + + +@tagged('-at_install', 'post_install') +class TestSaleCouponMultiwebsite(TestSaleCouponProgramNumbers): + + def setUp(self): + super(TestSaleCouponMultiwebsite, self).setUp() + self.website = self.env['website'].browse(1) + self.website2 = self.env['website'].create({'name': 'website 2'}) + + def test_01_multiwebsite_checks(self): + """ Ensure the multi website compliance of programs and coupons, both in + backend and frontend. + """ + order = self.empty_order + self.env['sale.order.line'].create({ + 'product_id': self.largeCabinet.id, + 'name': 'Large Cabinet', + 'product_uom_qty': 2.0, + 'order_id': order.id, + }) + + def _remove_reward(): + order.order_line.filtered('is_reward_line').unlink() + self.assertEqual(len(order.order_line.ids), 1, "Program should have been removed") + + def _apply_code(code, backend=True): + if backend: + self.env['sale.coupon.apply.code'].with_context(active_id=order.id).create({ + 'coupon_code': code + }).process_coupon() + else: + self.env['sale.coupon.apply.code'].sudo().apply_coupon(order, code) + + # ========================================== + # ========== Programs (with code) ========== + # ========================================== + + # 1. Backend - Generic + _apply_code(self.p1.promo_code) + self.assertEqual(len(order.order_line.ids), 2, "Should get the discount line as it is a generic promo program") + _remove_reward() + + # 2. Frontend - Generic + with MockRequest(self.env, website=self.website): + _apply_code(self.p1.promo_code, False) + self.assertEqual(len(order.order_line.ids), 2, "Should get the discount line as it is a generic promo program (2)") + _remove_reward() + + # make program specific + self.p1.website_id = self.website.id + # 3. Backend - Specific + with self.assertRaises(UserError): + _apply_code(self.p1.promo_code) # the order has no website_id so not possible to use a website specific code + + # 4. Frontend - Specific - Correct website + order.website_id = self.website.id + with MockRequest(self.env, website=self.website): + _apply_code(self.p1.promo_code, False) + self.assertEqual(len(order.order_line.ids), 2, "Should get the discount line as it is a specific promo program for the correct website") + _remove_reward() + + # 5. Frontend - Specific - Wrong website + self.p1.website_id = self.website2.id + with MockRequest(self.env, website=self.website): + _apply_code(self.p1.promo_code, False) + self.assertEqual(len(order.order_line.ids), 1, "Should not get the reward as wrong website") + + # ============================== + # =========== Coupons ========== + # ============================== + + order.website_id = False + self.env['coupon.generate.wizard'].with_context(active_id=self.discount_coupon_program.id).create({ + 'nbr_coupons': 4, + }).generate_coupon() + coupons = self.discount_coupon_program.coupon_ids + + # 1. Backend - Generic + _apply_code(coupons[0].code) + self.assertEqual(len(order.order_line.ids), 2, "Should get the discount line as it is a generic coupon program") + _remove_reward() + + # 2. Frontend - Generic + with MockRequest(self.env, website=self.website): + _apply_code(coupons[1].code, False) + self.assertEqual(len(order.order_line.ids), 2, "Should get the discount line as it is a generic coupon program (2)") + _remove_reward() + + # make program specific + self.discount_coupon_program.website_id = self.website.id + # 3. Backend - Specific + with self.assertRaises(UserError): + _apply_code(coupons[2].code) # the order has no website_id so not possible to use a website specific code + + # 4. Frontend - Specific - Correct website + order.website_id = self.website.id + with MockRequest(self.env, website=self.website): + _apply_code(coupons[2].code, False) + self.assertEqual(len(order.order_line.ids), 2, "Should get the discount line as it is a specific coupon program for the correct website") + _remove_reward() + + # 5. Frontend - Specific - Wrong website + self.discount_coupon_program.website_id = self.website2.id + with MockRequest(self.env, website=self.website): + _apply_code(coupons[3].code, False) + self.assertEqual(len(order.order_line.ids), 1, "Should not get the reward as wrong website") + + # ======================================== + # ========== Programs (no code) ========== + # ======================================== + + order.website_id = False + self.p1.website_id = False + self.p1.promo_code = False + self.p1.promo_code_usage = 'no_code_needed' + + # 1. Backend - Generic + order.recompute_coupon_lines() + self.assertEqual(len(order.order_line.ids), 2, "Should get the discount line as it is a generic promo program") + + # 2. Frontend - Generic + with MockRequest(self.env, website=self.website): + order.recompute_coupon_lines() + self.assertEqual(len(order.order_line.ids), 2, "Should get the discount line as it is a generic promo program (2)") + + # make program specific + self.p1.website_id = self.website.id + # 3. Backend - Specific + order.recompute_coupon_lines() + self.assertEqual(len(order.order_line.ids), 1, "The order has no website_id so not possible to use a website specific code") + + # 4. Frontend - Specific - Correct website + order.website_id = self.website.id + with MockRequest(self.env, website=self.website): + order.recompute_coupon_lines() + self.assertEqual(len(order.order_line.ids), 2, "Should get the discount line as it is a specific promo program for the correct website") + + # 5. Frontend - Specific - Wrong website + self.p1.website_id = self.website2.id + with MockRequest(self.env, website=self.website): + order.recompute_coupon_lines() + self.assertEqual(len(order.order_line.ids), 1, "Should not get the reward as wrong website")