[MERGE] forward port branch saas-16 up to 32396ec64e4

This commit is contained in:
Christophe Simonis
2017-06-27 14:23:37 +02:00
11 changed files with 66 additions and 53 deletions
+4
View File
@@ -72,6 +72,10 @@ 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 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'.")}
else:
if self.program_id not in applicable_programs and self.program_id.promo_applicability == 'on_current_order':
message = {'error': _('At least one of the required conditions is not met to get the reward!')}
+25 -6
View File
@@ -3,6 +3,7 @@
from odoo import api, fields, models, _
from odoo.exceptions import UserError
from odoo.tools.safe_eval import safe_eval
from odoo.tools import pycompat
@@ -149,6 +150,10 @@ class SaleCouponProgram(models.Model):
message = {'error': _('Global discounts are not cumulative.')}
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 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'.")}
else:
if self not in applicable_programs:
message = {'error': _('At least one of the required conditions is not met to get the reward!')}
@@ -185,7 +190,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):
"""
@@ -194,17 +199,17 @@ 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(
qty for product, qty in pycompat.items(products_qties)
if product in program.rule_product_ids.ids
if program._is_valid_product(product)
)
# 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 +241,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
+7 -11
View File
@@ -12,12 +12,13 @@ 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", default=[['customer', '=', True]], help="Coupon program will work for selected customers only")
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,9 @@ class SaleCouponRule(models.Model):
if self.filtered(lambda applicability: applicability.rule_minimum_amount < 0):
raise ValidationError(_('Minimum purchased amount should be greater than 0'))
@api.depends('rule_partners_domain')
# YTI TODO Remove in master
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
+6 -33
View File
@@ -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()
+3
View File
@@ -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',
+1
View File
@@ -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")
+7
View File
@@ -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")
@@ -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)
@@ -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")
+1 -3
View File
@@ -23,7 +23,6 @@
<group name="conditions" string="Conditions">
<field name="program_type" invisible="1"/>
<field name="rule_products_domain" placeholder="Select product" widget="domain" options="{'model': 'product.product'}"/>
<field name="rule_product_ids" widget="many2many_tags" invisible="1"/>
<label string="Quantity" for="rule_min_quantity" attrs="{'invisible': [('rule_products_domain', '=', False)]}"/>
<div attrs="{'invisible': [('rule_products_domain', '=',False)]}">
<field name="rule_min_quantity" class="oe_inline"/>
@@ -45,7 +44,7 @@
<group string="Rewards">
<group name='reward'>
<field name="reward_type" widget="radio"/>
<field name="discount_line_product_id"/>
<field name="discount_line_product_id" attrs="{'invisible': [('discount_line_product_id', '=', False)]}" readonly="True"/>
</group>
<group>
<field name="reward_product_id" attrs="{'invisible': [('reward_type', 'in', ('discount', 'free_shipping'))], 'required': [('reward_type', '=', 'product')]}" placeholder="Select reward product"/>
@@ -225,7 +224,6 @@
</xpath>
<xpath expr="//field[@name='program_type']" position="after">
<field name="rule_partners_domain" placeholder="Select customer" widget="domain" options="{'model': 'res.partner'}"/>
<field name="rule_partner_ids" widget="many2many_tags" invisible="1" domain="[('is_customer', '=', True)]"/>
</xpath>
<xpath expr="//div[@name='rule_minimum_amount']" position="after">
<field name="promo_code_usage" widget="radio"/>
+3
View File
@@ -8,6 +8,9 @@
<xpath expr="//header/button[@name='action_cancel']" position="before">
<button name="%(sale_coupon.sale_coupon_apply_code_action)d" string="Apply Coupon" type="action" groups="base.group_user" states="draft"/>
</xpath>
<xpath expr="//button[@name='action_done']" position="after">
<button name="recompute_coupon_lines" string="Update Promotions" type="object" attrs="{'invisible': [('state', 'in', ['sale', 'done', 'cancel'])]}"/>
</xpath>
</field>
</record>