[MERGE] forward port branch saas-11.3 up to a9f3cf76b93

This commit is contained in:
Christophe Simonis
2018-11-16 14:27:04 +01:00
4 changed files with 161 additions and 15 deletions
+8 -4
View File
@@ -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
View File
@@ -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"
+41
View File
@@ -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")
+69
View File
@@ -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")