diff --git a/models/sale_coupon.py b/models/sale_coupon.py index 56f56681578..10bdc6f97d5 100644 --- a/models/sale_coupon.py +++ b/models/sale_coupon.py @@ -72,7 +72,7 @@ class SaleCoupon(models.Model): message = {'error': _('Global discounts are not cumulable.')} elif self.program_id.reward_type == 'product' and not order._is_reward_in_order_lines(self.program_id): message = {'error': _('The reward products should be in the sales order lines to apply the discount.')} - elif order.partner_id not in self.program_id.rule_partner_ids: + elif not self.program_id._is_valid_partner(order.partner_id): message = {'error': _("The customer doesn't have access to this reward.")} elif not self.program_id._filter_programs_on_products(order): message = {'error': _("You don't have the required product quantities on your sales order. All the products should be recorded on the sales order. (Example: You need to have 3 T-shirts on your sales order if the promotion is 'Buy 2, Get 1 Free'.")} diff --git a/models/sale_coupon_program.py b/models/sale_coupon_program.py index 6a6f2f36c83..5341ad82898 100644 --- a/models/sale_coupon_program.py +++ b/models/sale_coupon_program.py @@ -3,6 +3,7 @@ from odoo import api, fields, models, _ from odoo.exceptions import UserError +from odoo.tools.safe_eval import safe_eval class SaleCouponProgram(models.Model): @@ -148,7 +149,7 @@ class SaleCouponProgram(models.Model): message = {'error': _('Global discounts are not cumulable.')} elif self.promo_applicability == 'on_current_order' and self.reward_type == 'product' and not order._is_reward_in_order_lines(self): message = {'error': _('The reward products should be in the sales order lines to apply the discount.')} - elif order.partner_id not in self.rule_partner_ids: + elif not self._is_valid_partner(order.partner_id): message = {'error': _("The customer doesn't have access to this reward.")} elif not self._filter_programs_on_products(order): message = {'error': _("You don't have the required product quantities on your sales order. If the reward is same product quantity, please make sure that all the products are recorded on the sales order (Example: You need to have 3 T-shirts on your sales order if the promotion is 'Buy 2, Get 1 Free'.")} @@ -188,7 +189,7 @@ class SaleCouponProgram(models.Model): return self.filtered(lambda program: program.maximum_use_number == 0 or program.order_count <= program.maximum_use_number) def _filter_programs_on_partners(self, order): - return self.filtered(lambda program: program.rule_partner_ids and order.partner_id.id in program.rule_partner_ids.ids or not program.rule_partner_ids) + return self.filtered(lambda program: program._is_valid_partner(order.partner_id)) def _filter_programs_on_products(self, order): """ @@ -197,14 +198,15 @@ class SaleCouponProgram(models.Model): or Buy 1 coke + get 1 coke free then check 2 cokes are on cart or not """ order_lines = order.order_line - order._get_reward_lines() - products_qties = dict.fromkeys(order_lines.mapped('product_id').ids, 0) + products_qties = dict.fromkeys([product for product in order_lines.mapped('product_id')], 0) for line in order_lines: - products_qties[line.product_id.id] += line.product_uom_qty - valid_programs = self.filtered(lambda program: not program.rule_product_ids) + products_qties[line.product_id] += line.product_uom_qty + valid_programs = self.filtered(lambda program: not program.rule_products_domain) for program in self - valid_programs: - ordered_rule_products_qty = sum(map(lambda product_id, qty: product_id in program.rule_product_ids.ids and qty, products_qties, products_qties.values())) + ordered_rule_products_qty = sum(map(lambda product, qty: + program._is_valid_product(product) and qty, products_qties, products_qties.values())) # Avoid program if 1 ordered foo on a program '1 foo, 1 free foo' - if program.reward_product_id in program.rule_product_ids and program.reward_type == 'product': + if program._is_valid_product(program.reward_product_id) and program.reward_type == 'product': line = order.order_line.filtered(lambda line: line.product_id == program.reward_product_id) ordered_rule_products_qty -= program.reward_product_quantity # needed_quantity = program.rule_min_quantity if self. @@ -236,3 +238,17 @@ class SaleCouponProgram(models.Model): programs = programs and programs._filter_programs_on_products(order) programs = programs and programs._filter_not_ordered_reward_programs(order) return programs + + def _is_valid_partner(self, partner): + if self.rule_partners_domain: + domain = safe_eval(self.rule_partners_domain) + [('id', '=', partner.id)] + return bool(self.env['res.partner'].search_count(domain)) + else: + return True + + def _is_valid_product(self, product): + if self.rule_products_domain: + domain = safe_eval(self.rule_products_domain) + [('id', '=', product.id)] + return bool(self.env['product.product'].search_count(domain)) + else: + return True diff --git a/models/sale_coupon_rules.py b/models/sale_coupon_rules.py index 5e840713dc3..ae1224cba5c 100644 --- a/models/sale_coupon_rules.py +++ b/models/sale_coupon_rules.py @@ -13,11 +13,12 @@ class SaleCouponRule(models.Model): rule_date_from = fields.Datetime(string="Start Date", help="Coupon program start date") rule_date_to = fields.Datetime(string="End Date", help="Coupon program end date") rule_partners_domain = fields.Char(string="Based on Customers", help="Coupon program will work for selected customers only") + # YTI TODO Remove rule_partner_ids and rule_product_ids in master rule_partner_ids = fields.Many2many('res.partner', 'rule_partner_rel', 'rule_id', 'partner_id', - string="Related Partners", compute='_compute_rule_partner_ids', store=True) + string="Related Partners", compute='_compute_rule_partner_ids', store=True, deprecated=True) rule_products_domain = fields.Char(string="Based on Products", default=[['sale_ok', '=', True]], help="On Purchase of selected product, reward will be given") rule_product_ids = fields.Many2many('product.product', 'rule_product_rel', 'rule_id', 'product_id', - string="Related Products", compute='_compute_rule_product_ids', store=True) + string="Related Products", compute='_compute_rule_product_ids', store=True, deprecated=True) rule_min_quantity = fields.Integer(string="Minimum Quantity", default=1, help="Minimum required product quantity to get the reward") rule_minimum_amount = fields.Float(default=0.0, help="Minimum required amount to get the reward") @@ -35,14 +36,11 @@ class SaleCouponRule(models.Model): if self.filtered(lambda applicability: applicability.rule_minimum_amount < 0): raise ValidationError(_('Minimum purchased amount should be greater than 0')) + # YTI TODO Remove in master @api.depends('rule_partners_domain') def _compute_rule_partner_ids(self): - for applicability in self.filtered(lambda x: x.rule_partners_domain): - domain = safe_eval(applicability.rule_partners_domain) - applicability.rule_partner_ids = domain and self.env['res.partner'].search(domain) + pass @api.depends('rule_products_domain') def _compute_rule_product_ids(self): - for applicability in self.filtered(lambda x: x.rule_products_domain): - domain = safe_eval(applicability.rule_products_domain) - applicability.rule_product_ids = domain and self.env['product.product'].search(domain) + pass diff --git a/models/sale_order.py b/models/sale_order.py index 667489cfd47..2ad94ebbfac 100644 --- a/models/sale_order.py +++ b/models/sale_order.py @@ -22,10 +22,8 @@ class SaleOrder(models.Model): order.reward_amount = sum([line.price_subtotal for line in order.order_line.filtered(lambda line: line.is_reward_line)]) @api.multi - def _recompute_coupon_lines(self): - if self.env.context.get('coupon_recompute', False): - return - for order in self.with_context(coupon_recompute=True): + def recompute_coupon_lines(self): + for order in self: order._remove_invalid_reward_lines() order._create_new_no_code_promo_reward_lines() order._update_existing_reward_lines() @@ -37,20 +35,9 @@ class SaleOrder(models.Model): order.with_context(sale_coupon_no_loop=False)._create_new_no_code_promo_reward_lines() return order - @api.model - def create(self, vals): - res = super(SaleOrder, self.with_context(coupon_recompute=True)).create(vals) - res = res.with_context(coupon_recompute=False) - res._recompute_coupon_lines() - return res - - def write(self, vals): - res = super(SaleOrder, self.with_context(coupon_recompute=True)).write(vals) - self._recompute_coupon_lines() - return res - def action_confirm(self): res = super(SaleOrder, self).action_confirm() + self.recompute_coupon_lines() self.generated_coupon_ids.write({'state': 'new'}) return res @@ -80,10 +67,10 @@ class SaleOrder(models.Model): def _get_reward_values_product(self, program): price_unit = self.order_line.filtered(lambda line: program.reward_product_id == line.product_id)[0].price_unit - order_lines = (self.order_line - self._get_reward_lines()).filtered(lambda x: x.product_id in program.rule_product_ids) + order_lines = (self.order_line - self._get_reward_lines()).filtered(lambda x: program._is_valid_product(x.product_id)) max_product_qty = sum(order_lines.mapped('product_uom_qty')) or 1 # Remove needed quantity from reward quantity if same reward and rule product - if program.reward_product_id in program.rule_product_ids: + if program._is_valid_product(program.reward_product_id): reward_product_qty = max_product_qty // (program.rule_min_quantity + program.reward_product_quantity) else: reward_product_qty = max_product_qty @@ -253,17 +240,6 @@ class SaleOrderLine(models.Model): is_reward_line = fields.Boolean('Is a program reward line') - @api.model - def create(self, vals): - res = super(SaleOrderLine, self).create(vals) - res.order_id._recompute_coupon_lines() - return res - - def write(self, vals): - res = super(SaleOrderLine, self).write(vals) - self.mapped('order_id')._recompute_coupon_lines() - return res - def unlink(self): # Reactivate coupons related to unlinked reward line for line in self.filtered(lambda line: line.is_reward_line): @@ -278,7 +254,4 @@ class SaleOrderLine(models.Model): if related_program: line.order_id.no_code_promo_program_ids -= related_program line.order_id.code_promo_program_id -= related_program - orders = self.mapped('order_id') - res = super(SaleOrderLine, self).unlink() - orders._recompute_coupon_lines() - return res + return super(SaleOrderLine, self).unlink() diff --git a/tests/common.py b/tests/common.py index 37c44d7b92f..6bb58d7c751 100644 --- a/tests/common.py +++ b/tests/common.py @@ -9,6 +9,9 @@ class TestSaleCouponCommon(common.TransactionCase): def setUp(self): super(TestSaleCouponCommon, self).setUp() + # Set all the existing programs to active=False to avoid interference + self.env['sale.coupon.program'].search([]).write({'active': False}) + # create partner for sale order. self.steve = self.env['res.partner'].create({ 'name': 'Steve Bucknor', diff --git a/tests/test_program_rewards.py b/tests/test_program_rewards.py index bb324810715..057e47ada77 100644 --- a/tests/test_program_rewards.py +++ b/tests/test_program_rewards.py @@ -27,6 +27,7 @@ class TestSaleCouponProgramRules(TestSaleCouponCommon): 'product_uom_qty': 1.0, }) ]}) + order.recompute_coupon_lines() self.assertEqual(len(order.order_line.ids), 3, "The promo offert should have been applied as the partner is correct, the discount is not created") diff --git a/tests/test_program_rules.py b/tests/test_program_rules.py index 35e30924d49..17018dd49fd 100644 --- a/tests/test_program_rules.py +++ b/tests/test_program_rules.py @@ -30,6 +30,7 @@ class TestProgramRules(TestSaleCouponCommon): 'product_uom_qty': 1.0, }) ]}) + order.recompute_coupon_lines() self.assertEqual(len(order.order_line.ids), 3, "The promo offert should have been applied as the partner is correct, the discount is not created") order = self.env['sale.order'].create({'partner_id': self.env.ref('base.res_partner_1').id}) @@ -47,6 +48,7 @@ class TestProgramRules(TestSaleCouponCommon): 'product_uom_qty': 1.0, }) ]}) + order.recompute_coupon_lines() self.assertEqual(len(order.order_line.ids), 2, "The promo offert shouldn't have been applied, the discount is created") def test_program_rules_minimum_purchased_amount(self): @@ -72,6 +74,7 @@ class TestProgramRules(TestSaleCouponCommon): 'product_uom_qty': 1.0, }) ]}) + order.recompute_coupon_lines() self.assertEqual(len(order.order_line.ids), 2, "The promo offert shouldn't have been applied as the purchased amount is not enough") order = self.env['sale.order'].create({'partner_id': self.steve.id}) @@ -89,6 +92,7 @@ class TestProgramRules(TestSaleCouponCommon): 'product_uom_qty': 1.0, }) ]}) + order.recompute_coupon_lines() self.assertEqual(len(order.order_line.ids), 3, "The promo offert should have been applied as the purchased amount is enough") def test_program_rules_validity_dates_and_uses(self): @@ -115,6 +119,7 @@ class TestProgramRules(TestSaleCouponCommon): 'product_uom_qty': 1.0, }) ]}) + order.recompute_coupon_lines() self.assertEqual(len(order.order_line.ids), 2, "The promo offert shouldn't have been applied we're not between the validity dates") self.immediate_promotion_program.write({ @@ -136,6 +141,7 @@ class TestProgramRules(TestSaleCouponCommon): 'product_uom_qty': 1.0, }) ]}) + order.recompute_coupon_lines() self.assertEqual(len(order.order_line.ids), 3, "The promo offert should have been applied as we're between the validity dates") order = self.env['sale.order'].create({'partner_id': self.env.ref('base.res_partner_1').id}) order.write({'order_line': [ @@ -152,4 +158,5 @@ class TestProgramRules(TestSaleCouponCommon): 'product_uom_qty': 1.0, }) ]}) + order.recompute_coupon_lines() self.assertEqual(len(order.order_line.ids), 2, "The promo offert shouldn't have been applied as the number of uses is exceeded") diff --git a/tests/test_program_with_code_operations.py b/tests/test_program_with_code_operations.py index b39e02b98c7..50457f5c14c 100644 --- a/tests/test_program_with_code_operations.py +++ b/tests/test_program_with_code_operations.py @@ -42,10 +42,12 @@ class TestProgramWithCodeOperations(TestSaleCouponCommon): self.env['sale.coupon.apply.code'].with_context(active_id=order.id).create({ 'coupon_code': self.code_promotion_program.coupon_ids.code }).process_coupon() + order.recompute_coupon_lines() self.assertEqual(len(order.order_line.ids), 2) # Remove the product A from the sale order order.write({'order_line': [(2, order.order_line[0].id, False)]}) + order.recompute_coupon_lines() self.assertEqual(len(order.order_line.ids), 0) def test_on_next_order_reward_promo_program(self): @@ -71,6 +73,7 @@ def test_on_next_order_reward_promo_program(self): 'coupon_code': 'bleurk' }).process_coupon() # Ensure that the coupon is correctly generated + order.recompute_coupon_lines() self.assertEqual(len(self.immediate_promotion_program.coupon_ids.ids), 1) generated_coupon = self.immediate_promotion_program.coupon_ids[0] @@ -89,4 +92,5 @@ def test_on_next_order_reward_promo_program(self): 'coupon_code': generated_coupon.code }) # Ensure that the generated coupon is valid + order.recompute_coupon_lines() self.assertEqual(len(order.order_line.ids), 2) diff --git a/tests/test_program_without_code_operations.py b/tests/test_program_without_code_operations.py index 32f2edf6a93..6d789e5b115 100644 --- a/tests/test_program_without_code_operations.py +++ b/tests/test_program_without_code_operations.py @@ -22,6 +22,7 @@ class TestProgramWithoutCodeOperations(TestSaleCouponCommon): 'product_uom_qty': 1.0, }) ]}) + order.recompute_coupon_lines() self.assertEqual(len(order.order_line.ids), 1, "The promo offer shouldn't have been applied as the product B isn't in the order") # Test case 2 (1 A 1 B): Assert that no reward is given, as the product B is not present in the correct quantity @@ -33,14 +34,17 @@ class TestProgramWithoutCodeOperations(TestSaleCouponCommon): 'product_uom_qty': 1.0, }) ]}) + order.recompute_coupon_lines() self.assertEqual(len(order.order_line.ids), 2, "The promo offer shouldn't have been applied as 2 product A aren't in the order") # Test case 3 (2 A 1 B): Assert that the reward is given as the product B is now in the order order.write({'order_line': [(1, order.order_line[0].id, {'product_uom_qty': 2.0})]}) + order.recompute_coupon_lines() self.assertEqual(len(order.order_line.ids), 3, "The promo offert should have been applied, the discount is not created") # Test case 4 (1 A 1 B): Assert that the reward is removed as we don't buy 2 products B anymore order.write({'order_line': [(1, order.order_line[0].id, {'product_uom_qty': 1.0})]}) + order.recompute_coupon_lines() self.assertEqual(len(order.order_line.ids), 2, "The promo reward should have been removed as the rules are not matched anymore") self.assertEqual(order.order_line[0].product_id.id, self.product_A.id, "The wrong line has been removed") self.assertEqual(order.order_line[1].product_id.id, self.product_B.id, "The wrong line has been removed") @@ -50,5 +54,6 @@ class TestProgramWithoutCodeOperations(TestSaleCouponCommon): (1, order.order_line[0].id, {'product_uom_qty': 2.0}), (2, order.order_line[0].id, False) ]}) + order.recompute_coupon_lines() self.assertEqual(len(order.order_line.ids), 1, "The promo reward should have been removed as the rules are not matched anymore") self.assertEqual(order.order_line.product_id.id, self.product_B.id, "The wrong line has been removed") diff --git a/views/sale_coupon_program_views.xml b/views/sale_coupon_program_views.xml index dc4cd937bb9..9ee1cefa613 100644 --- a/views/sale_coupon_program_views.xml +++ b/views/sale_coupon_program_views.xml @@ -23,7 +23,6 @@ -