mirror of
https://github.com/bringout/oca-ocb-sale.git
synced 2026-04-27 08:12:08 +02:00
19.0 vanilla
This commit is contained in:
parent
79f83631d5
commit
73afc09215
6267 changed files with 1534193 additions and 1130106 deletions
|
|
@ -1,13 +1,14 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import test_buy_gift_card
|
||||
from . import test_loyalty
|
||||
from . import test_loyalty_history
|
||||
from . import test_pay_with_gift_card
|
||||
from . import test_program_multi_company
|
||||
from . import test_program_numbers
|
||||
from . import test_program_rules
|
||||
from . import test_program_with_code_operations
|
||||
from . import test_program_without_code_operations
|
||||
from . import test_sale_auto_invoice
|
||||
from . import test_sale_invoicing
|
||||
from . import test_unlink_reward
|
||||
|
|
|
|||
|
|
@ -1,86 +1,75 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from collections import defaultdict
|
||||
|
||||
from odoo import Command
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.fields import Command
|
||||
|
||||
from odoo.addons.sale.tests.test_sale_product_attribute_value_config import TestSaleProductAttributeValueCommon
|
||||
from odoo.addons.sale.tests.common import SaleCommon
|
||||
|
||||
|
||||
class TestSaleCouponCommon(TestSaleProductAttributeValueCommon):
|
||||
class TestSaleCouponCommon(SaleCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls, chart_template_ref=None):
|
||||
super().setUpClass(chart_template_ref=chart_template_ref)
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
# set currency to not rely on demo data and avoid possible race condition
|
||||
cls.currency_ratio = 1.0
|
||||
pricelist = cls.env.ref('product.list0')
|
||||
pricelist.currency_id = cls._setup_currency(cls.currency_ratio)
|
||||
|
||||
# Disable noisy pricelist (aka demo data Benelux)
|
||||
cls.env.user.partner_id.write({
|
||||
'property_product_pricelist': pricelist.id,
|
||||
})
|
||||
(cls.env['product.pricelist'].search([]) - pricelist).write({'active': False})
|
||||
|
||||
# Set all the existing programs to active=False to avoid interference
|
||||
cls.env['loyalty.program'].search([]).sudo().write({'active': False})
|
||||
|
||||
# create partner for sale order.
|
||||
cls.steve = cls.env['res.partner'].create({
|
||||
'name': 'Steve Bucknor',
|
||||
'email': 'steve.bucknor@example.com',
|
||||
})
|
||||
|
||||
cls.empty_order = cls.env['sale.order'].create({
|
||||
'partner_id': cls.steve.id
|
||||
})
|
||||
|
||||
cls.uom_unit = cls.env.ref('uom.product_uom_unit')
|
||||
|
||||
# Taxes
|
||||
tax_group_group = cls.env['account.tax.group'].create({
|
||||
'name': 'Test Account Tax Group'
|
||||
})
|
||||
cls.tax_15pc_excl = cls.env['account.tax'].create({
|
||||
'name': "Tax 15%",
|
||||
'amount_type': 'percent',
|
||||
'amount': 15,
|
||||
'type_tax_use': 'sale',
|
||||
'tax_group_id': tax_group_group.id,
|
||||
})
|
||||
|
||||
cls.tax_10pc_incl = cls.env['account.tax'].create({
|
||||
'name': "10% Tax incl",
|
||||
'amount_type': 'percent',
|
||||
'amount': 10,
|
||||
'price_include': True,
|
||||
'price_include_override': 'tax_included',
|
||||
'tax_group_id': tax_group_group.id,
|
||||
})
|
||||
|
||||
cls.tax_10pc_base_incl = cls.env['account.tax'].create({
|
||||
'name': "10% Tax incl base amount",
|
||||
'amount_type': 'percent',
|
||||
'amount': 10,
|
||||
'price_include': True,
|
||||
'price_include_override': 'tax_included',
|
||||
'include_base_amount': True,
|
||||
'tax_group_id': tax_group_group.id,
|
||||
})
|
||||
|
||||
cls.tax_10pc_excl = cls.env['account.tax'].create({
|
||||
'name': "10% Tax excl",
|
||||
'amount_type': 'percent',
|
||||
'amount': 10,
|
||||
'price_include': False,
|
||||
'price_include_override': 'tax_excluded',
|
||||
'tax_group_id': tax_group_group.id,
|
||||
})
|
||||
|
||||
cls.tax_20pc_excl = cls.env['account.tax'].create({
|
||||
'name': "20% Tax excl",
|
||||
'amount_type': 'percent',
|
||||
'amount': 20,
|
||||
'price_include': False,
|
||||
'price_include_override': 'tax_excluded',
|
||||
'tax_group_id': tax_group_group.id,
|
||||
})
|
||||
|
||||
cls.tax_group = cls.env['account.tax'].create({
|
||||
'name': "tax_group",
|
||||
'amount_type': 'group',
|
||||
'children_tax_ids': [Command.set((cls.tax_10pc_incl + cls.tax_10pc_base_incl).ids)],
|
||||
'tax_group_id': tax_group_group.id,
|
||||
})
|
||||
|
||||
#products
|
||||
|
|
@ -114,7 +103,7 @@ class TestSaleCouponCommon(TestSaleProductAttributeValueCommon):
|
|||
|
||||
cls.product_gift_card = cls.env['product.product'].create({
|
||||
'name': 'Gift Card 50',
|
||||
'detailed_type': 'service',
|
||||
'type': 'service',
|
||||
'list_price': 50,
|
||||
'sale_ok': True,
|
||||
'taxes_id': False,
|
||||
|
|
@ -223,6 +212,8 @@ class TestSaleCouponCommon(TestSaleProductAttributeValueCommon):
|
|||
status = order._apply_program_reward(rewards, coupons)
|
||||
if 'error' in status:
|
||||
raise ValidationError(status['error'])
|
||||
elif len(coupons) == 1 and len(rewards) > 1:
|
||||
return rewards
|
||||
|
||||
def _claim_reward(self, order, program, coupon=False):
|
||||
if len(program.reward_ids) != 1:
|
||||
|
|
@ -243,6 +234,12 @@ class TestSaleCouponCommon(TestSaleProductAttributeValueCommon):
|
|||
continue
|
||||
self._claim_reward(order, program, coupons_per_program[program])
|
||||
|
||||
def _generate_coupons(self, loyality_program, coupon_qty=1):
|
||||
self.env['loyalty.generate.wizard'].with_context(active_id=loyality_program.id).create({
|
||||
'coupon_qty': coupon_qty,
|
||||
}).generate_coupons()
|
||||
return loyality_program.coupon_ids
|
||||
|
||||
class TestSaleCouponNumbersCommon(TestSaleCouponCommon):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
|
|
@ -274,14 +271,6 @@ class TestSaleCouponNumbersCommon(TestSaleCouponCommon):
|
|||
'taxes_id': False,
|
||||
})
|
||||
|
||||
cls.steve = cls.env['res.partner'].create({
|
||||
'name': 'Steve Bucknor',
|
||||
'email': 'steve.bucknor@example.com',
|
||||
})
|
||||
cls.empty_order = cls.env['sale.order'].create({
|
||||
'partner_id': cls.steve.id
|
||||
})
|
||||
|
||||
cls.p1 = cls.env['loyalty.program'].create({
|
||||
'name': 'Code for 10% on orders',
|
||||
'trigger': 'with_code',
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import Command
|
||||
from odoo.tests.common import tagged
|
||||
|
||||
from odoo.addons.sale_loyalty.tests.common import TestSaleCouponCommon
|
||||
from odoo.tests.common import tagged
|
||||
|
||||
|
||||
@tagged('-at_install', 'post_install')
|
||||
class TestBuyGiftCard(TestSaleCouponCommon):
|
||||
|
|
@ -16,13 +16,11 @@ class TestBuyGiftCard(TestSaleCouponCommon):
|
|||
(0, False, {
|
||||
'product_id': self.product_A.id,
|
||||
'name': 'Ordinary Product A',
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 1.0,
|
||||
}),
|
||||
(0, False, {
|
||||
'product_id': self.product_gift_card.id,
|
||||
'name': 'Gift Card Product',
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 1.0,
|
||||
})
|
||||
]})
|
||||
|
|
|
|||
|
|
@ -1,11 +1,15 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import Command
|
||||
from odoo.tests import tagged, new_test_user
|
||||
from freezegun import freeze_time
|
||||
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.fields import Command
|
||||
from odoo.tests import new_test_user, tagged
|
||||
from odoo.tools.float_utils import float_compare
|
||||
|
||||
from odoo.addons.sale_loyalty.tests.common import TestSaleCouponCommon
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestLoyalty(TestSaleCouponCommon):
|
||||
|
||||
|
|
@ -14,14 +18,18 @@ class TestLoyalty(TestSaleCouponCommon):
|
|||
super().setUpClass()
|
||||
cls.env['loyalty.program'].search([]).write({'active': False})
|
||||
|
||||
cls.partner_a = cls.env['res.partner'].create({'name': 'Jean Jacques'})
|
||||
|
||||
cls.product_a = cls.env['product.product'].create({
|
||||
'name': 'Product C',
|
||||
'list_price': 100,
|
||||
'sale_ok': True,
|
||||
'taxes_id': [(6, 0, [])],
|
||||
})
|
||||
cls.product_a, cls.product_b = cls.env['product.product'].create([
|
||||
{
|
||||
'name': 'Product C',
|
||||
'list_price': 100,
|
||||
'sale_ok': True,
|
||||
'taxes_id': [Command.set([])],
|
||||
},
|
||||
{
|
||||
'name': "Product B",
|
||||
'sale_ok': True,
|
||||
}
|
||||
])
|
||||
|
||||
cls.ewallet_program = cls.env['loyalty.program'].create({
|
||||
'name': 'eWallet Program',
|
||||
|
|
@ -43,7 +51,7 @@ class TestLoyalty(TestSaleCouponCommon):
|
|||
|
||||
cls.ewallet = cls.env['loyalty.card'].create({
|
||||
'program_id': cls.ewallet_program.id,
|
||||
'partner_id': cls.partner_a.id,
|
||||
'partner_id': cls.partner.id,
|
||||
'points': 10,
|
||||
})
|
||||
cls.ewallet_program.coupon_ids = [Command.set([cls.ewallet.id])]
|
||||
|
|
@ -88,16 +96,14 @@ class TestLoyalty(TestSaleCouponCommon):
|
|||
})],
|
||||
})
|
||||
|
||||
order = self.env['sale.order'].create({
|
||||
'partner_id': self.partner_a.id,
|
||||
})
|
||||
order = self.empty_order
|
||||
order._update_programs_and_rewards()
|
||||
claimable_rewards = order._get_claimable_rewards()
|
||||
# Should be empty since we do not have any coupon created yet
|
||||
self.assertFalse(claimable_rewards, "No program should be applicable")
|
||||
loyalty_card = self.env['loyalty.card'].create({
|
||||
'program_id': loyalty_program.id,
|
||||
'partner_id': self.partner_a.id,
|
||||
'partner_id': self.partner.id,
|
||||
'points': 10,
|
||||
})
|
||||
self.ewallet.points = 0
|
||||
|
|
@ -142,7 +148,7 @@ class TestLoyalty(TestSaleCouponCommon):
|
|||
})
|
||||
|
||||
order = self.env['sale.order'].with_user(self.user_salemanager).create({
|
||||
'partner_id': self.partner_a.id,
|
||||
'partner_id': self.partner.id,
|
||||
'order_line': [
|
||||
(0, 0, {
|
||||
'product_id': self.product_a.id,
|
||||
|
|
@ -216,20 +222,20 @@ class TestLoyalty(TestSaleCouponCommon):
|
|||
coupon_partner, _ = self.env['loyalty.card'].create([
|
||||
{
|
||||
'program_id': coupon_program.id,
|
||||
'partner_id': self.partner_a.id,
|
||||
'partner_id': self.partner.id,
|
||||
'points': 1,
|
||||
'code': '5555',
|
||||
},
|
||||
{
|
||||
'program_id': ewallet_program.id,
|
||||
'partner_id': self.partner_a.id,
|
||||
'partner_id': self.partner.id,
|
||||
'points': 115,
|
||||
},
|
||||
])
|
||||
|
||||
# Create the order
|
||||
order = self.env['sale.order'].with_user(self.user_salemanager).create({
|
||||
'partner_id': self.partner_a.id,
|
||||
'partner_id': self.partner.id,
|
||||
'order_line': [
|
||||
Command.create({
|
||||
'product_id': product_a.id,
|
||||
|
|
@ -282,7 +288,7 @@ class TestLoyalty(TestSaleCouponCommon):
|
|||
})
|
||||
|
||||
order = self.env['sale.order'].create({
|
||||
'partner_id': self.partner_a.id,
|
||||
'partner_id': self.partner.id,
|
||||
'order_line': [Command.create({'product_id': product_a.id})],
|
||||
})
|
||||
self.assertEqual(order.reward_amount, 0)
|
||||
|
|
@ -338,16 +344,16 @@ class TestLoyalty(TestSaleCouponCommon):
|
|||
|
||||
loyalty_card = self.env['loyalty.card'].create({
|
||||
'program_id': loyalty_program.id,
|
||||
'partner_id': self.partner_a.id,
|
||||
'partner_id': self.partner.id,
|
||||
'points': 0,
|
||||
})
|
||||
|
||||
order = self.env['sale.order'].with_user(self.user_salemanager).create({
|
||||
'partner_id': self.partner_a.id,
|
||||
'partner_id': self.partner.id,
|
||||
'order_line': [
|
||||
Command.create({
|
||||
'product_id': self.product_A.id,
|
||||
'tax_id': False,
|
||||
'tax_ids': False,
|
||||
}),
|
||||
]
|
||||
})
|
||||
|
|
@ -376,6 +382,61 @@ class TestLoyalty(TestSaleCouponCommon):
|
|||
order.action_confirm()
|
||||
self.assertEqual(loyalty_card.points, 90)
|
||||
|
||||
def test_multiple_rewards_after_confirm(self):
|
||||
"""
|
||||
Check that multiple rewards from a loyalty promotion program are correctly applied to a SO
|
||||
after its confirmation by asserting that:
|
||||
- Both rewards are applied to the order lines.
|
||||
- The total points cost matches the rule's requirement.
|
||||
- The coupon's points are fully consumed after applying the rewards.
|
||||
"""
|
||||
promo_program = self.env['loyalty.program'].create({
|
||||
'name': 'Multiple Rewards Promotion',
|
||||
'program_type': 'promotion',
|
||||
'applies_on': 'current',
|
||||
'company_id': self.env.company.id,
|
||||
'trigger': 'auto',
|
||||
'rule_ids': [
|
||||
Command.create({
|
||||
'product_ids': self.product_A,
|
||||
'reward_point_amount': 1,
|
||||
'reward_point_mode': 'order',
|
||||
'minimum_qty': 1,
|
||||
}),
|
||||
],
|
||||
'reward_ids': [
|
||||
Command.create({
|
||||
'discount': 10,
|
||||
'discount_applicability': 'specific',
|
||||
'discount_product_ids': [self.product_A.id],
|
||||
'required_points': 0.5,
|
||||
}),
|
||||
Command.create({
|
||||
'discount': 15,
|
||||
'discount_applicability': 'specific',
|
||||
'discount_product_ids': [self.product_B.id],
|
||||
'required_points': 0.5,
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
order = self.empty_order
|
||||
order.order_line = [
|
||||
Command.create({'product_id': self.product_A.id, 'product_uom_qty': 1}),
|
||||
Command.create({'product_id': self.product_B.id, 'product_uom_qty': 1}),
|
||||
]
|
||||
order.action_confirm()
|
||||
|
||||
order._update_programs_and_rewards()
|
||||
coupon = order.coupon_point_ids.coupon_id.filtered(lambda c: c.program_id == promo_program)
|
||||
reward1, reward2 = rewards = promo_program.reward_ids
|
||||
order._apply_program_reward(reward1, coupon)
|
||||
order._apply_program_reward(reward2, coupon)
|
||||
|
||||
self.assertEqual(order.order_line.reward_id, rewards, "All rewards should be applied")
|
||||
self.assertEqual(sum(order.order_line.mapped('points_cost')), 1)
|
||||
self.assertEqual(coupon.points, 0)
|
||||
|
||||
def test_points_awarded_discount_code_no_domain_program(self):
|
||||
"""
|
||||
Check the calculation for points awarded when there is a discount coupon applied and the
|
||||
|
|
@ -385,16 +446,16 @@ class TestLoyalty(TestSaleCouponCommon):
|
|||
loyalty_program = LoyaltyProgram.create(LoyaltyProgram._get_template_values()['loyalty'])
|
||||
loyalty_card = self.env['loyalty.card'].create({
|
||||
'program_id': loyalty_program.id,
|
||||
'partner_id': self.partner_a.id,
|
||||
'partner_id': self.partner.id,
|
||||
'points': 0,
|
||||
})
|
||||
|
||||
order = self.env['sale.order'].with_user(self.user_salemanager).create({
|
||||
'partner_id': self.partner_a.id,
|
||||
'partner_id': self.partner.id,
|
||||
'order_line': [
|
||||
Command.create({
|
||||
'product_id': self.product_A.id,
|
||||
'tax_id': False,
|
||||
'tax_ids': False,
|
||||
}),
|
||||
]
|
||||
})
|
||||
|
|
@ -412,10 +473,8 @@ class TestLoyalty(TestSaleCouponCommon):
|
|||
related to that discount is not in the domain of the loyalty program.
|
||||
Expected behavior: The discount is not included in the computation of points
|
||||
"""
|
||||
product_category_base = self.env.ref('product.product_category_1')
|
||||
product_category_food = self.env['product.category'].create({
|
||||
'name': "Food",
|
||||
'parent_id': product_category_base.id
|
||||
})
|
||||
|
||||
self.product_A.categ_id = product_category_food
|
||||
|
|
@ -426,20 +485,20 @@ class TestLoyalty(TestSaleCouponCommon):
|
|||
loyalty_program.rule_ids.product_category_id = product_category_food.id
|
||||
loyalty_card = self.env['loyalty.card'].create({
|
||||
'program_id': loyalty_program.id,
|
||||
'partner_id': self.partner_a.id,
|
||||
'partner_id': self.partner.id,
|
||||
'points': 0,
|
||||
})
|
||||
|
||||
order = self.env['sale.order'].with_user(self.user_salemanager).create({
|
||||
'partner_id': self.partner_a.id,
|
||||
'partner_id': self.partner.id,
|
||||
'order_line': [
|
||||
Command.create({
|
||||
'product_id': self.product_A.id,
|
||||
'tax_id': False,
|
||||
'tax_ids': False,
|
||||
}),
|
||||
Command.create({
|
||||
'product_id': self.product_B.id,
|
||||
'tax_id': False,
|
||||
'tax_ids': False,
|
||||
}),
|
||||
]
|
||||
})
|
||||
|
|
@ -458,10 +517,8 @@ class TestLoyalty(TestSaleCouponCommon):
|
|||
domain of the loyalty program.
|
||||
Expected behavior: The discount is included in the computation of points
|
||||
"""
|
||||
product_category_base = self.env.ref('product.product_category_1')
|
||||
product_category_food = self.env['product.category'].create({
|
||||
'name': "Food",
|
||||
'parent_id': product_category_base.id
|
||||
})
|
||||
|
||||
self.product_A.categ_id = product_category_food
|
||||
|
|
@ -472,7 +529,7 @@ class TestLoyalty(TestSaleCouponCommon):
|
|||
loyalty_program.rule_ids.product_category_id = product_category_food.id
|
||||
loyalty_card = self.env['loyalty.card'].create({
|
||||
'program_id': loyalty_program.id,
|
||||
'partner_id': self.partner_a.id,
|
||||
'partner_id': self.partner.id,
|
||||
'points': 0,
|
||||
})
|
||||
|
||||
|
|
@ -484,15 +541,15 @@ class TestLoyalty(TestSaleCouponCommon):
|
|||
discount_product.categ_id = product_category_food.id
|
||||
|
||||
order = self.env['sale.order'].with_user(self.user_salemanager).create({
|
||||
'partner_id': self.partner_a.id,
|
||||
'partner_id': self.partner.id,
|
||||
'order_line': [
|
||||
Command.create({
|
||||
'product_id': self.product_A.id,
|
||||
'tax_id': False,
|
||||
'tax_ids': False,
|
||||
}),
|
||||
Command.create({
|
||||
'product_id': self.product_B.id,
|
||||
'tax_id': False,
|
||||
'tax_ids': False,
|
||||
}),
|
||||
]
|
||||
})
|
||||
|
|
@ -511,15 +568,15 @@ class TestLoyalty(TestSaleCouponCommon):
|
|||
loyalty_program = LoyaltyProgram.create(LoyaltyProgram._get_template_values()['loyalty'])
|
||||
loyalty_card = self.env['loyalty.card'].create({
|
||||
'program_id': loyalty_program.id,
|
||||
'partner_id': self.partner_a.id,
|
||||
'partner_id': self.partner.id,
|
||||
'points': 0,
|
||||
})
|
||||
order = self.env['sale.order'].with_user(self.user_salemanager).create({
|
||||
'partner_id': self.partner_a.id,
|
||||
'partner_id': self.partner.id,
|
||||
'order_line': [
|
||||
Command.create({
|
||||
'product_id': self.product_A.id,
|
||||
'tax_id': False,
|
||||
'tax_ids': False,
|
||||
}),
|
||||
]
|
||||
})
|
||||
|
|
@ -539,7 +596,7 @@ class TestLoyalty(TestSaleCouponCommon):
|
|||
loyalty_program = LoyaltyProgram.create(LoyaltyProgram._get_template_values()['loyalty'])
|
||||
loyalty_card = self.env['loyalty.card'].create({
|
||||
'program_id': loyalty_program.id,
|
||||
'partner_id': self.partner_a.id,
|
||||
'partner_id': self.partner.id,
|
||||
'points': 0,
|
||||
})
|
||||
|
||||
|
|
@ -563,11 +620,11 @@ class TestLoyalty(TestSaleCouponCommon):
|
|||
gift_card = program_gift_card.coupon_ids[0]
|
||||
|
||||
order = self.env['sale.order'].with_user(self.user_salemanager).create({
|
||||
'partner_id': self.partner_a.id,
|
||||
'partner_id': self.partner.id,
|
||||
'order_line': [
|
||||
Command.create({
|
||||
'product_id': self.product_A.id,
|
||||
'tax_id': False,
|
||||
'tax_ids': False,
|
||||
}),
|
||||
]
|
||||
})
|
||||
|
|
@ -608,7 +665,7 @@ class TestLoyalty(TestSaleCouponCommon):
|
|||
}])
|
||||
|
||||
order = self.env['sale.order'].with_user(self.user_salemanager).create({
|
||||
'partner_id': self.partner_a.id,
|
||||
'partner_id': self.partner.id,
|
||||
'order_line': [Command.create({
|
||||
'product_id': product_A.id,
|
||||
'product_uom_qty': 3,
|
||||
|
|
@ -629,6 +686,133 @@ class TestLoyalty(TestSaleCouponCommon):
|
|||
self._claim_reward(order, coupon_program)
|
||||
self.assertEqual(float_compare(order.amount_total, 218.7, precision_rounding=3), 0, "300 * 0.9 * 0.9 * 0.9 = 218.7")
|
||||
|
||||
def test_promotion_program_restricted_to_pricelists(self):
|
||||
self.env['product.pricelist'].search([]).action_archive()
|
||||
company_currency = self.env.company.currency_id
|
||||
pricelist_1, pricelist_2 = self.env['product.pricelist'].create([
|
||||
{'name': 'Basic company_currency pricelist', 'currency_id': company_currency.id},
|
||||
{'name': 'Other company_currency pricelist', 'currency_id': company_currency.id},
|
||||
])
|
||||
self.immediate_promotion_program.active = True
|
||||
order = self.empty_order.copy()
|
||||
order.write({'order_line': [
|
||||
(0, False, {
|
||||
'product_id': self.product_A.id,
|
||||
'name': '1 Product A',
|
||||
'product_uom_qty': 1.0,
|
||||
}),
|
||||
(0, False, {
|
||||
'product_id': self.product_B.id,
|
||||
'name': '2 Product B',
|
||||
'product_uom_qty': 1.0,
|
||||
}),
|
||||
]})
|
||||
|
||||
applied_message = "The promo offer should have been applied."
|
||||
not_applied_message = "The promo offer should not have been applied because the order's " \
|
||||
"pricelist is not eligible to this promotion."
|
||||
|
||||
order.pricelist_id = self.env['product.pricelist']
|
||||
order._update_programs_and_rewards()
|
||||
self._claim_reward(order, self.immediate_promotion_program)
|
||||
self.assertEqual(len(order.order_line.ids), 3, applied_message)
|
||||
|
||||
order.pricelist_id = pricelist_1
|
||||
order._update_programs_and_rewards()
|
||||
self._claim_reward(order, self.immediate_promotion_program)
|
||||
self.assertEqual(len(order.order_line.ids), 3, applied_message)
|
||||
|
||||
self.immediate_promotion_program.pricelist_ids = [pricelist_1.id]
|
||||
order.pricelist_id = self.env['product.pricelist']
|
||||
order._update_programs_and_rewards()
|
||||
self._claim_reward(order, self.immediate_promotion_program)
|
||||
self.assertEqual(len(order.order_line.ids), 2, not_applied_message)
|
||||
|
||||
order.pricelist_id = pricelist_1
|
||||
order._update_programs_and_rewards()
|
||||
self._claim_reward(order, self.immediate_promotion_program)
|
||||
self.assertEqual(len(order.order_line.ids), 3, applied_message)
|
||||
|
||||
order.pricelist_id = pricelist_2
|
||||
order._update_programs_and_rewards()
|
||||
self._claim_reward(order, self.immediate_promotion_program)
|
||||
self.assertEqual(len(order.order_line.ids), 2, not_applied_message)
|
||||
|
||||
self.immediate_promotion_program.pricelist_ids = [pricelist_1.id, pricelist_2.id]
|
||||
order.pricelist_id = self.env['product.pricelist']
|
||||
order._update_programs_and_rewards()
|
||||
self._claim_reward(order, self.immediate_promotion_program)
|
||||
self.assertEqual(len(order.order_line.ids), 2, not_applied_message)
|
||||
|
||||
order.pricelist_id = pricelist_1
|
||||
order._update_programs_and_rewards()
|
||||
self._claim_reward(order, self.immediate_promotion_program)
|
||||
self.assertEqual(len(order.order_line.ids), 3, applied_message)
|
||||
|
||||
def test_coupon_program_restricted_to_pricelists(self):
|
||||
self.env['product.pricelist'].search([]).action_archive()
|
||||
company_currency = self.env.company.currency_id
|
||||
pricelist_1, pricelist_2 = self.env['product.pricelist'].create([
|
||||
{'name': 'Basic company_currency pricelist', 'currency_id': company_currency.id},
|
||||
{'name': 'Other company_currency pricelist', 'currency_id': company_currency.id},
|
||||
])
|
||||
|
||||
self.code_promotion_program.active = True
|
||||
self.env['loyalty.generate.wizard'].with_context(
|
||||
active_id=self.code_promotion_program.id
|
||||
).create({'coupon_qty': 7, 'points_granted': 1}).generate_coupons()
|
||||
coupons = self.code_promotion_program.coupon_ids
|
||||
|
||||
order_no_pricelist = self.empty_order.copy()
|
||||
order_no_pricelist.write({'pricelist_id': None, 'order_line': [
|
||||
(0, False, {
|
||||
'product_id': self.product_A.id,
|
||||
'name': '1 Product A',
|
||||
'product_uom_qty': 1.0,
|
||||
}),
|
||||
]})
|
||||
order_pricelist_1 = order_no_pricelist.copy()
|
||||
order_pricelist_1.pricelist_id = pricelist_1
|
||||
order_pricelist_2 = order_no_pricelist.copy()
|
||||
order_pricelist_2.pricelist_id = pricelist_2
|
||||
|
||||
applied_message = "The coupon code should have been applied."
|
||||
not_applied_message = "The coupon code should not have been applied because the order's " \
|
||||
"pricelist is not eligible to this promotion."
|
||||
|
||||
order_0 = order_no_pricelist.copy()
|
||||
self._apply_promo_code(order_0, coupons[0].code)
|
||||
self.assertEqual(len(order_0.order_line.ids), 2, applied_message)
|
||||
|
||||
order_1 = order_pricelist_1.copy()
|
||||
self._apply_promo_code(order_1, coupons[1].code)
|
||||
self.assertEqual(len(order_1.order_line.ids), 2, applied_message)
|
||||
|
||||
self.code_promotion_program.pricelist_ids = [pricelist_1.id]
|
||||
order_2 = order_no_pricelist.copy()
|
||||
with self.assertRaises(ValidationError):
|
||||
self._apply_promo_code(order_2, coupons[2].code)
|
||||
self.assertEqual(len(order_2.order_line.ids), 1, not_applied_message)
|
||||
|
||||
order_3 = order_pricelist_1.copy()
|
||||
self._apply_promo_code(order_3, coupons[3].code)
|
||||
self.assertEqual(len(order_3.order_line.ids), 2, applied_message)
|
||||
|
||||
order_4 = order_pricelist_2.copy()
|
||||
with self.assertRaises(ValidationError):
|
||||
self._apply_promo_code(order_4, coupons[4].code)
|
||||
self.assertEqual(len(order_4.order_line.ids), 1, not_applied_message)
|
||||
|
||||
self.code_promotion_program.pricelist_ids = [pricelist_1.id, pricelist_2.id]
|
||||
order_5 = order_no_pricelist.copy()
|
||||
with self.assertRaises(ValidationError):
|
||||
self._apply_promo_code(order_5, coupons[5].code)
|
||||
self.assertEqual(len(order_5.order_line.ids), 1, not_applied_message)
|
||||
|
||||
order_6 = order_pricelist_1.copy()
|
||||
self._apply_promo_code(order_6, coupons[6].code)
|
||||
self.assertEqual(len(order_6.order_line.ids), 2, applied_message)
|
||||
|
||||
def test_specific_promotion_on_free_product(self):
|
||||
|
||||
product_A = self.env['product.product'].create({
|
||||
|
|
@ -657,7 +841,7 @@ class TestLoyalty(TestSaleCouponCommon):
|
|||
}])
|
||||
|
||||
order = self.env['sale.order'].with_user(self.user_salemanager).create({
|
||||
'partner_id': self.partner_a.id,
|
||||
'partner_id': self.partner.id,
|
||||
'order_line': [
|
||||
Command.create({
|
||||
'product_id': product_A.id,
|
||||
|
|
@ -693,7 +877,7 @@ class TestLoyalty(TestSaleCouponCommon):
|
|||
}])
|
||||
|
||||
order = self.env['sale.order'].with_user(self.user_salemanager).create({
|
||||
'partner_id': self.partner_a.id,
|
||||
'partner_id': self.partner.id,
|
||||
'order_line': [
|
||||
Command.create({
|
||||
'product_id': product_A.id,
|
||||
|
|
@ -706,6 +890,24 @@ class TestLoyalty(TestSaleCouponCommon):
|
|||
|
||||
self.assertEqual(giftcard_program.coupon_count, 0)
|
||||
|
||||
def test_ewallet_code_use_restriction(self):
|
||||
self.env['loyalty.generate.wizard'].with_context(active_id=self.ewallet_program.id).create({
|
||||
'coupon_qty': 1,
|
||||
'points_granted': 100,
|
||||
}).generate_coupons()
|
||||
|
||||
order = self.env['sale.order'].with_user(self.user_salemanager).create({
|
||||
'partner_id': self.partner.id,
|
||||
'order_line': [
|
||||
Command.create({
|
||||
'product_id': self.product_a.id,
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
with self.assertRaises(ValidationError):
|
||||
self._apply_promo_code(order, self.ewallet_program.coupon_ids[0].code)
|
||||
|
||||
def test_100_percent_discount(self):
|
||||
"""
|
||||
Check whether a program offering 100% discount on an order reduces the order's total amount
|
||||
|
|
@ -734,10 +936,10 @@ class TestLoyalty(TestSaleCouponCommon):
|
|||
})],
|
||||
}])
|
||||
self.env['loyalty.card'].create({
|
||||
'program_id': loyalty_program.id, 'partner_id': self.partner_a.id, 'points': 2
|
||||
'program_id': loyalty_program.id, 'partner_id': self.partner.id, 'points': 2
|
||||
})
|
||||
order = self.env['sale.order'].create({
|
||||
'partner_id': self.partner_a.id,
|
||||
'partner_id': self.partner.id,
|
||||
'order_line': [(0, 0, {
|
||||
'product_id': self.product_A.id, 'product_uom_qty': 1, 'price_unit': price
|
||||
}) for price in (5.60, 8.92, 44.91, 217.26, 2400.00)],
|
||||
|
|
@ -771,9 +973,9 @@ class TestLoyalty(TestSaleCouponCommon):
|
|||
'required_points': 1,
|
||||
})],
|
||||
}])
|
||||
self.env['loyalty.card'].create({'program_id': loyalty_program.id, 'partner_id': self.partner_a.id, 'points': 2})
|
||||
self.env['loyalty.card'].create({'program_id': loyalty_program.id, 'partner_id': self.partner.id, 'points': 2})
|
||||
order = self.env['sale.order'].create({
|
||||
'partner_id': self.partner_a.id,
|
||||
'partner_id': self.partner.id,
|
||||
'order_line': [(0, 0, {'product_id': self.product_D.id, 'product_uom_qty': 1})],
|
||||
})
|
||||
|
||||
|
|
@ -787,7 +989,7 @@ class TestLoyalty(TestSaleCouponCommon):
|
|||
self.ewallet.points = 1000
|
||||
|
||||
order = self.env['sale.order'].create({
|
||||
'partner_id': self.partner_a.id,
|
||||
'partner_id': self.partner.id,
|
||||
'order_line': [Command.create({
|
||||
'product_id': self.product_a.id,
|
||||
'points_cost': 100,
|
||||
|
|
@ -804,7 +1006,7 @@ class TestLoyalty(TestSaleCouponCommon):
|
|||
self.ewallet.points = 10
|
||||
|
||||
order = self.env['sale.order'].create({
|
||||
'partner_id': self.partner_a.id,
|
||||
'partner_id': self.partner.id,
|
||||
'order_line': [Command.create({
|
||||
'product_id': self.product_a.id,
|
||||
'points_cost': 100,
|
||||
|
|
@ -820,6 +1022,47 @@ class TestLoyalty(TestSaleCouponCommon):
|
|||
|
||||
self.assertEqual(self.ewallet.points, 50)
|
||||
|
||||
def test_discount_reward_claimable_only_once(self):
|
||||
"""
|
||||
Check that discount rewards already applied won't be shown in the claimable rewards anymore.
|
||||
"""
|
||||
program = self.env['loyalty.program'].create({
|
||||
'name': "10% Discount & Gift",
|
||||
'applies_on': 'current',
|
||||
'trigger': 'with_code',
|
||||
'program_type': 'promotion',
|
||||
'rule_ids': [Command.create({'mode': 'with_code', 'code': "10PERCENT&GIFT"})],
|
||||
'reward_ids': [
|
||||
Command.create({
|
||||
'reward_type': 'product',
|
||||
'reward_product_id': self.product_B.id,
|
||||
'reward_product_qty': 1,
|
||||
}),
|
||||
Command.create({
|
||||
'reward_type': 'discount',
|
||||
'discount': 10,
|
||||
'discount_mode': 'percent',
|
||||
'discount_applicability': 'specific',
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
coupon = self.env['loyalty.card'].create({
|
||||
'program_id': program.id, 'points': 20, 'code': 'GIFT_CARD'
|
||||
})
|
||||
|
||||
order = self.env['sale.order'].create({
|
||||
'partner_id': self.partner.id,
|
||||
'order_line': [Command.create({'product_id': self.product_a.id})]
|
||||
})
|
||||
|
||||
product_reward = program.reward_ids.filtered(lambda reward: reward.reward_type == 'product')
|
||||
discount_reward = program.reward_ids - product_reward
|
||||
order._apply_program_reward(discount_reward, coupon)
|
||||
rewards = order._get_claimable_rewards()[coupon]
|
||||
msg = "Only the free product should be applicable, as the discount was already applied."
|
||||
self.assertEqual(rewards, product_reward, msg)
|
||||
|
||||
def test_archived_reward_products(self):
|
||||
"""
|
||||
Check that we do not use loyalty rewards that have no active reward product.
|
||||
|
|
@ -855,7 +1098,7 @@ class TestLoyalty(TestSaleCouponCommon):
|
|||
product_c.active = False
|
||||
|
||||
order = self.env['sale.order'].create({
|
||||
'partner_id': self.partner_a.id,
|
||||
'partner_id': self.partner.id,
|
||||
'order_line': [
|
||||
Command.create({
|
||||
'product_id': self.product_a.id,
|
||||
|
|
@ -873,43 +1116,206 @@ class TestLoyalty(TestSaleCouponCommon):
|
|||
rewards = [value.ids for value in order._get_claimable_rewards().values()]
|
||||
self.assertTrue(any(loyalty_program_tag.reward_ids[0].id in r for r in rewards))
|
||||
|
||||
def test_discount_reward_claimable_only_once(self):
|
||||
def test_domain_on_cheapest_reward(self):
|
||||
product_tag = self.env['product.tag'].create({'name': "Discountable"})
|
||||
self.env['loyalty.program'].create({
|
||||
'name': "10% Discount",
|
||||
'program_type': 'promo_code',
|
||||
'rule_ids': [Command.create({'code': "10discount"})],
|
||||
'reward_ids': [
|
||||
Command.create({
|
||||
'reward_type': 'discount',
|
||||
'discount': 10,
|
||||
'discount_mode': 'percent',
|
||||
'discount_applicability': 'cheapest',
|
||||
'discount_product_tag_id': product_tag.id,
|
||||
}),
|
||||
],
|
||||
})
|
||||
self.product_A.product_tag_ids = product_tag
|
||||
order = self.empty_order
|
||||
order.write({
|
||||
'order_line':[
|
||||
# product_A: lst_price: 100, Tax included price: 115
|
||||
Command.create({'product_id': self.product_A.id}),
|
||||
# Product_B: lst_price: 5, Tax included price: 5.75
|
||||
Command.create({'product_id': self.product_B.id}),
|
||||
]
|
||||
})
|
||||
self._apply_promo_code(order, '10discount')
|
||||
msg = "Discount should only be applied to the line with a correctly tagged product."
|
||||
self.assertEqual(order.order_line[2].price_total, -11.5, msg)
|
||||
|
||||
self.product_C.write({
|
||||
'list_price': 50,
|
||||
'product_tag_ids': product_tag,
|
||||
})
|
||||
order.order_line[2:].unlink()
|
||||
order.write({
|
||||
'order_line':[
|
||||
# product_C: lst_price = Tax included price: 50
|
||||
Command.create({'product_id': self.product_C.id}),
|
||||
]
|
||||
})
|
||||
self._apply_promo_code(order, '10discount')
|
||||
msg = "Discount should be applied to the line with the cheapest valid product."
|
||||
self.assertEqual(order.order_line[3].price_total, -5.0, msg)
|
||||
|
||||
def test_sol_free_product_description_equals_reward_description(self):
|
||||
"""
|
||||
Check that discount rewards already applied won't be shown in the claimable rewards anymore.
|
||||
Ensure that if a "Free Product" reward is added to a sale order,
|
||||
its line description matches the reward description.
|
||||
"""
|
||||
program = self.env['loyalty.program'].create({
|
||||
'name': '10% Discount & Gift',
|
||||
'applies_on': 'current',
|
||||
'trigger': 'with_code',
|
||||
loyalty_program = self.env['loyalty.program'].create(
|
||||
self.env['loyalty.program']._get_template_values()['buy_x_get_y']
|
||||
)
|
||||
reward = loyalty_program.reward_ids[0]
|
||||
updated_description = f"{reward.description} Adding manual description"
|
||||
reward.description = updated_description
|
||||
|
||||
order = self.empty_order
|
||||
order.write({
|
||||
'order_line': [
|
||||
Command.create({
|
||||
'product_id': reward.reward_product_id.id,
|
||||
'name': '1 Product',
|
||||
'product_uom_qty': 4.0,
|
||||
}),
|
||||
]
|
||||
})
|
||||
order._update_programs_and_rewards()
|
||||
self._claim_reward(order, loyalty_program)
|
||||
|
||||
self.assertEqual(len(order.order_line.ids), 2)
|
||||
self.assertEqual(order.order_line[1].name, updated_description)
|
||||
|
||||
def test_archiving_loyalty_card_unlinks_draft_points_from_sale_order(self):
|
||||
"""
|
||||
When a loyalty card has points accrued from a draft sale order, archiving the
|
||||
card should unlink those draft points so they are no longer claimable on that order
|
||||
"""
|
||||
loyalty_program = self.env['loyalty.program'].create({
|
||||
'name': 'Loyalty Program',
|
||||
'program_type': 'loyalty',
|
||||
'trigger': 'auto',
|
||||
'applies_on': 'both',
|
||||
'rule_ids': [
|
||||
Command.create({
|
||||
'reward_point_mode': 'unit',
|
||||
'reward_point_amount': 100,
|
||||
'product_ids': [self.product_a.id],
|
||||
}),
|
||||
],
|
||||
'reward_ids': [
|
||||
Command.create({
|
||||
'reward_type': 'discount',
|
||||
'discount': 50,
|
||||
'discount_mode': 'percent',
|
||||
'discount_applicability': 'order',
|
||||
'required_points': 10,
|
||||
}),
|
||||
],
|
||||
})
|
||||
loyalty_card = self.env['loyalty.card'].create({
|
||||
'program_id': loyalty_program.id,
|
||||
'partner_id': self.partner.id,
|
||||
'points': 0,
|
||||
})
|
||||
sale_order = self.empty_order
|
||||
sale_order.write({
|
||||
'order_line': [
|
||||
Command.create({
|
||||
'product_id': self.product_a.id,
|
||||
}),
|
||||
]
|
||||
})
|
||||
sale_order._update_programs_and_rewards()
|
||||
claimable_rewards = sale_order._get_claimable_rewards()
|
||||
self.assertTrue(claimable_rewards[loyalty_card])
|
||||
loyalty_card.action_archive()
|
||||
claimable_rewards = sale_order._get_claimable_rewards()
|
||||
self.assertFalse(claimable_rewards.get(loyalty_card))
|
||||
|
||||
def test_free_product_sol_is_zero_price(self):
|
||||
self.env['res.config.settings'].create({
|
||||
'group_discount_per_so_line': True,
|
||||
}).execute()
|
||||
loyalty_program = self.env['loyalty.program'].create({
|
||||
'name': 'Loyalty Program',
|
||||
'program_type': 'promotion',
|
||||
'rule_ids': [Command.create({'mode': 'with_code', 'code': '10PERCENT&GIFT'})],
|
||||
'trigger': 'auto',
|
||||
'applies_on': 'both',
|
||||
'rule_ids': [
|
||||
Command.create({
|
||||
'reward_point_mode': 'unit',
|
||||
'reward_point_amount': 1,
|
||||
'product_ids': [self.product_a.id],
|
||||
}),
|
||||
],
|
||||
'reward_ids': [
|
||||
Command.create({
|
||||
'reward_type': 'product',
|
||||
'reward_product_id': self.product_B.id,
|
||||
'reward_product_qty': 1,
|
||||
}),
|
||||
Command.create({
|
||||
'reward_type': 'discount',
|
||||
'discount': 10,
|
||||
'discount_mode': 'percent',
|
||||
'discount_applicability': 'specific',
|
||||
'required_points': 1,
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
coupon = self.env['loyalty.card'].create({
|
||||
'program_id': program.id, 'points': 20, 'code': 'GIFT_CARD'
|
||||
sale_order = self.empty_order
|
||||
sale_order.write({
|
||||
'order_line': [
|
||||
Command.create({
|
||||
'product_id': self.product_a.id,
|
||||
'product_uom_qty': 1,
|
||||
}),
|
||||
],
|
||||
})
|
||||
sale_order._update_programs_and_rewards()
|
||||
self._claim_reward(sale_order, loyalty_program)
|
||||
# In real use case, so.plan_id is set to False in _verify_cart_after_update in
|
||||
# sale_subscription module. Since discount depends on so.plan_id, this triggers
|
||||
# a recomputation of the discount.
|
||||
# Here we manually call the compute method to simulate the behavior
|
||||
sale_order.order_line._compute_discount()
|
||||
reward_line = sale_order.order_line.filtered('reward_id')
|
||||
self.assertEqual(reward_line.discount, 100)
|
||||
self.assertEqual(reward_line.price_total, 0)
|
||||
|
||||
order = self.env['sale.order'].create({
|
||||
'partner_id': self.partner_a.id,
|
||||
'order_line': [Command.create({'product_id': self.product_a.id})]
|
||||
def test_reapplying_reward_keeps_reward_price_unit(self):
|
||||
"""
|
||||
Ensure that re-applying a reward doesn't reset the existing reward line unit price to zero
|
||||
"""
|
||||
self.immediate_promotion_program.active = True
|
||||
sale_order = self.empty_order
|
||||
sale_order.write({
|
||||
'order_line': [
|
||||
Command.create({
|
||||
'product_id': self.product_A.id,
|
||||
'product_uom_qty': 1,
|
||||
}),
|
||||
],
|
||||
})
|
||||
sale_order._update_programs_and_rewards()
|
||||
self._claim_reward(sale_order, self.immediate_promotion_program)
|
||||
reward_line = sale_order.order_line.filtered('reward_id')
|
||||
reward_line_price_unit = reward_line.price_unit
|
||||
sale_order._update_programs_and_rewards()
|
||||
self._claim_reward(sale_order, self.immediate_promotion_program)
|
||||
self.assertEqual(reward_line.price_unit, reward_line_price_unit)
|
||||
|
||||
product_reward = program.reward_ids.filtered(lambda reward: reward.reward_type == 'product')
|
||||
discount_reward = program.reward_ids - product_reward
|
||||
order._apply_program_reward(discount_reward, coupon)
|
||||
rewards = order._get_claimable_rewards()[coupon]
|
||||
msg = "Only the free product should be applicable, as the discount was already applied."
|
||||
self.assertEqual(rewards, product_reward, msg)
|
||||
@freeze_time("2026-01-10")
|
||||
def test_expired_ewallet_is_not_claimable(self):
|
||||
self.ewallet.expiration_date = '2026-01-01'
|
||||
sale_order = self.empty_order
|
||||
sale_order.write({
|
||||
'partner_id': self.partner.id,
|
||||
'order_line': [
|
||||
Command.create({
|
||||
'product_id': self.product_a.id,
|
||||
}),
|
||||
],
|
||||
})
|
||||
sale_order.action_open_reward_wizard()
|
||||
sale_order._update_programs_and_rewards()
|
||||
claimable_rewards = sale_order._get_claimable_rewards()
|
||||
self.assertFalse(claimable_rewards.get(self.ewallet))
|
||||
|
|
|
|||
|
|
@ -0,0 +1,129 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.fields import Command
|
||||
from odoo.tests import tagged
|
||||
|
||||
from odoo.addons.sale_loyalty.tests.common import TestSaleCouponCommon
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestLoyaltyhistory(TestSaleCouponCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.partner_a = cls.env['res.partner'].create({'name': 'Jean Jacques'})
|
||||
cls.loyalty_program = cls.env['loyalty.program'].create({
|
||||
'name': 'Full Discount',
|
||||
'program_type': 'loyalty',
|
||||
'trigger': 'auto',
|
||||
'applies_on': 'both',
|
||||
'rule_ids': [Command.create({
|
||||
'reward_point_mode': 'unit',
|
||||
'reward_point_amount': 1,
|
||||
'product_ids': [cls.product_A.id],
|
||||
})],
|
||||
'reward_ids': [
|
||||
Command.create({
|
||||
'reward_type': 'discount',
|
||||
'discount': 10,
|
||||
'discount_mode': 'percent',
|
||||
'discount_applicability': 'order',
|
||||
'required_points': 1,
|
||||
}),
|
||||
Command.create({
|
||||
'active': False,
|
||||
'reward_type': 'product',
|
||||
'reward_product_id': cls.product_B.id,
|
||||
'required_points': 2,
|
||||
}),
|
||||
],
|
||||
})
|
||||
cls.loyalty_card = cls.env['loyalty.card'].create({
|
||||
'program_id': cls.loyalty_program.id, 'partner_id': cls.partner_a.id, 'points': 2
|
||||
})
|
||||
|
||||
def test_add_loyalty_history_line_with_reward(self):
|
||||
order = self.empty_order
|
||||
order.write({
|
||||
'order_line': [
|
||||
Command.create({
|
||||
'product_id': self.product_A.id,
|
||||
'name': 'Ordinary Product A',
|
||||
'product_uom_qty': 1.0,
|
||||
}),
|
||||
],
|
||||
})
|
||||
order._update_programs_and_rewards()
|
||||
self._auto_rewards(order, self.immediate_promotion_program)
|
||||
|
||||
order.action_confirm()
|
||||
coupon_applied = self.immediate_promotion_program.coupon_ids.filtered(lambda x: x.order_id == order)
|
||||
history_records = len(coupon_applied.history_ids.filtered(lambda history: history.order_id == order.id))
|
||||
self.assertEqual(history_records, 1, "A history line should be created on confirmation of order")
|
||||
|
||||
def test_add_loyalty_history_line_without_reward(self):
|
||||
order = self.empty_order
|
||||
order.write({
|
||||
'partner_id': self.partner_a.id,
|
||||
'order_line': [
|
||||
Command.create({
|
||||
'product_id': self.product_A.id,
|
||||
'tax_ids': False,
|
||||
}),
|
||||
]
|
||||
})
|
||||
order.action_confirm()
|
||||
order._update_programs_and_rewards()
|
||||
self._claim_reward(order, self.loyalty_program)
|
||||
history_records = self.loyalty_card.history_ids.filtered(lambda history: history.order_id == order.id)
|
||||
self.assertEqual(history_records.used, 1.0,
|
||||
"The history line should be updated on change of order lines in a confirmed order")
|
||||
|
||||
def test_delete_loyalty_history_line_on_cancel(self):
|
||||
order = self.empty_order
|
||||
order.write({
|
||||
'partner_id': self.partner_a.id,
|
||||
'order_line': [
|
||||
Command.create({
|
||||
'product_id': self.product_A.id,
|
||||
'tax_ids': False,
|
||||
}),
|
||||
]
|
||||
})
|
||||
order._update_programs_and_rewards()
|
||||
self._claim_reward(order, self.loyalty_program)
|
||||
order.action_confirm()
|
||||
lines_before_cancel = len(self.loyalty_card.history_ids)
|
||||
order._action_cancel()
|
||||
self.assertEqual(lines_before_cancel - 1, len(self.loyalty_card.history_ids),
|
||||
"History line should be deleted after order cancel")
|
||||
|
||||
def test_loyalty_history_multi_reward(self):
|
||||
"""Verify that applying multiple rewards sums up the total points cost."""
|
||||
self.loyalty_card.points = initial_points = 4
|
||||
self.loyalty_program.with_context(active_test=False).reward_ids.active = True
|
||||
order = self.empty_order
|
||||
order.write({
|
||||
'partner_id': self.partner_a.id,
|
||||
'order_line': [
|
||||
Command.create({
|
||||
'product_id': self.product_A.id,
|
||||
'tax_ids': False,
|
||||
}),
|
||||
],
|
||||
})
|
||||
for reward in self.loyalty_program.reward_ids:
|
||||
order._apply_program_reward(reward, self.loyalty_card)
|
||||
self.assertEqual(len(order.order_line.filtered('reward_id')), 2)
|
||||
self.assertEqual(order.order_line.mapped('points_cost'), [0, 1, 2])
|
||||
|
||||
order.action_confirm()
|
||||
loyalty_history = self.loyalty_card.history_ids
|
||||
self.assertEqual(loyalty_history.issued, 1, "1 point should be rewarded")
|
||||
self.assertEqual(loyalty_history.used, 3, "A total of 3 points should be used")
|
||||
self.assertEqual(
|
||||
self.loyalty_card.points,
|
||||
initial_points + loyalty_history.issued - loyalty_history.used,
|
||||
"Loyalty points should equal initial points + points issued - points used",
|
||||
)
|
||||
|
|
@ -20,7 +20,6 @@ class TestPayWithGiftCard(TestSaleCouponCommon):
|
|||
Command.create({
|
||||
'product_id': self.product_A.id,
|
||||
'name': 'Ordinary Product A',
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 1.0,
|
||||
})
|
||||
]})
|
||||
|
|
@ -41,7 +40,6 @@ class TestPayWithGiftCard(TestSaleCouponCommon):
|
|||
Command.create({
|
||||
'product_id': self.product_B.id,
|
||||
'name': 'Ordinary Product b',
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 1.0,
|
||||
})
|
||||
]})
|
||||
|
|
@ -62,7 +60,6 @@ class TestPayWithGiftCard(TestSaleCouponCommon):
|
|||
Command.create({
|
||||
'product_id': self.product_A.id,
|
||||
'name': 'Ordinary Product A',
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 20.0,
|
||||
})
|
||||
]})
|
||||
|
|
@ -83,7 +80,6 @@ class TestPayWithGiftCard(TestSaleCouponCommon):
|
|||
Command.create({
|
||||
'product_id': self.product_C.id,
|
||||
'name': 'Ordinary Product C',
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 1.0,
|
||||
})
|
||||
]})
|
||||
|
|
@ -124,7 +120,6 @@ class TestPayWithGiftCard(TestSaleCouponCommon):
|
|||
Command.create({
|
||||
'product_id': self.product_C.id,
|
||||
'name': 'Ordinary Product C',
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 1.0,
|
||||
})
|
||||
]})
|
||||
|
|
@ -180,7 +175,6 @@ class TestPayWithGiftCard(TestSaleCouponCommon):
|
|||
Command.create({
|
||||
'product_id': self.product_B.id,
|
||||
'name': 'Ordinary Product b',
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 1.0,
|
||||
'price_unit': 200.0,
|
||||
})
|
||||
|
|
@ -205,8 +199,8 @@ class TestPayWithGiftCard(TestSaleCouponCommon):
|
|||
gift_card_line = order.order_line - sol
|
||||
self.assertAlmostEqual(gift_card_line.price_total, -100.0)
|
||||
self.assertAlmostEqual(order.amount_total, before_gift_card_payment - 100.0)
|
||||
self.assertTrue(all(line.tax_id for line in order.order_line))
|
||||
self.assertEqual(order.order_line.tax_id, self.tax_15pc_excl)
|
||||
self.assertTrue(all(line.tax_ids for line in order.order_line))
|
||||
self.assertEqual(order.order_line.tax_ids, self.tax_15pc_excl)
|
||||
|
||||
# TAX INCL
|
||||
gift_card_line.unlink() # Remove gift card
|
||||
|
|
@ -217,8 +211,8 @@ class TestPayWithGiftCard(TestSaleCouponCommon):
|
|||
gift_card_line = order.order_line - sol
|
||||
self.assertAlmostEqual(gift_card_line.price_total, -100.0)
|
||||
self.assertAlmostEqual(order.amount_total, before_gift_card_payment - 100.0)
|
||||
self.assertTrue(all(line.tax_id for line in order.order_line))
|
||||
self.assertEqual(gift_card_line.tax_id, self.tax_10pc_incl)
|
||||
self.assertTrue(all(line.tax_ids for line in order.order_line))
|
||||
self.assertEqual(gift_card_line.tax_ids, self.tax_10pc_incl)
|
||||
|
||||
# TAX INCL + TAX EXCL
|
||||
gift_card_line.unlink() # Remove gift card
|
||||
|
|
@ -229,8 +223,8 @@ class TestPayWithGiftCard(TestSaleCouponCommon):
|
|||
gift_card_line = order.order_line - sol
|
||||
self.assertAlmostEqual(gift_card_line.price_total, -100.0)
|
||||
self.assertAlmostEqual(order.amount_total, before_gift_card_payment - 100.0)
|
||||
self.assertTrue(all(line.tax_id for line in order.order_line))
|
||||
self.assertEqual(gift_card_line.tax_id, self.tax_10pc_incl + self.tax_15pc_excl)
|
||||
self.assertTrue(all(line.tax_ids for line in order.order_line))
|
||||
self.assertEqual(gift_card_line.tax_ids, self.tax_10pc_incl + self.tax_15pc_excl)
|
||||
|
||||
def test_paying_with_gift_card_fixed_tax(self):
|
||||
""" Test payment of sale order with fixed tax using gift card """
|
||||
|
|
@ -253,7 +247,6 @@ class TestPayWithGiftCard(TestSaleCouponCommon):
|
|||
Command.create({
|
||||
'product_id': self.product_A.id,
|
||||
'name': "Ordinary Product A",
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 1.0,
|
||||
})
|
||||
]})
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.sale_loyalty.tests.common import TestSaleCouponCommon
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.fields import Command
|
||||
from odoo.tests import tagged
|
||||
from odoo import Command
|
||||
|
||||
from odoo.addons.sale_loyalty.tests.common import TestSaleCouponCommon
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
|
|
@ -45,13 +44,11 @@ class TestSaleCouponMultiCompany(TestSaleCouponCommon):
|
|||
(0, False, {
|
||||
'product_id': self.product_A.id,
|
||||
'name': '1 Product A',
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 1.0,
|
||||
}),
|
||||
(0, False, {
|
||||
'product_id': self.product_B.id,
|
||||
'name': '2 Product B',
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 1.0,
|
||||
})
|
||||
]})
|
||||
|
|
@ -68,13 +65,11 @@ class TestSaleCouponMultiCompany(TestSaleCouponCommon):
|
|||
(0, False, {
|
||||
'product_id': self.product_A.id,
|
||||
'name': '1 Product A',
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 1.0,
|
||||
}),
|
||||
(0, False, {
|
||||
'product_id': self.product_B.id,
|
||||
'name': '2 Product B',
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 1.0,
|
||||
})
|
||||
]})
|
||||
|
|
@ -95,20 +90,48 @@ class TestSaleCouponMultiCompany(TestSaleCouponCommon):
|
|||
Command.create({
|
||||
'product_id': self.product_A.id,
|
||||
'name': '1 Product A',
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 1.0,
|
||||
}),
|
||||
Command.create({
|
||||
'product_id': self.product_B.id,
|
||||
'name': '2 Product B',
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 1.0,
|
||||
})
|
||||
],
|
||||
'company_id': branch_a.id,
|
||||
'partner_id': self.steve.id
|
||||
'partner_id': self.partner.id
|
||||
}
|
||||
)
|
||||
|
||||
order._update_programs_and_rewards()
|
||||
self.assertIn(self.immediate_promotion_program, order._get_applied_programs())
|
||||
|
||||
def test_applicable_programs_confirm_on_branch(self):
|
||||
# create a branch
|
||||
self.env['loyalty.program'].search([]).write({'active': False})
|
||||
branch_a = self.env['res.company'].create(
|
||||
{'name': 'Branch A', 'parent_id': self.company_a.id}
|
||||
)
|
||||
|
||||
LoyaltyProgram = self.env['loyalty.program']
|
||||
LoyaltyProgram.create(LoyaltyProgram._get_template_values()['loyalty'])
|
||||
|
||||
self.sale_user.write({'company_ids': [Command.set((branch_a + self.company_a).ids)]})
|
||||
|
||||
# create an order
|
||||
order = self.empty_order
|
||||
order.update(
|
||||
{
|
||||
'order_line': [
|
||||
Command.create({
|
||||
'product_id': self.product_A.id,
|
||||
}),
|
||||
],
|
||||
'company_id': branch_a.id,
|
||||
'partner_id': self.partner.id,
|
||||
'user_id': self.sale_user.id
|
||||
}
|
||||
)
|
||||
|
||||
order.with_user(self.sale_user).with_company(branch_a.id).sudo(False).action_confirm()
|
||||
self.assertEqual(order.state, 'sale')
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.sale_loyalty.tests.common import TestSaleCouponNumbersCommon
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.fields import Command
|
||||
from odoo.tests import tagged
|
||||
from odoo.tools.float_utils import float_compare
|
||||
|
||||
from odoo.addons.sale_loyalty.tests.common import TestSaleCouponNumbersCommon
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestSaleCouponProgramNumbers(TestSaleCouponNumbersCommon):
|
||||
|
|
@ -93,7 +93,7 @@ class TestSaleCouponProgramNumbers(TestSaleCouponNumbersCommon):
|
|||
'name': "15% Tax",
|
||||
'amount_type': 'percent',
|
||||
'amount': 15,
|
||||
'price_include': True,
|
||||
'price_include_override': 'tax_included',
|
||||
})
|
||||
p_specific_product = self.env['loyalty.program'].create({
|
||||
'name': '20% reduction on Large Cabinet in cart',
|
||||
|
|
@ -126,10 +126,9 @@ class TestSaleCouponProgramNumbers(TestSaleCouponNumbersCommon):
|
|||
|
||||
self._auto_rewards(order, self.all_programs)
|
||||
self.assertEqual(len(order.order_line.ids), 1, "We should not get the reduction line since we dont have 320$ tax excluded (cabinet is 320$ tax included)")
|
||||
sol1.tax_id.price_include = False
|
||||
sol1._compute_tax_id()
|
||||
sol1.tax_ids.price_include_override = 'tax_excluded'
|
||||
sol1._compute_tax_ids()
|
||||
self.env.flush_all()
|
||||
self.env['account.tax'].invalidate_model(['price_include'])
|
||||
self._auto_rewards(order, self.all_programs)
|
||||
self.assertEqual(len(order.order_line.ids), 2, "We should now get the reduction line since we have 320$ tax included (cabinet is 320$ tax included)")
|
||||
# Name | Qty | price_unit | Tax | HTVA | TVAC | TVA |
|
||||
|
|
@ -217,7 +216,7 @@ class TestSaleCouponProgramNumbers(TestSaleCouponNumbersCommon):
|
|||
'name': "35% Tax incl",
|
||||
'amount_type': 'percent',
|
||||
'amount': 35,
|
||||
'price_include': True,
|
||||
'price_include_override': 'tax_included',
|
||||
})
|
||||
|
||||
# Set tax and prices on products as neeed for the test
|
||||
|
|
@ -330,8 +329,10 @@ class TestSaleCouponProgramNumbers(TestSaleCouponNumbersCommon):
|
|||
# --------------------------------------------------------------------------------
|
||||
# TOTAL | 2576.77 | 2946.11 | 369.34
|
||||
|
||||
self.assertAlmostEqual(order.amount_total, 1901.11, 2, "The order total with programs should be 1901.11")
|
||||
self.assertEqual(order.amount_untaxed, 1594.95, "The order untaxed total without any programs should be 2576.77")
|
||||
self.assertRecordValues(order, [{
|
||||
'amount_total': 1901.11,
|
||||
'amount_untaxed': 1594.95,
|
||||
}])
|
||||
self.assertEqual(len(order.order_line.ids), 5, "The order without any programs should have 5 lines")
|
||||
|
||||
# Apply all the programs
|
||||
|
|
@ -345,8 +346,10 @@ class TestSaleCouponProgramNumbers(TestSaleCouponNumbersCommon):
|
|||
# --------------------------------------------------------------------------------
|
||||
# TOTAL AFTER APPLYING FREE PRODUCT PROGRAMS | 1594.95 | 1901.11 | 306.16
|
||||
|
||||
self.assertAlmostEqual(order.amount_total, 1901.11, 2, "The order total with programs should be 1901.11")
|
||||
self.assertEqual(order.amount_untaxed, 1594.95, "The order untaxed total with programs should be 1594.95")
|
||||
self.assertRecordValues(order, [{
|
||||
'amount_total': 1901.11,
|
||||
'amount_untaxed': 1594.95,
|
||||
}])
|
||||
self.assertEqual(len(order.order_line.ids), 8, "Order should contains 5 regular product lines and 3 free product lines")
|
||||
|
||||
# Apply 10% on top of everything
|
||||
|
|
@ -362,18 +365,24 @@ class TestSaleCouponProgramNumbers(TestSaleCouponNumbersCommon):
|
|||
# --------------------------------------------------------------------------------
|
||||
# TOTAL AFTER APPLYING 10% GLOBAL PROGRAM | 1435.46 | 1711.00 | 275.54
|
||||
|
||||
self.assertEqual(order.amount_total, 1711, "The order total with programs should be 1711")
|
||||
self.assertEqual(order.amount_untaxed, 1435.46, "The order untaxed total with programs should be 1435.46")
|
||||
self.assertRecordValues(order, [{
|
||||
'amount_total': 1711.0,
|
||||
'amount_untaxed': 1435.45,
|
||||
}])
|
||||
self.assertEqual(len(order.order_line.ids), 12, "Order should contains 5 regular product lines, 3 free product lines and 4 discount lines (one for every tax)")
|
||||
|
||||
# -- This is a test inside the test
|
||||
order.order_line._compute_tax_id()
|
||||
self.assertEqual(order.amount_total, 1711, "Recomputing tax on sale order lines should not change total amount")
|
||||
self.assertEqual(order.amount_untaxed, 1435.46, "Recomputing tax on sale order lines should not change untaxed amount")
|
||||
order.order_line._compute_tax_ids()
|
||||
self.assertRecordValues(order, [{
|
||||
'amount_total': 1711.0,
|
||||
'amount_untaxed': 1435.45,
|
||||
}])
|
||||
self.assertEqual(len(order.order_line.ids), 12, "Recomputing tax on sale order lines should not change number of order line")
|
||||
self._auto_rewards(order, self.all_programs)
|
||||
self.assertEqual(order.amount_total, 1711, "Recomputing tax on sale order lines should not change total amount")
|
||||
self.assertEqual(order.amount_untaxed, 1435.46, "Recomputing tax on sale order lines should not change untaxed amount")
|
||||
self.assertRecordValues(order, [{
|
||||
'amount_total': 1711.0,
|
||||
'amount_untaxed': 1435.45,
|
||||
}])
|
||||
self.assertEqual(len(order.order_line.ids), 12, "Recomputing tax on sale order lines should not change number of order line")
|
||||
# -- End test inside the test
|
||||
|
||||
|
|
@ -402,8 +411,10 @@ class TestSaleCouponProgramNumbers(TestSaleCouponNumbersCommon):
|
|||
# --------------------------------------------------------------------------------
|
||||
# TOTAL AFTER APPLYING 20% ON LARGE CABINET | 1363.46 | 1628.2 | 264.74
|
||||
|
||||
self.assertEqual(order.amount_total, 1628.2, "The order total with programs should be 1628.2")
|
||||
self.assertEqual(order.amount_untaxed, 1363.46, "The order untaxed total with programs should be 1363.45")
|
||||
self.assertRecordValues(order, [{
|
||||
'amount_total': 1628.2,
|
||||
'amount_untaxed': 1363.45,
|
||||
}])
|
||||
self.assertEqual(len(order.order_line.ids), 13, "Order should have a new discount line for 20% on Large Cabinet")
|
||||
|
||||
# Check that if you delete one of the discount tax line, the others tax lines from the same promotion got deleted as well.
|
||||
|
|
@ -425,7 +436,7 @@ class TestSaleCouponProgramNumbers(TestSaleCouponNumbersCommon):
|
|||
# Name | Qty | price_unit | Tax | HTVA | TVAC | TVA |
|
||||
# --------------------------------------------------------------------------------
|
||||
# Large Cabinet | 4 | 100.00 | 15% excl | 400.00 | 460.00 | 60.00
|
||||
# Conference Chair | 4 | 100.00 | 10% incl | 363.64 | 400.00 | 36.36
|
||||
# Conference Chair | 4 | 100.00 | 10% incl | 363.63 | 400.00 | 36.36
|
||||
# Pedal Bins | 5 | 100.00 | / | 500.00 | 500.00 | /
|
||||
# Drawer Black | 2 | 100.00 | 15% excl | 200.00 | 230.00 | 30.00
|
||||
# Product A | 3 | 100.00 | 35% incl | 222.22 | 411.11 | 188.89
|
||||
|
|
@ -440,9 +451,9 @@ class TestSaleCouponProgramNumbers(TestSaleCouponNumbersCommon):
|
|||
# 10% on tax 35+50% | 1 | -30.00 | 35% incl | -22.22 | -41.11 | -18.89
|
||||
# 50% excl
|
||||
# --------------------------------------------------------------------------------
|
||||
# TOTAL | 1445.28 | 1718.20 | 272.92
|
||||
# TOTAL | 1445.27 | 1718.20 | 272.92
|
||||
|
||||
self.assertEqual(order.amount_untaxed, 1445.28, "The order should have one more paid Conference Chair with 10% incl tax and discounted by 10%")
|
||||
self.assertEqual(order.amount_untaxed, 1445.27, "The order should have one more paid Conference Chair with 10% incl tax and discounted by 10%")
|
||||
|
||||
# Check that if you remove a product, his reward lines got removed, especially the discount per tax one
|
||||
sol2.unlink()
|
||||
|
|
@ -464,8 +475,10 @@ class TestSaleCouponProgramNumbers(TestSaleCouponNumbersCommon):
|
|||
# --------------------------------------------------------------------------------
|
||||
# TOTAL | 1118.00 | 1349.00 | 240.20
|
||||
|
||||
self.assertAlmostEqual(order.amount_total, 1358.2, 2, "The order total with programs should be 1358.20")
|
||||
self.assertEqual(order.amount_untaxed, 1118, "The order untaxed total with programs should be 1118.00")
|
||||
self.assertRecordValues(order, [{
|
||||
'amount_total': 1358.2,
|
||||
'amount_untaxed': 1118.0,
|
||||
}])
|
||||
self.assertEqual(len(order.order_line.ids), 10, "Order should contains 10 lines: 4 products lines, 2 free products lines and 4 discount lines")
|
||||
|
||||
def test_program_numbers_extras(self):
|
||||
|
|
@ -519,7 +532,7 @@ class TestSaleCouponProgramNumbers(TestSaleCouponNumbersCommon):
|
|||
'name': 'Drawer Black',
|
||||
'product_uom_qty': 1.0,
|
||||
'order_id': order.id,
|
||||
'tax_id': [(4, self.tax_0pc_excl.id)]
|
||||
'tax_ids': [(4, self.tax_0pc_excl.id)]
|
||||
})
|
||||
self._auto_rewards(order, self.all_programs)
|
||||
self.assertEqual(order.amount_total, 0, "Total should be null. The fixed amount discount is higher than the SO total, it should be reduced to the SO total")
|
||||
|
|
@ -577,6 +590,17 @@ class TestSaleCouponProgramNumbers(TestSaleCouponNumbersCommon):
|
|||
generated_coupon = order._get_reward_coupons()
|
||||
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.points, 0, "The coupon should not have it's points already.")
|
||||
self.assertFalse(order._get_claimable_rewards(), "No rewards should be claimable")
|
||||
|
||||
order.action_confirm()
|
||||
self.assertEqual(
|
||||
generated_coupon.points, 1,
|
||||
"The coupon should have 1 point after confirmation",
|
||||
)
|
||||
self.assertFalse(
|
||||
order._get_claimable_rewards(),
|
||||
"Next-order coupon rewards shouldn't be claimable on current order",
|
||||
)
|
||||
|
||||
def test_coupon_rule_minimum_amount(self):
|
||||
""" Ensure coupon with minimum amount rule are correctly
|
||||
|
|
@ -684,7 +708,7 @@ class TestSaleCouponProgramNumbers(TestSaleCouponNumbersCommon):
|
|||
'product_uom_qty': 1.0,
|
||||
'price_unit': 100.0,
|
||||
'order_id': order.id,
|
||||
'tax_id': [(6, 0, (self.tax_15pc_excl.id,))],
|
||||
'tax_ids': [(6, 0, (self.tax_15pc_excl.id,))],
|
||||
},
|
||||
{
|
||||
'product_id': self.pedalBin.id,
|
||||
|
|
@ -692,7 +716,7 @@ class TestSaleCouponProgramNumbers(TestSaleCouponNumbersCommon):
|
|||
'product_uom_qty': 1.0,
|
||||
'price_unit': 100.0,
|
||||
'order_id': order.id,
|
||||
'tax_id': [(6, 0, [])],
|
||||
'tax_ids': [(6, 0, [])],
|
||||
},
|
||||
{
|
||||
'product_id': self.product_A.id,
|
||||
|
|
@ -700,7 +724,7 @@ class TestSaleCouponProgramNumbers(TestSaleCouponNumbersCommon):
|
|||
'product_uom_qty': 1.0,
|
||||
'price_unit': 100.0,
|
||||
'order_id': order.id,
|
||||
'tax_id': [(6, 0, [])],
|
||||
'tax_ids': [(6, 0, [])],
|
||||
},
|
||||
])
|
||||
|
||||
|
|
@ -729,13 +753,13 @@ class TestSaleCouponProgramNumbers(TestSaleCouponNumbersCommon):
|
|||
self._auto_rewards(order, self.all_programs)
|
||||
self._apply_promo_code(order, 'test_10pc')
|
||||
self._auto_rewards(order, self.all_programs)
|
||||
self.assertAlmostEqual(order.amount_tax, 1.13, 2)
|
||||
self.assertEqual(order.amount_untaxed, 22.72)
|
||||
self.assertAlmostEqual(order.amount_tax, 1.14, 2)
|
||||
self.assertEqual(order.amount_untaxed, 22.71)
|
||||
self.assertEqual(order.amount_total, 23.85, "The promotion program should not make the order total go below 0be altered after recomputation")
|
||||
# It should stay the same after a recompute, order matters
|
||||
self._auto_rewards(order, self.all_programs)
|
||||
self.assertAlmostEqual(order.amount_tax, 1.13, 2)
|
||||
self.assertEqual(order.amount_untaxed, 22.72)
|
||||
self.assertAlmostEqual(order.amount_tax, 1.14, 2)
|
||||
self.assertEqual(order.amount_untaxed, 22.71)
|
||||
self.assertEqual(order.amount_total, 23.85, "The promotion program should not make the order total go below 0be altered after recomputation")
|
||||
|
||||
def test_coupon_and_coupon_discount_fixed_amount_tax_incl(self):
|
||||
|
|
@ -771,7 +795,7 @@ class TestSaleCouponProgramNumbers(TestSaleCouponNumbersCommon):
|
|||
'product_uom_qty': 1.0,
|
||||
'price_unit': 100.0,
|
||||
'order_id': order.id,
|
||||
'tax_id': [(6, 0, (self.tax_10pc_incl.id,))],
|
||||
'tax_ids': [(6, 0, (self.tax_10pc_incl.id,))],
|
||||
},
|
||||
{
|
||||
'product_id': self.pedalBin.id,
|
||||
|
|
@ -779,7 +803,7 @@ class TestSaleCouponProgramNumbers(TestSaleCouponNumbersCommon):
|
|||
'product_uom_qty': 1.0,
|
||||
'price_unit': 100.0,
|
||||
'order_id': order.id,
|
||||
'tax_id': [(6, 0, [])],
|
||||
'tax_ids': [(6, 0, [])],
|
||||
},
|
||||
{
|
||||
'product_id': self.product_A.id,
|
||||
|
|
@ -787,7 +811,7 @@ class TestSaleCouponProgramNumbers(TestSaleCouponNumbersCommon):
|
|||
'product_uom_qty': 1.0,
|
||||
'price_unit': 100.0,
|
||||
'order_id': order.id,
|
||||
'tax_id': [(6, 0, [])],
|
||||
'tax_ids': [(6, 0, [])],
|
||||
},
|
||||
])
|
||||
|
||||
|
|
@ -800,10 +824,10 @@ class TestSaleCouponProgramNumbers(TestSaleCouponNumbersCommon):
|
|||
}).generate_coupons()
|
||||
coupon = coupon_program.coupon_ids
|
||||
self._apply_promo_code(order, coupon.code)
|
||||
self.assertEqual(order.amount_total, 0.0, "The promotion program should not make the order total go below 0")
|
||||
self.assertEqual(order.amount_total, 0, "The promotion program should not make the order total go below 0")
|
||||
self.assertEqual(order.amount_tax, 0)
|
||||
self._auto_rewards(order, self.all_programs)
|
||||
self.assertEqual(order.amount_total, 0.0, "The promotion program should not be altered after recomputation")
|
||||
self.assertEqual(order.amount_total, 0, "The promotion program should not be altered after recomputation")
|
||||
self.assertEqual(order.amount_tax, 0)
|
||||
|
||||
order.order_line[3:].unlink() #remove all coupon
|
||||
|
|
@ -895,9 +919,9 @@ class TestSaleCouponProgramNumbers(TestSaleCouponNumbersCommon):
|
|||
'name': "30% Tax",
|
||||
'amount_type': 'percent',
|
||||
'amount': 30,
|
||||
'price_include': True,
|
||||
'price_include_override': 'tax_included',
|
||||
})
|
||||
sol2.tax_id = percent_tax
|
||||
sol2.tax_ids = percent_tax
|
||||
|
||||
self._auto_rewards(order, self.all_programs)
|
||||
self.assertEqual(len(order.order_line.ids), 4, "Conference Chair + Drawer Black + 20% on no TVA product (Conference Chair) + 20% on 15% tva product (Drawer Black)")
|
||||
|
|
@ -908,9 +932,9 @@ class TestSaleCouponProgramNumbers(TestSaleCouponNumbersCommon):
|
|||
# 25% discount | 1 | -16.50 | / | -16.50 | -16.50 | 0.00
|
||||
# 25% discount | 1 | -12.50 | 30% incl | -9.62 | -12.50 | -2.88
|
||||
# --------------------------------------------------------------------------------
|
||||
# TOTAL | 78.34 | 87.00 | 8.66
|
||||
# TOTAL | 78.35 | 87.00 | 8.66
|
||||
self.assertEqual(order.amount_total, 87.00, "Total untaxed should be as per above comment")
|
||||
self.assertEqual(order.amount_untaxed, 78.34, "Total with taxes should be as per above comment")
|
||||
self.assertEqual(order.amount_untaxed, 78.35, "Total with taxes should be as per above comment")
|
||||
|
||||
def test_program_numbers_free_prod_with_min_amount_and_qty_on_same_prod(self):
|
||||
# This test focus on giving a free product based on both
|
||||
|
|
@ -1052,7 +1076,7 @@ class TestSaleCouponProgramNumbers(TestSaleCouponNumbersCommon):
|
|||
'product_uom_qty': 14.0,
|
||||
'price_unit': 118.0,
|
||||
'order_id': order.id,
|
||||
'tax_id': False,
|
||||
'tax_ids': False,
|
||||
})
|
||||
self._auto_rewards(order, self.all_programs)
|
||||
self.assertEqual(order.amount_total, 1486.80, "10% discount should be applied")
|
||||
|
|
@ -1264,6 +1288,42 @@ class TestSaleCouponProgramNumbers(TestSaleCouponNumbersCommon):
|
|||
self.assertEqual(order.amount_total, 190.0, 'The price must be 190.0 since there is now 2x 5$ discount and 2x Product F')
|
||||
self.assertEqual(order.order_line.filtered(lambda x: x.is_reward_line).price_unit, -5, 'The discount unit price should still be -5 after the quantity was manually changed')
|
||||
|
||||
def test_program_multi_product_max_discount(self):
|
||||
order = self.empty_order
|
||||
coupon_program = self.env['loyalty.program'].create({
|
||||
'name': "50% off for cheapest product(max $30)",
|
||||
'trigger': 'with_code',
|
||||
'program_type': 'coupons',
|
||||
'reward_ids': [(0, 0, {
|
||||
'reward_type': 'discount',
|
||||
'discount': 50,
|
||||
'discount_mode': 'percent',
|
||||
'discount_applicability': 'cheapest',
|
||||
'discount_max_amount': 30,
|
||||
})],
|
||||
})
|
||||
|
||||
# create SOL
|
||||
self.env['sale.order.line'].create({
|
||||
'product_id': self.largeCabinet.id,
|
||||
'product_uom_qty': 2.0,
|
||||
'order_id': order.id,
|
||||
})
|
||||
|
||||
# generate and apply coupon
|
||||
self.env['loyalty.generate.wizard'].with_context(active_id=coupon_program.id).create({
|
||||
'coupon_qty': 1,
|
||||
'points_granted': 1,
|
||||
}).generate_coupons()
|
||||
|
||||
coupon = coupon_program.coupon_ids
|
||||
self._apply_promo_code(order, coupon.code)
|
||||
|
||||
self.assertEqual(len(order.order_line), 2, "The order must contain 2 order lines")
|
||||
self.assertEqual(
|
||||
order.amount_total, 610.0, "The price must be 610.0 since the max discount is 30"
|
||||
)
|
||||
|
||||
def test_specific_discount_product_group(self):
|
||||
# Tests the following:
|
||||
# 1 program: -5$ on [A, B]
|
||||
|
|
@ -1475,26 +1535,26 @@ class TestSaleCouponProgramNumbers(TestSaleCouponNumbersCommon):
|
|||
self.assertEqual(order.amount_total, 5, 'Price should be 10$ - 5$(discount) = 5$')
|
||||
self.assertEqual(order.amount_tax, 0, 'No taxes are applied yet')
|
||||
|
||||
sol.tax_id = self.tax_10pc_base_incl
|
||||
sol.tax_ids = self.tax_10pc_base_incl
|
||||
self._auto_rewards(order, program)
|
||||
|
||||
self.assertEqual(order.amount_total, 5, 'Price should be 10$ - 5$(discount) = 5$')
|
||||
self.assertEqual(float_compare(order.amount_tax, 5 / 11, precision_rounding=3), 0, '10% Tax included in 5$')
|
||||
|
||||
sol.tax_id = self.tax_10pc_excl
|
||||
sol.tax_ids = self.tax_10pc_excl
|
||||
self._auto_rewards(order, program)
|
||||
|
||||
# Value is 5.99 instead of 6 because you cannot have 6 with 10% tax excluded and a precision rounding of 2
|
||||
self.assertAlmostEqual(order.amount_total, 6, 1, msg='Price should be 11$ - 5$(discount) = 6$')
|
||||
self.assertEqual(float_compare(order.amount_tax, 6 / 11, precision_rounding=3), 0, '10% Tax included in 6$')
|
||||
|
||||
sol.tax_id = self.tax_20pc_excl
|
||||
sol.tax_ids = self.tax_20pc_excl
|
||||
self._auto_rewards(order, program)
|
||||
|
||||
self.assertEqual(order.amount_total, 7, 'Price should be 12$ - 5$(discount) = 7$')
|
||||
self.assertEqual(float_compare(order.amount_tax, 7 / 12, precision_rounding=3), 0, '20% Tax included on 7$')
|
||||
|
||||
sol.tax_id = self.tax_10pc_base_incl + self.tax_10pc_excl
|
||||
sol.tax_ids = self.tax_10pc_base_incl + self.tax_10pc_excl
|
||||
self._auto_rewards(order, program)
|
||||
|
||||
self.assertAlmostEqual(order.amount_total, 6, 1, msg='Price should be 11$ - 5$(discount) = 6$')
|
||||
|
|
@ -1537,21 +1597,21 @@ class TestSaleCouponProgramNumbers(TestSaleCouponNumbersCommon):
|
|||
self.assertAlmostEqual(order.amount_total, 15, 1, msg='Price should be 20$ - 5$(discount) = 15$')
|
||||
self.assertEqual(order.amount_tax, 0, 'No taxes are applied yet')
|
||||
|
||||
sol1.tax_id = self.tax_10pc_base_incl
|
||||
sol1.tax_ids = self.tax_10pc_base_incl
|
||||
self._auto_rewards(order, program)
|
||||
|
||||
self.assertAlmostEqual(order.amount_total, 15, 1, msg='Price should be 20$ - 5$(discount) = 15$')
|
||||
self.assertEqual(float_compare(order.amount_tax, 5 / 11 + 0, precision_rounding=3), 0,
|
||||
'10% Tax included in 5$ in sol1 (highest cost) and 0 in sol2')
|
||||
|
||||
sol2.tax_id = self.tax_10pc_excl
|
||||
sol2.tax_ids = self.tax_10pc_excl
|
||||
self._auto_rewards(order, program)
|
||||
|
||||
self.assertAlmostEqual(order.amount_total, 16, 1, msg='Price should be 21$ - 5$(discount) = 16$')
|
||||
# Tax amount = 10% in 10$ + 10% in 11$ - 10% in 5$ (apply on excluded)
|
||||
self.assertEqual(float_compare(order.amount_tax, 5 / 11, precision_rounding=3), 0)
|
||||
|
||||
sol2.tax_id = self.tax_10pc_base_incl + self.tax_10pc_excl
|
||||
sol2.tax_ids = self.tax_10pc_base_incl + self.tax_10pc_excl
|
||||
self._auto_rewards(order, program)
|
||||
|
||||
self.assertAlmostEqual(order.amount_total, 16, 1, msg='Price should be 21$ - 5$(discount) = 16$')
|
||||
|
|
@ -1567,7 +1627,7 @@ class TestSaleCouponProgramNumbers(TestSaleCouponNumbersCommon):
|
|||
'product_uom_qty': 1.0,
|
||||
'order_id': order.id,
|
||||
})
|
||||
sol3.tax_id = self.tax_10pc_excl
|
||||
sol3.tax_ids = self.tax_10pc_excl
|
||||
self._auto_rewards(order, program)
|
||||
|
||||
self.assertAlmostEqual(order.amount_total, 27, 1, msg='Price should be 32$ - 5$(discount) = 27$')
|
||||
|
|
@ -1801,6 +1861,100 @@ class TestSaleCouponProgramNumbers(TestSaleCouponNumbersCommon):
|
|||
self._auto_rewards(order, loyalty_program)
|
||||
|
||||
self.assertEqual(len(order.order_line), 2, 'Promotion should add 1 line')
|
||||
self.assertEqual(order.order_line[0].tax_id, tax_15pc_excl)
|
||||
self.assertEqual(order.order_line[1].tax_id, tax_15pc_excl)
|
||||
self.assertEqual(order.order_line[0].tax_ids, tax_15pc_excl)
|
||||
self.assertEqual(order.order_line[1].tax_ids, tax_15pc_excl)
|
||||
self.assertEqual(order.amount_total, 156.0, '140$ + 15% - 5$ = 156$')
|
||||
|
||||
def test_rounded_used_loyalty_points(self):
|
||||
"""Check that the loyalty points used in a reward are rounded according to the currency."""
|
||||
loyalty_program = self.env['loyalty.program'].create({
|
||||
'name': 'Test loyalty card',
|
||||
'program_type': 'loyalty',
|
||||
'trigger': 'auto',
|
||||
'applies_on': 'both',
|
||||
'rule_ids': [Command.set([])],
|
||||
'reward_ids': [Command.create({
|
||||
'reward_type': 'discount',
|
||||
'discount_mode': 'per_point',
|
||||
'discount': 0.03,
|
||||
'discount_applicability': 'order',
|
||||
'required_points': 1,
|
||||
})],
|
||||
})
|
||||
order = self.empty_order
|
||||
self.env['loyalty.card'].create([{
|
||||
'program_id': loyalty_program.id,
|
||||
'partner_id': order.partner_id.id,
|
||||
'points': 3030,
|
||||
}])
|
||||
product_a = self._create_product(
|
||||
name='product_a',
|
||||
lst_price=3000.0,
|
||||
taxes_id=[Command.set([])],
|
||||
)
|
||||
order.order_line = [Command.create({'product_id': product_a.id})]
|
||||
|
||||
coupon = loyalty_program.coupon_ids[0]
|
||||
order._apply_program_reward(loyalty_program.reward_ids[0], coupon)
|
||||
order.action_confirm()
|
||||
self.assertEqual(len(order.order_line), 2, 'Promotion should add 1 line')
|
||||
used_points = coupon.history_ids[0].used
|
||||
self.assertEqual(used_points, coupon.currency_id.round(used_points))
|
||||
|
||||
def test_apply_order_and_specific_discounts(self):
|
||||
"""Ensure you can apply a full-order discount, and then a product-specific discount."""
|
||||
order_program, specific_program = self.env['loyalty.program'].create([
|
||||
{
|
||||
'name': "$50 discount",
|
||||
'program_type': 'promotion',
|
||||
'trigger': 'auto',
|
||||
'applies_on': 'current',
|
||||
'rule_ids': [Command.create({})],
|
||||
'reward_ids': [Command.create({
|
||||
'reward_type': 'discount',
|
||||
'discount_mode': 'per_order',
|
||||
'discount': 50,
|
||||
'discount_applicability': 'order',
|
||||
'required_points': 1,
|
||||
})],
|
||||
},
|
||||
{
|
||||
'name': "$10 discount on Pedal Bin",
|
||||
'program_type': 'promotion',
|
||||
'trigger': 'auto',
|
||||
'applies_on': 'current',
|
||||
'rule_ids': [Command.create({})],
|
||||
'reward_ids': [Command.create({
|
||||
'reward_type': 'discount',
|
||||
'discount_mode': 'per_order',
|
||||
'discount': 10,
|
||||
'discount_applicability': 'specific',
|
||||
'discount_product_ids': self.pedalBin.ids,
|
||||
'required_points': 1,
|
||||
})],
|
||||
},
|
||||
])
|
||||
order = self.empty_order
|
||||
order.order_line = [Command.create({
|
||||
'product_id': self.pedalBin.id,
|
||||
'tax_ids': self.tax_20pc_excl.ids,
|
||||
})]
|
||||
|
||||
self.assertAlmostEqual(
|
||||
order.amount_total,
|
||||
self.pedalBin.list_price * (1 + self.tax_20pc_excl.amount / 100), # $56.4
|
||||
msg="Order total should equal product list price plus taxes",
|
||||
)
|
||||
|
||||
self._auto_rewards(order, order_program)
|
||||
self.assertAlmostEqual(
|
||||
order.amount_total,
|
||||
self.pedalBin.list_price * (1 + self.tax_20pc_excl.amount / 100) - 50, # $6.4
|
||||
msg="The order total should be $50 less than initially after the discount is applied.",
|
||||
)
|
||||
|
||||
self._auto_rewards(order, specific_program)
|
||||
self.assertFalse(
|
||||
order.amount_total,
|
||||
"Order total should be 0, as a specific discount should have been applied.",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,13 +1,18 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from datetime import date, timedelta
|
||||
|
||||
from odoo.addons.sale_loyalty.tests.common import TestSaleCouponCommon
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo import Command
|
||||
from freezegun import freeze_time
|
||||
from pytz import timezone
|
||||
|
||||
class TestProgramRules(TestSaleCouponCommon):
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.fields import Command, Datetime
|
||||
|
||||
from odoo.addons.payment.tests.common import PaymentCommon
|
||||
from odoo.addons.sale_loyalty.tests.common import TestSaleCouponCommon
|
||||
|
||||
|
||||
class TestProgramRules(TestSaleCouponCommon, PaymentCommon):
|
||||
# Test all the validity rules to allow a customer to have a reward.
|
||||
# The check based on the products is already done in the basic operations test
|
||||
|
||||
|
|
@ -25,13 +30,11 @@ class TestProgramRules(TestSaleCouponCommon):
|
|||
(0, False, {
|
||||
'product_id': self.product_A.id,
|
||||
'name': '1 Product A',
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 1.0,
|
||||
}),
|
||||
(0, False, {
|
||||
'product_id': self.product_B.id,
|
||||
'name': '2 Product B',
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 1.0,
|
||||
})
|
||||
]})
|
||||
|
|
@ -39,18 +42,16 @@ class TestProgramRules(TestSaleCouponCommon):
|
|||
self._claim_reward(order, self.immediate_promotion_program)
|
||||
self.assertEqual(len(order.order_line.ids), 2, "The promo offer shouldn't have been applied as the purchased amount is not enough")
|
||||
|
||||
order = self.env['sale.order'].create({'partner_id': self.steve.id})
|
||||
order = self.env['sale.order'].create({'partner_id': self.partner.id})
|
||||
order.write({'order_line': [
|
||||
(0, False, {
|
||||
'product_id': self.product_A.id,
|
||||
'name': '10 Product A',
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 10.0,
|
||||
}),
|
||||
(0, False, {
|
||||
'product_id': self.product_B.id,
|
||||
'name': '2 Product B',
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 1.0,
|
||||
})
|
||||
]})
|
||||
|
|
@ -282,8 +283,8 @@ class TestProgramRules(TestSaleCouponCommon):
|
|||
discounts = set(order.order_line.mapped('name')) - {'Product A'}
|
||||
self.assertEqual(len(discounts), 1, "The order should contains the Product A line and a discount")
|
||||
# The name of the discount is dynamically changed to smth looking like:
|
||||
# "Discount: Get 5% discount if buy at least 2 Product - On product with following tax: Tax 15.00%"
|
||||
self.assertTrue('Discount: 5% on your order' in discounts.pop(), "The discount should be a 5% discount")
|
||||
# "Discount Get 5% discount if buy at least 2 Product - On product with following tax: Tax 15.00%"
|
||||
self.assertTrue('Discount 5% on your order' in discounts.pop(), "The discount should be a 5% discount")
|
||||
|
||||
sol.product_uom_qty = 5
|
||||
order._update_programs_and_rewards()
|
||||
|
|
@ -291,71 +292,215 @@ class TestProgramRules(TestSaleCouponCommon):
|
|||
self._claim_reward(order, p2)
|
||||
discounts = set(order.order_line.mapped('name')) - {'Product A'}
|
||||
self.assertEqual(len(discounts), 1, "The order should contains the Product A line and a discount")
|
||||
self.assertTrue('Discount: 10% on your order' in discounts.pop(), "The discount should be a 10% discount")
|
||||
self.assertTrue('Discount 10% on your order' in discounts.pop(), "The discount should be a 10% discount")
|
||||
|
||||
def test_program_rules_validity_dates_and_uses(self):
|
||||
# Test case: Based on the validity dates and the number of allowed uses
|
||||
@freeze_time('2011-11-02 09:00:21')
|
||||
def test_program_rules_validity_dates(self):
|
||||
# Test date_to (no date_from)
|
||||
today = date.today()
|
||||
past_day = today - timedelta(days=2)
|
||||
future_day = today + timedelta(days=2)
|
||||
self.immediate_promotion_program.write({'date_to': past_day})
|
||||
order = self.empty_order
|
||||
order.write({'order_line': [
|
||||
Command.create({
|
||||
'product_id': self.product_A.id,
|
||||
'name': '1 Product A',
|
||||
'product_uom_qty': 1.0,
|
||||
}),
|
||||
Command.create({
|
||||
'product_id': self.product_B.id,
|
||||
'name': '2 Product B',
|
||||
'product_uom_qty': 1.0,
|
||||
})
|
||||
]})
|
||||
self._auto_rewards(order, self.immediate_promotion_program)
|
||||
msg = "The promo shouldn't have been applied as it is expired."
|
||||
self.assertEqual(len(order.order_line.ids), 2, msg)
|
||||
|
||||
self.immediate_promotion_program.write({'date_to': future_day})
|
||||
self._auto_rewards(order, self.immediate_promotion_program)
|
||||
msg = "The promo should have been applied we're between the validity dates."
|
||||
self.assertEqual(len(order.order_line.ids), 3, msg)
|
||||
|
||||
# Test date_from (no date_to)
|
||||
self.immediate_promotion_program.write({
|
||||
'date_from': future_day, 'date_to': False,
|
||||
})
|
||||
self._auto_rewards(order, self.immediate_promotion_program)
|
||||
msg = "The promo shouldn't have been applied as it is not active yet."
|
||||
self.assertEqual(len(order.order_line.ids), 2, msg)
|
||||
|
||||
self.immediate_promotion_program.write({'date_from': past_day})
|
||||
self._auto_rewards(order, self.immediate_promotion_program)
|
||||
msg = "The promo should have been applied we're between the validity dates."
|
||||
self.assertEqual(len(order.order_line.ids), 3, msg)
|
||||
|
||||
# Test date_from and date_to
|
||||
self.immediate_promotion_program.write({'date_from': past_day, 'date_to': future_day})
|
||||
self._auto_rewards(order, self.immediate_promotion_program)
|
||||
msg = "The promo should have been applied as we're between the validity dates"
|
||||
self.assertEqual(len(order.order_line.ids), 3, msg)
|
||||
|
||||
self.immediate_promotion_program.write({
|
||||
'date_to': date.today() - timedelta(days=2),
|
||||
'date_from': today + timedelta(days=1),
|
||||
'date_to': future_day,
|
||||
})
|
||||
self._auto_rewards(order, self.immediate_promotion_program)
|
||||
msg = "The promo offer shouldn't have been applied as it is not active yet."
|
||||
self.assertEqual(len(order.order_line.ids), 2, msg)
|
||||
|
||||
self.immediate_promotion_program.write({
|
||||
'date_from': past_day,
|
||||
'date_to': today - timedelta(days=1),
|
||||
})
|
||||
self._auto_rewards(order, self.immediate_promotion_program)
|
||||
msg = "The promo offer shouldn't have been applied as it is expired."
|
||||
self.assertEqual(len(order.order_line.ids), 2, msg)
|
||||
|
||||
self.immediate_promotion_program.write({'date_from': today, 'date_to': today})
|
||||
self._auto_rewards(order, self.immediate_promotion_program)
|
||||
msg = "The promo should have been applied as today is a valid starting and ending date."
|
||||
self.assertEqual(len(order.order_line.ids), 3, msg)
|
||||
|
||||
def test_program_rules_number_of_uses(self):
|
||||
# Test case: Based on the number of allowed uses
|
||||
self.immediate_promotion_program.write({
|
||||
'limit_usage': True,
|
||||
'max_usage': 1,
|
||||
})
|
||||
|
||||
order = self.empty_order
|
||||
order.write({'order_line': [
|
||||
(0, False, {
|
||||
Command.create({
|
||||
'product_id': self.product_A.id,
|
||||
'name': '1 Product A',
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 1.0,
|
||||
}),
|
||||
(0, False, {
|
||||
'product_id': self.product_B.id,
|
||||
'name': '2 Product B',
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 1.0,
|
||||
})
|
||||
]})
|
||||
self._auto_rewards(order, self.immediate_promotion_program)
|
||||
self.assertEqual(len(order.order_line.ids), 2, "The promo offer shouldn't have been applied we're not between the validity dates")
|
||||
self.assertEqual(len(order.order_line.ids), 2, "The promo offer should have been applied")
|
||||
|
||||
self.immediate_promotion_program.write({
|
||||
'date_to': date.today() + timedelta(days=2),
|
||||
order = self.env['sale.order'].create({
|
||||
'partner_id': self.env['res.partner'].create({'name': 'My Partner'}).id
|
||||
})
|
||||
order = self.env['sale.order'].create({'partner_id': self.steve.id})
|
||||
|
||||
order.write({'order_line': [
|
||||
(0, False, {
|
||||
'product_id': self.product_A.id,
|
||||
'name': '1 Product A',
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 10.0,
|
||||
}),
|
||||
(0, False, {
|
||||
Command.create({
|
||||
'product_id': self.product_B.id,
|
||||
'name': '2 Product B',
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 1.0,
|
||||
})
|
||||
]})
|
||||
self._auto_rewards(order, self.immediate_promotion_program)
|
||||
self.assertEqual(len(order.order_line.ids), 3, "The promo offer should have been applied as we're between the validity dates")
|
||||
order = self.env['sale.order'].create({'partner_id': self.env['res.partner'].create({'name': 'My Partner'}).id})
|
||||
order.write({'order_line': [
|
||||
(0, False, {
|
||||
'product_id': self.product_A.id,
|
||||
'name': '1 Product A',
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 10.0,
|
||||
}),
|
||||
(0, False, {
|
||||
'product_id': self.product_B.id,
|
||||
'name': '2 Product B',
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 1.0,
|
||||
})
|
||||
]})
|
||||
# Invalidate total_order_count
|
||||
self.immediate_promotion_program.invalidate_recordset(['order_count', 'total_order_count'])
|
||||
self._auto_rewards(order, self.immediate_promotion_program)
|
||||
self.assertEqual(len(order.order_line.ids), 2, "The promo offer shouldn't have been applied as the number of uses is exceeded")
|
||||
msg = "The promo offer shouldn't have been applied as the number of uses is exceeded"
|
||||
self.assertEqual(len(order.order_line.ids), 1, msg)
|
||||
|
||||
def test_program_rules_validity_date_timezones(self):
|
||||
"""Test that the validity dates are checked according to the company's time zone"""
|
||||
self.env.company.partner_id.tz = 'Europe/London'
|
||||
self.partner.tz = 'America/Los_Angeles'
|
||||
midnight = Datetime.today()
|
||||
yesterday = (midnight - timedelta(days=1)).date()
|
||||
self.immediate_promotion_program.update({
|
||||
'date_to': yesterday,
|
||||
'limit_usage': True,
|
||||
'max_usage': 1,
|
||||
})
|
||||
order = self.empty_order.with_context(tz=self.partner.tz)
|
||||
order.order_line = [
|
||||
Command.create({'product_id': self.product_A.id}),
|
||||
Command.create({'product_id': self.product_B.id}),
|
||||
]
|
||||
|
||||
with freeze_time(midnight):
|
||||
# Try apply reward at UTC midnight with LA time zone in context (expired)
|
||||
self._auto_rewards(order, self.immediate_promotion_program)
|
||||
self.assertFalse(
|
||||
order.order_line.filtered('is_reward_line'),
|
||||
"Promo should not be applied if only valid in the customer's time zone",
|
||||
)
|
||||
|
||||
with freeze_time(timezone(self.env.company.partner_id.tz).localize(midnight)):
|
||||
# Try apply reward at London midnight (expired)
|
||||
self._auto_rewards(order, self.immediate_promotion_program)
|
||||
self.assertFalse(
|
||||
order.order_line.filtered('is_reward_line'),
|
||||
"Promo should not be applied if only valid in the customer's time zone",
|
||||
)
|
||||
|
||||
self.partner.tz = 'Europe/Brussels'
|
||||
with freeze_time(timezone(self.partner.tz).localize(midnight)):
|
||||
# Apply reward at Brussels midnight (still valid in company's time zone)
|
||||
self._auto_rewards(order, self.immediate_promotion_program)
|
||||
self.assertTrue(
|
||||
order.order_line.filtered('is_reward_line'),
|
||||
"Promo should be applied if valid in the company's time zone",
|
||||
)
|
||||
|
||||
def test_program_rules_validity_date_transactions(self):
|
||||
"""Test that the validity dates are checked according to the time of transaction."""
|
||||
today = Datetime.today()
|
||||
tomorrow = today + timedelta(days=1)
|
||||
self.immediate_promotion_program.update({
|
||||
'date_to': today,
|
||||
'limit_usage': True,
|
||||
'max_usage': 1,
|
||||
'reward_ids': [Command.set(self.program_gift_card.reward_ids.ids)],
|
||||
})
|
||||
order = self.empty_order
|
||||
order.order_line = [
|
||||
Command.create({'product_id': self.product_A.id}),
|
||||
Command.create({'product_id': self.product_B.id}),
|
||||
]
|
||||
|
||||
intial_amount = order.amount_total
|
||||
self._auto_rewards(order, self.immediate_promotion_program)
|
||||
self.assertLess(order.amount_total, intial_amount, "A discount should be applied")
|
||||
|
||||
tx = order.transaction_ids = self._create_transaction(
|
||||
flow='redirect',
|
||||
sale_order_ids=[order.id],
|
||||
state='pending',
|
||||
reference=order.name,
|
||||
amount=order.amount_total,
|
||||
)
|
||||
# Our slow provider only gets around to confirming the transaction the next day
|
||||
with freeze_time(tomorrow):
|
||||
tx._set_done()
|
||||
tx._post_process()
|
||||
self.assertAlmostEqual(
|
||||
order.amount_total, tx.amount,
|
||||
msg="Discount should still apply if transaction gets confirmed post-expiration",
|
||||
)
|
||||
|
||||
def test_buy_x_get_y_free_applies_correctly_with_non_unit_uom(self):
|
||||
buy_x_get_y = self.env['loyalty.program'].create({
|
||||
'name': 'Buy 12 Take 6',
|
||||
'program_type': 'buy_x_get_y',
|
||||
'trigger': 'auto',
|
||||
'applies_on': 'current',
|
||||
'rule_ids': [Command.create({
|
||||
'reward_point_mode': 'unit',
|
||||
'product_ids': self.product_A.ids,
|
||||
})],
|
||||
'reward_ids': [Command.create({
|
||||
'reward_type': 'product',
|
||||
'reward_product_id': self.product_A.id,
|
||||
'required_points': 12,
|
||||
'reward_product_qty': 6,
|
||||
})],
|
||||
})
|
||||
order = self.empty_order
|
||||
order.order_line = [
|
||||
Command.create({
|
||||
'product_id': self.product_A.id,
|
||||
'product_uom_id': self.ref('uom.product_uom_dozen'),
|
||||
'product_uom_qty': 1,
|
||||
}),
|
||||
]
|
||||
self._auto_rewards(order, buy_x_get_y)
|
||||
reward_line = order.order_line.filtered('is_reward_line')
|
||||
self.assertTrue(reward_line)
|
||||
self.assertEqual(reward_line.product_uom_qty, 6)
|
||||
|
|
|
|||
|
|
@ -1,14 +1,35 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.sale_loyalty.tests.common import TestSaleCouponCommon
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.fields import Command
|
||||
|
||||
from odoo.addons.sale_loyalty.tests.common import TestSaleCouponCommon
|
||||
|
||||
|
||||
class TestProgramWithCodeOperations(TestSaleCouponCommon):
|
||||
# Test the basic operation (apply_coupon) on an coupon program on which we should
|
||||
# apply the reward when the code is correct or remove the reward automatically when the reward is
|
||||
# not valid anymore.
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
cls.discount_with_multi_rewards = cls.env['loyalty.program'].create({
|
||||
'name': 'Loyalty program with multiple discount rewards',
|
||||
'program_type': 'coupons',
|
||||
'reward_ids': [
|
||||
Command.create({
|
||||
'reward_type': 'discount',
|
||||
'discount_mode': 'percent',
|
||||
'discount': 20,
|
||||
}),
|
||||
Command.create({
|
||||
'reward_type': 'discount',
|
||||
'discount_mode': 'percent',
|
||||
'discount': 5,
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
def test_program_usability(self):
|
||||
# After clicking "Generate coupons", there is no domain so it shows "Match all records".
|
||||
|
|
@ -32,7 +53,7 @@ class TestProgramWithCodeOperations(TestSaleCouponCommon):
|
|||
|
||||
self.env['loyalty.generate.wizard'].with_context(active_id=self.code_promotion_program.id).create({
|
||||
'mode': 'selected',
|
||||
'customer_ids': self.steve,
|
||||
'customer_ids': self.partner,
|
||||
'points_granted': 1,
|
||||
}).generate_coupons()
|
||||
coupon = self.code_promotion_program.coupon_ids
|
||||
|
|
@ -50,7 +71,6 @@ class TestProgramWithCodeOperations(TestSaleCouponCommon):
|
|||
(0, False, {
|
||||
'product_id': self.product_A.id,
|
||||
'name': '1 Product A',
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 1.0,
|
||||
})
|
||||
]})
|
||||
|
|
@ -87,7 +107,6 @@ class TestProgramWithCodeOperations(TestSaleCouponCommon):
|
|||
(0, False, {
|
||||
'product_id': self.product_A.id,
|
||||
'name': '1 Product A',
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 1.0,
|
||||
})
|
||||
]})
|
||||
|
|
@ -100,7 +119,6 @@ class TestProgramWithCodeOperations(TestSaleCouponCommon):
|
|||
(0, False, {
|
||||
'product_id': self.product_A.id,
|
||||
'name': '1 Product A',
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 1.0,
|
||||
})
|
||||
]})
|
||||
|
|
@ -126,7 +144,6 @@ class TestProgramWithCodeOperations(TestSaleCouponCommon):
|
|||
|
||||
first_pricelist = self.env['product.pricelist'].create({
|
||||
'name': 'First pricelist',
|
||||
'discount_policy': 'with_discount',
|
||||
'item_ids': [(0, 0, {
|
||||
'compute_price': 'percentage',
|
||||
'base': 'list_price',
|
||||
|
|
@ -142,7 +159,6 @@ class TestProgramWithCodeOperations(TestSaleCouponCommon):
|
|||
(0, False, {
|
||||
'product_id': self.product_C.id,
|
||||
'name': '1 Product C',
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 1.0,
|
||||
})
|
||||
]})
|
||||
|
|
@ -199,7 +215,6 @@ class TestProgramWithCodeOperations(TestSaleCouponCommon):
|
|||
(0, False, {
|
||||
'product_id': self.third_product.id,
|
||||
'name': '1 Third Product',
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 1.0,
|
||||
})
|
||||
]})
|
||||
|
|
@ -213,7 +228,6 @@ class TestProgramWithCodeOperations(TestSaleCouponCommon):
|
|||
(0, False, {
|
||||
'product_id': self.product_A.id,
|
||||
'name': '1 Product A',
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 1.0,
|
||||
})
|
||||
]})
|
||||
|
|
@ -229,7 +243,6 @@ class TestProgramWithCodeOperations(TestSaleCouponCommon):
|
|||
(0, False, {
|
||||
'product_id': self.product_B.id,
|
||||
'name': '1 Product B',
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 1.0,
|
||||
})
|
||||
]})
|
||||
|
|
@ -258,7 +271,6 @@ class TestProgramWithCodeOperations(TestSaleCouponCommon):
|
|||
(0, False, {
|
||||
'product_id': self.product_A.id,
|
||||
'name': '1 Product A',
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 1.0,
|
||||
})
|
||||
]})
|
||||
|
|
@ -269,7 +281,6 @@ class TestProgramWithCodeOperations(TestSaleCouponCommon):
|
|||
(0, False, {
|
||||
'product_id': self.product_B.id,
|
||||
'name': '1 Product B',
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 1.0,
|
||||
})
|
||||
]})
|
||||
|
|
@ -283,6 +294,128 @@ class TestProgramWithCodeOperations(TestSaleCouponCommon):
|
|||
order_bis._update_programs_and_rewards()
|
||||
self.assertEqual(len(order_bis.order_line), 2, "Free product from a coupon generated from a promotion program on next order should not dissapear")
|
||||
|
||||
def test_partner_assigned_to_next_order_coupon(self):
|
||||
""" Test the assignment of a partner on coupons with program type `next_order_coupons`.
|
||||
|
||||
1. Create a loyalty program of type `next_order_coupons`.
|
||||
2. Create a sale order and add a product to it.
|
||||
3. Apply the loyalty program to the sale order.
|
||||
4. Verify that the generated coupon is assigned to the order's partner.
|
||||
"""
|
||||
loyalty_program = self.env['loyalty.program'].create({
|
||||
'name': '10% Discount on Next Order',
|
||||
'program_type': 'next_order_coupons',
|
||||
'applies_on': 'future',
|
||||
'trigger': 'auto',
|
||||
'rule_ids': [Command.create({})],
|
||||
'reward_ids': [Command.create({
|
||||
'reward_type': 'discount',
|
||||
'discount': 10,
|
||||
'discount_mode': 'percent',
|
||||
'discount_applicability': 'order',
|
||||
})],
|
||||
})
|
||||
order = self.empty_order
|
||||
order.write({'order_line': [
|
||||
Command.create({
|
||||
'product_id': self.product_A.id,
|
||||
'name': '1 Product A',
|
||||
'product_uom_qty': 1.0,
|
||||
})
|
||||
]})
|
||||
generated_coupons = order._try_apply_program(loyalty_program).get('coupon')
|
||||
self.assertTrue(generated_coupons, "A coupon should have been generated")
|
||||
self.assertEqual(generated_coupons.partner_id, order.partner_id,
|
||||
"The partner should be set on the coupon with program type 'next_order_coupons'"
|
||||
)
|
||||
|
||||
def test_public_partner_updated_in_next_order_coupon(self):
|
||||
""" Test the update of a partner on coupons with program type `next_order_coupons`.
|
||||
|
||||
1. Create a loyalty program of type `next_order_coupons`.
|
||||
2. Create a sale order for a public user and add a product to it.
|
||||
3. Apply the loyalty program to the sale order.
|
||||
4. Verify that the generated coupon is assigned to the public user.
|
||||
5. Change the partner.
|
||||
6. Verify that the generated coupon was updated to this new user.
|
||||
"""
|
||||
loyalty_program = self.env['loyalty.program'].create({
|
||||
'name': "10% Discount on Next Order",
|
||||
'program_type': 'next_order_coupons',
|
||||
'applies_on': 'future',
|
||||
'trigger': 'auto',
|
||||
'rule_ids': [Command.create({})],
|
||||
'reward_ids': [Command.create({
|
||||
'reward_type': 'discount',
|
||||
'discount': 10,
|
||||
'discount_mode': 'percent',
|
||||
'discount_applicability': 'order',
|
||||
})],
|
||||
})
|
||||
order = self.empty_order
|
||||
order.write({
|
||||
'partner_id': self.env.ref('base.public_partner').id,
|
||||
'order_line': [Command.create({'product_id': self.product_A.id})],
|
||||
})
|
||||
generated_coupons = order._try_apply_program(loyalty_program).get('coupon')
|
||||
self.assertTrue(generated_coupons, "A coupon should have been generated")
|
||||
self.assertEqual(
|
||||
generated_coupons.partner_id, order.partner_id,
|
||||
"The partner should be set on the coupon with program type 'next_order_coupons'",
|
||||
)
|
||||
self.assertTrue(generated_coupons.partner_id.is_public)
|
||||
|
||||
# Change partner from Public User to a known customer (e.g. a portal user logging in)
|
||||
order.partner_id = self.partner
|
||||
order._update_programs_and_rewards()
|
||||
self.assertEqual(
|
||||
generated_coupons.partner_id, self.partner,
|
||||
"The coupon's partner_id should be updated if it was created for a Public User",
|
||||
)
|
||||
|
||||
def test_change_reward_on_confirmed_order(self):
|
||||
"""Check that changing rewards on a confirmed order restores points on the coupon.
|
||||
Tested flow:
|
||||
- have a coupon program with 2 discount rewards;
|
||||
- have confirmed order;
|
||||
- apply a 10% discount reward, costing 1 point;
|
||||
- change to a 50% discount reward, costing 5 points;
|
||||
- check that there are still 5 points left on the coupon.
|
||||
"""
|
||||
program = self.code_promotion_program_with_discount
|
||||
program.update({
|
||||
'rule_ids': [Command.clear()],
|
||||
'reward_ids': [Command.create({
|
||||
'discount': 50,
|
||||
'discount_mode': 'percent',
|
||||
'discount_applicability': 'order',
|
||||
'required_points': 5,
|
||||
})],
|
||||
})
|
||||
discount10, discount50 = program.reward_ids
|
||||
|
||||
self.env['loyalty.generate.wizard'].with_context(active_id=program.id).create({
|
||||
'coupon_qty': 1,
|
||||
'points_granted': 10,
|
||||
}).generate_coupons()
|
||||
coupon = program.coupon_ids
|
||||
|
||||
order = self.empty_order
|
||||
order.order_line = [Command.create({'product_id': self.product_C.id})]
|
||||
order.action_confirm()
|
||||
|
||||
order.order_line.product_updatable = True # in case `sale_project` is installed
|
||||
order._apply_program_reward(discount10, coupon)
|
||||
reward_line = order.order_line.filtered('is_reward_line')
|
||||
self.assertEqual(order.amount_total, 90, "10% discount should be applied")
|
||||
self.assertEqual(coupon.points, 9, "10% discount reward should use 1 point")
|
||||
|
||||
order.order_line.product_updatable = True # in case `sale_project` is installed
|
||||
order._apply_program_reward(discount50, coupon)
|
||||
self.assertIn(reward_line, order.order_line, "Reward line should be re-used")
|
||||
self.assertEqual(order.amount_total, 50, "50% discount should be applied")
|
||||
self.assertEqual(coupon.points, 5, "50% discount reward should use 5 points")
|
||||
|
||||
def test_edit_and_reapply_promotion_program(self):
|
||||
# The flow:
|
||||
# 1. Create a program auto applied, giving a fixed amount discount
|
||||
|
|
@ -310,7 +443,6 @@ class TestProgramWithCodeOperations(TestSaleCouponCommon):
|
|||
(0, False, {
|
||||
'product_id': self.product_A.id,
|
||||
'name': '1 Product A',
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 1.0,
|
||||
})
|
||||
]})
|
||||
|
|
@ -331,3 +463,148 @@ class TestProgramWithCodeOperations(TestSaleCouponCommon):
|
|||
self._apply_promo_code(order, 'test')
|
||||
# But the above line should not add any reward
|
||||
self.assertEqual(len(order.order_line), 2, "You should get a discount line") # product + discount
|
||||
|
||||
def test_reapply_multiple_global_rewards_when_new_discount_greater(self):
|
||||
""" Test applying the maximum reward discount from multiple rewards when the applied
|
||||
coupon discount is lower.
|
||||
|
||||
1. Create a two loyalty program of type `coupons`.
|
||||
2. Add multiple rewards to the second program.
|
||||
3. Generate a coupon for each program.
|
||||
2. Create a sale order and add a product to it.
|
||||
3. Apply the Coupon to the sale order.
|
||||
4. Try to apply the second coupon with multiple rewards.
|
||||
5. Reward with best discount will be shown.
|
||||
"""
|
||||
self.code_promotion_program_with_discount.rule_ids.unlink()
|
||||
coupon_1 = self._generate_coupons(self.code_promotion_program_with_discount)
|
||||
coupon_2 = self._generate_coupons(self.discount_with_multi_rewards)
|
||||
|
||||
order = self.empty_order
|
||||
self.assertEqual(order.amount_total, 0.0)
|
||||
order.write({'order_line': [
|
||||
Command.create({
|
||||
'product_id': self.product_A.id,
|
||||
'name': '1 Product A',
|
||||
'product_uom_qty': 1.0,
|
||||
}),
|
||||
Command.create({
|
||||
'product_id': self.product_B.id,
|
||||
'name': '1 Product B',
|
||||
'product_uom_qty': 1.0,
|
||||
}),
|
||||
]})
|
||||
|
||||
# The order line should be created with the correct price unit
|
||||
self.assertEqual(len(order.order_line.ids), 2)
|
||||
self.assertEqual(order.order_line[0].price_unit, 100.0)
|
||||
self.assertEqual(order.order_line[1].price_unit, 5.0)
|
||||
expected_total = order.amount_total * 0.80
|
||||
|
||||
# Apply the first coupon
|
||||
self._apply_promo_code(order, coupon_1.code)
|
||||
self.assertEqual(len(order.order_line.ids), 3)
|
||||
msg = "The discount line should be the 10% discount on the sale order total."
|
||||
self.assertEqual(order.order_line[2].price_unit, -10.5, msg=msg)
|
||||
|
||||
# Apply the second coupon with multiple rewards
|
||||
self._apply_promo_code(order, coupon_2.code)
|
||||
self.assertEqual(len(order.order_line.ids), 3)
|
||||
msg = "The discount line should be the 20% discount on the sale order total."
|
||||
self.assertEqual(order.order_line[2].price_unit, -21.0, msg=msg)
|
||||
msg = "Order total should reflect the 20% discount"
|
||||
self.assertAlmostEqual(order.amount_total, expected_total, msg=msg)
|
||||
|
||||
def test_reapply_multiple_higher_global_rewards_lets_choose_best(self):
|
||||
""" Test applying the maximum reward discount from multiple rewards when the applied
|
||||
coupon discount is lower.
|
||||
|
||||
1. Create a two loyalty program of type `coupons`.
|
||||
2. Add multiple rewards of higher discount than the applied discount.
|
||||
3. Generate a coupon for each program.
|
||||
2. Create a sale order and add a product to it.
|
||||
3. Apply the Coupon to the sale order.
|
||||
4. Try to apply the second coupon with multiple rewards.
|
||||
5. Reward with max discount will be shown.
|
||||
"""
|
||||
self.code_promotion_program_with_discount.rule_ids.unlink()
|
||||
coupon_1 = self._generate_coupons(self.code_promotion_program_with_discount)
|
||||
self.discount_with_multi_rewards.reward_ids[1].discount = 15
|
||||
coupon_2 = self._generate_coupons(self.discount_with_multi_rewards)
|
||||
|
||||
order = self.empty_order
|
||||
self.assertEqual(order.amount_total, 0.0)
|
||||
order.write({'order_line': [
|
||||
Command.create({
|
||||
'product_id': self.product_A.id,
|
||||
'name': '1 Product A',
|
||||
'product_uom_qty': 1.0,
|
||||
})
|
||||
]})
|
||||
|
||||
# The order line should be created with the correct price unit
|
||||
self.assertEqual(len(order.order_line.ids), 1)
|
||||
self.assertEqual(order.order_line[0].price_unit, 100.0)
|
||||
expected_total = order.amount_total * 0.80
|
||||
|
||||
# Apply the first coupon
|
||||
self._apply_promo_code(order, coupon_1.code)
|
||||
self.assertEqual(len(order.order_line.ids), 2)
|
||||
msg = "The discount line should be the 10% discount on the sale order total."
|
||||
self.assertEqual(order.order_line[1].price_unit, -10.0, msg=msg)
|
||||
|
||||
# Apply the second coupon with multiple rewards of higher discount
|
||||
rewards = self._apply_promo_code(order, coupon_2.code)
|
||||
self.assertEqual(len(rewards), 2)
|
||||
|
||||
# Choose the reward with the maximum discount
|
||||
chosen_reward = rewards.filtered(lambda r: r.discount == 20)
|
||||
order._apply_program_reward(chosen_reward, coupon_2)
|
||||
self.assertEqual(len(order.order_line), 2)
|
||||
msg = "The discount line should be the 20% discount on the sale order total."
|
||||
self.assertEqual(order.order_line[1].price_unit, -20.0, msg=msg)
|
||||
msg = "Order total should reflect the 20% discount"
|
||||
self.assertAlmostEqual(order.amount_total, expected_total, msg=msg)
|
||||
|
||||
def test_reapplying_new_multiple_lower_global_rewards_discount_raise_validation(self):
|
||||
""" Test raising validation when the new coupon discount from multiple rewards
|
||||
is less than the applied coupon discount.
|
||||
|
||||
1. Create a two loyalty program of type `coupons`.
|
||||
2. Add multiple rewards to the second program.
|
||||
3. Generate a coupon for each program.
|
||||
2. Create a sale order and add a product to it.
|
||||
3. Apply the Coupon to the sale order.
|
||||
4. Try to apply the second coupon with multiple rewards.
|
||||
5. Verify that it raises a validation error.
|
||||
"""
|
||||
self.code_promotion_program_with_discount.rule_ids.unlink()
|
||||
coupon_1 = self._generate_coupons(self.code_promotion_program_with_discount)
|
||||
self.discount_with_multi_rewards.reward_ids[0].discount = 7
|
||||
coupon_2 = self._generate_coupons(self.discount_with_multi_rewards)
|
||||
|
||||
order = self.empty_order
|
||||
self.assertEqual(order.amount_total, 0.0)
|
||||
order.write({'order_line': [
|
||||
Command.create({
|
||||
'product_id': self.product_A.id,
|
||||
'name': '1 Product A',
|
||||
'product_uom_qty': 1.0,
|
||||
})
|
||||
]})
|
||||
|
||||
# The order line should be created with the correct price unit
|
||||
self.assertEqual(len(order.order_line.ids), 1)
|
||||
self.assertEqual(order.order_line[0].price_unit, 100.0)
|
||||
|
||||
# Apply the first coupon
|
||||
self._apply_promo_code(order, coupon_1.code)
|
||||
msg = "The discount line should be the 10% discount on the sale order total."
|
||||
self.assertEqual(order.order_line[1].price_unit, -10.0, msg=msg)
|
||||
self.assertEqual(len(order.order_line.ids), 2)
|
||||
|
||||
# raise validation error when applying the second coupon with multiple rewards
|
||||
# with a discount lower than the applied coupon discount
|
||||
msg = "The new coupon discount should be greater than the applied coupon discount"
|
||||
with self.assertRaises(ValidationError, msg=msg):
|
||||
self._apply_promo_code(order, coupon_2.code)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.sale_loyalty.tests.common import TestSaleCouponCommon
|
||||
|
|
@ -18,7 +17,6 @@ class TestProgramWithoutCodeOperations(TestSaleCouponCommon):
|
|||
(0, False, {
|
||||
'product_id': self.product_A.id,
|
||||
'name': '1 Product A',
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 1.0,
|
||||
})
|
||||
]})
|
||||
|
|
@ -31,7 +29,6 @@ class TestProgramWithoutCodeOperations(TestSaleCouponCommon):
|
|||
(0, False, {
|
||||
'product_id': self.product_B.id,
|
||||
'name': '2 Product B',
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 1.0,
|
||||
})
|
||||
]})
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.fields import Command
|
||||
from odoo.tests import tagged
|
||||
|
||||
from odoo.addons.sale_loyalty.tests.common import TestSaleCouponCommon
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestSaleAutoInvoice(TestSaleCouponCommon):
|
||||
|
||||
def test_automatic_invoice_on_zero_amount_order(self):
|
||||
self.env['ir.config_parameter'].sudo().set_param('sale.automatic_invoice', 'True')
|
||||
# Create a loyalty program with 100% discount
|
||||
self.env['loyalty.program'].sudo().create({
|
||||
'name': '100discount',
|
||||
'program_type': 'promo_code',
|
||||
'rule_ids': [
|
||||
Command.create({
|
||||
'code': "100dis",
|
||||
'minimum_amount': 0,
|
||||
})
|
||||
],
|
||||
'reward_ids': [
|
||||
Command.create({
|
||||
'discount': 100,
|
||||
}),
|
||||
],
|
||||
})
|
||||
# Add order line to order
|
||||
self.env["sale.order.line"].create({
|
||||
'order_id': self.empty_order.id,
|
||||
'product_id': self.product_A.id,
|
||||
'product_uom_qty': 1,
|
||||
'price_unit': 200,
|
||||
})
|
||||
# Apply discount
|
||||
self._apply_promo_code(self.empty_order, '100dis')
|
||||
self.empty_order._validate_order()
|
||||
self.assertTrue(
|
||||
self.empty_order.invoice_ids,
|
||||
"Invoices should be generated for orders with zero total amount",
|
||||
)
|
||||
|
|
@ -1,10 +1,9 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.sale_loyalty.tests.common import TestSaleCouponCommon
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tests import tagged
|
||||
|
||||
from odoo.addons.sale_loyalty.tests.common import TestSaleCouponCommon
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestSaleInvoicing(TestSaleCouponCommon):
|
||||
|
|
@ -50,17 +49,19 @@ class TestSaleInvoicing(TestSaleCouponCommon):
|
|||
|
||||
order.action_confirm()
|
||||
invoiceable_lines = order._get_invoiceable_lines()
|
||||
# Product was not delivered, we cannot invoice
|
||||
# the product line nor the promotion line
|
||||
order._compute_invoice_status()
|
||||
# Product was not delivered, the order invoice status is 'No' as invoicing it should not be
|
||||
# promoted, but the reward line should still be invoiceable, if users wants to invoice it
|
||||
self.assertEqual(order.invoice_status, 'no')
|
||||
self.assertEqual(len(invoiceable_lines), 1)
|
||||
|
||||
inv = order._create_invoices()
|
||||
self.assertEqual(len(inv.invoice_line_ids), 1)
|
||||
invoiceable_lines = order._get_invoiceable_lines()
|
||||
self.assertEqual(len(invoiceable_lines), 0)
|
||||
with self.assertRaises(UserError):
|
||||
order._create_invoices()
|
||||
inv.button_cancel()
|
||||
|
||||
order.order_line[0].qty_delivered = 1
|
||||
# Product is delivered, the two lines can be invoiced.
|
||||
order._compute_invoice_status()
|
||||
self.assertEqual(order.invoice_status, 'to invoice')
|
||||
invoiceable_lines = order._get_invoiceable_lines()
|
||||
self.assertEqual(order.order_line, invoiceable_lines)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import Command
|
||||
from datetime import timedelta
|
||||
|
||||
from odoo.fields import Command, Date
|
||||
from odoo.tests.common import tagged
|
||||
|
||||
from odoo.addons.sale_loyalty.tests.common import TestSaleCouponCommon
|
||||
from odoo.tests.common import tagged
|
||||
|
||||
|
||||
@tagged('-at_install', 'post_install')
|
||||
|
|
@ -36,13 +38,11 @@ class TestUnlinkReward(TestSaleCouponCommon):
|
|||
Command.create({
|
||||
'product_id': self.product_A.id,
|
||||
'name': 'Ordinary Product A',
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 1.0,
|
||||
}),
|
||||
Command.create({
|
||||
'product_id': self.product_B.id,
|
||||
'name': '2 Product B',
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 1.0,
|
||||
}),
|
||||
]})
|
||||
|
|
@ -53,3 +53,19 @@ class TestUnlinkReward(TestSaleCouponCommon):
|
|||
# Check that the reward is archived and not deleted
|
||||
self.assertTrue(self.reward.exists())
|
||||
self.assertFalse(self.reward.active)
|
||||
|
||||
def test_unlink_expired_coupon_line(self):
|
||||
"""Ensure that lines linked to expired coupons get unlinked from the order."""
|
||||
order = self.empty_order
|
||||
order.order_line = [Command.create({'product_id': self.product_A.id})]
|
||||
coupon_program = self.code_promotion_program
|
||||
self.env['loyalty.generate.wizard'].with_context(active_id=coupon_program.id).create({
|
||||
'coupon_qty': 1,
|
||||
'points_granted': 1,
|
||||
}).generate_coupons()
|
||||
coupon = coupon_program.coupon_ids
|
||||
self._apply_promo_code(order, coupon.code)
|
||||
self.assertTrue(order.order_line.coupon_id)
|
||||
coupon.expiration_date = Date.today() - timedelta(days=1)
|
||||
order._update_programs_and_rewards()
|
||||
self.assertFalse(order.order_line.coupon_id)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue