[MERGE] forward port branch saas-11.3 up to a9f3cf76b93
This commit is contained in:
@@ -229,10 +229,9 @@ class SaleCouponProgram(models.Model):
|
||||
valid_products = program._get_valid_products(products)
|
||||
ordered_rule_products_qty = sum(products_qties[product] for product in valid_products)
|
||||
# Avoid program if 1 ordered foo on a program '1 foo, 1 free foo'
|
||||
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)
|
||||
if program.promo_applicability == 'on_current_order' and \
|
||||
program._is_valid_product(program.reward_product_id) and program.reward_type == 'product':
|
||||
ordered_rule_products_qty -= program.reward_product_quantity
|
||||
# needed_quantity = program.rule_min_quantity if self.
|
||||
if ordered_rule_products_qty >= program.rule_min_quantity:
|
||||
valid_programs |= program
|
||||
return valid_programs
|
||||
@@ -267,7 +266,12 @@ class SaleCouponProgram(models.Model):
|
||||
# Product requirement should not be checked if the coupon got generated by a promotion program (the requirement should have only be checked to generate the coupon)
|
||||
if not next_order:
|
||||
programs = programs and programs._filter_programs_on_products(order)
|
||||
programs = programs and programs._filter_not_ordered_reward_programs(order)
|
||||
|
||||
programs_curr_order = programs.filtered(lambda p: p.promo_applicability == 'on_current_order')
|
||||
programs = programs.filtered(lambda p: p.promo_applicability == 'on_next_order')
|
||||
if programs_curr_order:
|
||||
# Checking if rewards are in the SO should not be performed for rewards on_next_order
|
||||
programs += programs_curr_order._filter_not_ordered_reward_programs(order)
|
||||
return programs
|
||||
|
||||
def _is_valid_partner(self, partner):
|
||||
|
||||
+43
-11
@@ -217,13 +217,24 @@ class SaleOrder(models.Model):
|
||||
self.write({'order_line': [(0, False, value) for value in self._get_reward_line_values(program)]})
|
||||
|
||||
def _create_reward_coupon(self, program):
|
||||
coupon = self.env['sale.coupon'].create({
|
||||
'program_id': program.id,
|
||||
'state': 'reserved',
|
||||
'partner_id': self.partner_id.id,
|
||||
'order_id': self.id,
|
||||
'discount_line_product_id': program.discount_line_product_id.id
|
||||
})
|
||||
# if there is already a coupon that was set as expired, reactivate that one instead of creating a new one
|
||||
coupon = self.env['sale.coupon'].search([
|
||||
('program_id', '=', program.id),
|
||||
('state', '=', 'expired'),
|
||||
('partner_id', '=', self.partner_id.id),
|
||||
('order_id', '=', self.id),
|
||||
('discount_line_product_id', '=', program.discount_line_product_id.id),
|
||||
], limit=1)
|
||||
if coupon:
|
||||
coupon.write({'state': 'reserved'})
|
||||
else:
|
||||
coupon = self.env['sale.coupon'].create({
|
||||
'program_id': program.id,
|
||||
'state': 'reserved',
|
||||
'partner_id': self.partner_id.id,
|
||||
'order_id': self.id,
|
||||
'discount_line_product_id': program.discount_line_product_id.id
|
||||
})
|
||||
self.generated_coupon_ids |= coupon
|
||||
return coupon
|
||||
|
||||
@@ -258,8 +269,17 @@ class SaleOrder(models.Model):
|
||||
return programs
|
||||
|
||||
def _get_applied_coupon_program_coming_from_another_so(self):
|
||||
# TODO: Remove me in master as no more used
|
||||
pass
|
||||
|
||||
def _get_valid_applied_coupon_program(self):
|
||||
self.ensure_one()
|
||||
programs = self.applied_coupon_ids.mapped('program_id')._filter_programs_from_common_rules(self, True)
|
||||
# applied_coupon_ids's coupons might be coming from:
|
||||
# * a coupon generated from a previous order that benefited from a promotion_program that rewarded the next sale order.
|
||||
# In that case requirements to benefit from the program (Quantity and price) should not be checked anymore
|
||||
# * a coupon_program, in that case the promo_applicability is always for the current order and everything should be checked (filtered)
|
||||
programs = self.applied_coupon_ids.mapped('program_id').filtered(lambda p: p.promo_applicability == 'on_next_order')._filter_programs_from_common_rules(self, True)
|
||||
programs += self.applied_coupon_ids.mapped('program_id').filtered(lambda p: p.promo_applicability == 'on_current_order')._filter_programs_from_common_rules(self)
|
||||
return programs
|
||||
|
||||
def _create_new_no_code_promo_reward_lines(self):
|
||||
@@ -334,18 +354,26 @@ class SaleOrder(models.Model):
|
||||
self.ensure_one()
|
||||
order = self
|
||||
|
||||
applicable_programs = order._get_applicable_no_code_promo_program() + order._get_applicable_programs() + order._get_applied_coupon_program_coming_from_another_so()
|
||||
applied_programs = order._get_applied_programs_with_rewards_on_current_order()
|
||||
applicable_programs = order._get_applicable_no_code_promo_program() + order._get_applicable_programs() + order._get_valid_applied_coupon_program()
|
||||
applied_programs = order._get_applied_programs_with_rewards_on_current_order() + order._get_applied_programs_with_rewards_on_next_order()
|
||||
programs_to_remove = applied_programs - applicable_programs
|
||||
products_to_remove = programs_to_remove.mapped('discount_line_product_id')
|
||||
|
||||
# delete reward line coming from an archived coupon (it will never be updated/removed when recomputing the order)
|
||||
invalid_lines = order.order_line.filtered(lambda line: line.is_reward_line and line.product_id.id not in (applied_programs).mapped('discount_line_product_id').ids)
|
||||
|
||||
# Invalid generated coupon for which we are not eligible anymore ('expired' since it is specific to this SO and we may again met the requirements)
|
||||
self.generated_coupon_ids.filtered(lambda coupon: coupon.program_id.discount_line_product_id.id in products_to_remove.ids).write({'state': 'expired'})
|
||||
# Reset applied coupons for which we are not eligible anymore ('valid' so it can be use on another )
|
||||
coupons_to_remove = order.applied_coupon_ids.filtered(lambda coupon: coupon.program_id in programs_to_remove)
|
||||
coupons_to_remove.write({'state': 'new'})
|
||||
|
||||
# Unbind promotion and coupon programs which requirements are not met anymore
|
||||
order.no_code_promo_program_ids -= programs_to_remove
|
||||
order.code_promo_program_id -= programs_to_remove
|
||||
order.applied_coupon_ids -= order.applied_coupon_ids.filtered(lambda coupon: coupon.program_id in programs_to_remove)
|
||||
order.applied_coupon_ids -= coupons_to_remove
|
||||
|
||||
# Remove their reward lines
|
||||
invalid_lines |= order.order_line.filtered(lambda line: line.product_id.id in products_to_remove.ids)
|
||||
invalid_lines.unlink()
|
||||
|
||||
@@ -358,6 +386,10 @@ class SaleOrder(models.Model):
|
||||
self.applied_coupon_ids.mapped('program_id') + \
|
||||
self.code_promo_program_id.filtered(lambda p: p.promo_applicability == 'on_current_order')
|
||||
|
||||
def _get_applied_programs_with_rewards_on_next_order(self):
|
||||
return self.no_code_promo_program_ids.filtered(lambda p: p.promo_applicability == 'on_next_order') + \
|
||||
self.code_promo_program_id.filtered(lambda p: p.promo_applicability == 'on_next_order')
|
||||
|
||||
|
||||
class SaleOrderLine(models.Model):
|
||||
_inherit = "sale.order.line"
|
||||
|
||||
@@ -490,3 +490,44 @@ class TestSaleCouponProgramNumbers(TestSaleCouponCommon):
|
||||
fixed_amount_program.write({'active': False}) # Check archived product will remove discount lines on recompute
|
||||
order.recompute_coupon_lines()
|
||||
self.assertEqual(len(order.order_line.ids), 1, "Archiving the program should remove the program reward line")
|
||||
|
||||
def test_program_next_order(self):
|
||||
order = self.empty_order
|
||||
self.env['sale.coupon.program'].create({
|
||||
'name': 'Free Keyboard if at least 1 article',
|
||||
'promo_code_usage': 'no_code_needed',
|
||||
'promo_applicability': 'on_next_order',
|
||||
'program_type': 'promotion_program',
|
||||
'reward_type': 'product',
|
||||
'reward_product_id': self.wirelessKeyboard.id,
|
||||
'rule_min_quantity': 2,
|
||||
})
|
||||
sol1 = self.env['sale.order.line'].create({
|
||||
'product_id': self.iPadMini.id,
|
||||
'name': 'iPad Mini',
|
||||
'product_uom_qty': 1.0,
|
||||
'order_id': order.id,
|
||||
})
|
||||
order.recompute_coupon_lines()
|
||||
self.assertEqual(len(order.order_line.ids), 1, "Nothing should be added to the cart")
|
||||
self.assertEqual(len(order.generated_coupon_ids), 0, "No coupon should have been generated yet")
|
||||
|
||||
sol1.product_uom_qty = 2
|
||||
order.recompute_coupon_lines()
|
||||
generated_coupon = order.generated_coupon_ids
|
||||
self.assertEqual(len(order.order_line.ids), 1, "Nothing should be added to the cart (2)")
|
||||
self.assertEqual(len(generated_coupon), 1, "A coupon should have been generated")
|
||||
self.assertEqual(generated_coupon.state, 'reserved', "The coupon should be reserved")
|
||||
|
||||
sol1.product_uom_qty = 1
|
||||
order.recompute_coupon_lines()
|
||||
generated_coupon = order.generated_coupon_ids
|
||||
self.assertEqual(len(order.order_line.ids), 1, "Nothing should be added to the cart (3)")
|
||||
self.assertEqual(len(generated_coupon), 1, "No more coupon should have been generated and the existing one should not have been deleted")
|
||||
self.assertEqual(generated_coupon.state, 'expired', "The coupon should have been set as expired as it is no more valid since we don't have the required quantity")
|
||||
|
||||
sol1.product_uom_qty = 2
|
||||
order.recompute_coupon_lines()
|
||||
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")
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from openerp.addons.sale_coupon.tests.common import TestSaleCouponCommon
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.fields import Date
|
||||
|
||||
class TestProgramRules(TestSaleCouponCommon):
|
||||
@@ -171,3 +172,71 @@ class TestProgramRules(TestSaleCouponCommon):
|
||||
]})
|
||||
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")
|
||||
|
||||
def test_program_rules_coupon_qty_and_amount_remove_not_eligible(self):
|
||||
''' This test will:
|
||||
* Check quantity and amount requirements works as expected (since it's slightly different from a promotion_program)
|
||||
* Ensure that if a reward from a coupon_program was allowed and the conditions are not met anymore,
|
||||
the reward will be removed on recompute.
|
||||
'''
|
||||
self.immediate_promotion_program.active = False # Avoid having this program to add rewards on this test
|
||||
order = self.empty_order
|
||||
|
||||
program = self.env['sale.coupon.program'].create({
|
||||
'name': 'Get 10% discount if buy at least 4 Product A and $320',
|
||||
'program_type': 'coupon_program',
|
||||
'reward_type': 'discount',
|
||||
'discount_type': 'percentage',
|
||||
'discount_percentage': 10.0,
|
||||
'rule_products_domain': "[('id', 'in', [%s])]" % (self.product_A.id),
|
||||
'rule_min_quantity': 3,
|
||||
'rule_minimum_amount': 320.00,
|
||||
})
|
||||
|
||||
sol1 = self.env['sale.order.line'].create({
|
||||
'product_id': self.product_A.id,
|
||||
'name': 'Product A',
|
||||
'product_uom_qty': 2.0,
|
||||
'order_id': order.id,
|
||||
})
|
||||
|
||||
sol2 = self.env['sale.order.line'].create({
|
||||
'product_id': self.product_B.id,
|
||||
'name': 'Product B',
|
||||
'product_uom_qty': 4.0,
|
||||
'order_id': order.id,
|
||||
})
|
||||
|
||||
# Default value for coupon generate wizard is generate by quantity and generate only one coupon
|
||||
self.env['sale.coupon.generate'].with_context(active_id=program.id).create({}).generate_coupon()
|
||||
coupon = program.coupon_ids[0]
|
||||
|
||||
# Not enough amount since we only have 220 (100*2 + 5*4)
|
||||
with self.assertRaises(UserError):
|
||||
self.env['sale.coupon.apply.code'].with_context(active_id=order.id).create({
|
||||
'coupon_code': coupon.code
|
||||
}).process_coupon()
|
||||
|
||||
sol2.product_uom_qty = 24
|
||||
|
||||
# Not enough qty since we only have 3 Product A (Amount is ok: 100*2 + 5*24 = 320)
|
||||
with self.assertRaises(UserError):
|
||||
self.env['sale.coupon.apply.code'].with_context(active_id=order.id).create({
|
||||
'coupon_code': coupon.code
|
||||
}).process_coupon()
|
||||
|
||||
sol1.product_uom_qty = 3
|
||||
|
||||
self.env['sale.coupon.apply.code'].with_context(active_id=order.id).create({
|
||||
'coupon_code': coupon.code
|
||||
}).process_coupon()
|
||||
order.recompute_coupon_lines()
|
||||
|
||||
self.assertEqual(len(order.order_line.ids), 3, "The order should contains the Product A line, the Product B line and the discount line")
|
||||
self.assertEqual(coupon.state, 'used', "The coupon should be set to Consumed as it has been used")
|
||||
|
||||
sol1.product_uom_qty = 2
|
||||
order.recompute_coupon_lines()
|
||||
|
||||
self.assertEqual(len(order.order_line.ids), 2, "The discount line should have been removed as we don't meet the program requirements")
|
||||
self.assertEqual(coupon.state, 'new', "The coupon should be reset to Valid as it's reward got removed")
|
||||
|
||||
Reference in New Issue
Block a user