diff --git a/addons/sale_loyalty/models/sale_order.py b/addons/sale_loyalty/models/sale_order.py index 5f897f49f5d..4e777948027 100644 --- a/addons/sale_loyalty/models/sale_order.py +++ b/addons/sale_loyalty/models/sale_order.py @@ -992,7 +992,7 @@ class SaleOrder(models.Model): if not program.filtered_domain(self._get_program_domain()): return {'error': _('The program is not available for this order.')} elif program in self._get_applied_programs(): - return {'error': _('This program is already applied to this order.')} + return {'error': _('This program is already applied to this order.'), 'already_applied': True} # Check for applicability from the program's triggers/rules. # This step should also compute the amount of points to give for that program on that order. status = self._program_check_compute_points(program)[program] @@ -1058,7 +1058,7 @@ class SaleOrder(models.Model): if 'error' in apply_result and (not program.is_nominative or (program.is_nominative and not coupon)): if rule: self.code_enabled_rule_ids -= rule - if coupon: + if coupon and not apply_result.get('already_applied', False): self.applied_coupon_ids -= coupon return apply_result coupon = apply_result.get('coupon', self.env['loyalty.card']) diff --git a/addons/website_sale_loyalty/tests/test_shop_sale_coupon.py b/addons/website_sale_loyalty/tests/test_shop_sale_coupon.py index 6801758812e..65d3062bc47 100644 --- a/addons/website_sale_loyalty/tests/test_shop_sale_coupon.py +++ b/addons/website_sale_loyalty/tests/test_shop_sale_coupon.py @@ -1,10 +1,14 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from datetime import timedelta -from odoo import fields +from odoo import fields, http from odoo.exceptions import ValidationError -from odoo.tests import HttpCase, tagged, TransactionCase +from odoo.fields import Command +from odoo.tests import HttpCase, tagged + from odoo.addons.sale.tests.test_sale_product_attribute_value_config import TestSaleProductAttributeValueCommon +from odoo.addons.website.tools import MockRequest +from odoo.addons.website_sale_loyalty.controllers.main import WebsiteSale @tagged('post_install', '-at_install') @@ -366,3 +370,55 @@ class TestWebsiteSaleCoupon(HttpCase): msg = "The coupon should've been removed from the order" self.assertEqual(len(order.applied_coupon_ids), 0, msg=msg) + + def test_04_apply_coupon_code_twice(self): + """This test ensures that applying a coupon with code twice will: + 1. Raise an error + 2. Not delete the coupon + """ + website = self.env['website'].browse(1) + + # Create product + product = self.env['product.product'].create({ + 'name': 'Product', + 'list_price': 100, + 'sale_ok': True, + 'taxes_id': [], + }) + + order = self.empty_order + order.write({ + 'website_id': website.id, + 'order_line': [ + Command.create({ + 'product_id': product.id, + }), + ] + }) + + WebsiteSaleController = WebsiteSale() + + installed_modules = set(self.env['ir.module.module'].search([ + ('state', '=', 'installed'), + ]).mapped('name')) + for _ in http._generate_routing_rules(installed_modules, nodb_only=False): + pass + + with MockRequest(self.env, website=website, sale_order_id=order.id) as request: + # Check the base cart value + self.assertEqual(order.amount_total, 100.0, "The base cart value is incorrect.") + + # Apply coupon for the first time + WebsiteSaleController.pricelist(promo=self.coupon.code) + + # Check that the coupon has been applied + self.assertEqual(order.amount_total, 90.0, "The coupon is not applied.") + + # Apply the coupon again + WebsiteSaleController.pricelist(promo=self.coupon.code) + WebsiteSaleController.cart() + error_msg = request.session.get('error_promo_code') + + # Check that the coupon stay applied + self.assertEqual(bool(error_msg), True, "Apply a coupon twice should display an error message") + self.assertEqual(order.amount_total, 90.0, "Apply a coupon twice shouldn't delete it")