mirror of
https://github.com/bringout/oca-ocb-sale.git
synced 2026-04-27 12:12:02 +02:00
Initial commit: Sale packages
This commit is contained in:
commit
14e3d26998
6469 changed files with 2479670 additions and 0 deletions
|
|
@ -0,0 +1,13 @@
|
|||
# -*- 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_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_invoicing
|
||||
from . import test_unlink_reward
|
||||
352
odoo-bringout-oca-ocb-sale_loyalty/sale_loyalty/tests/common.py
Normal file
352
odoo-bringout-oca-ocb-sale_loyalty/sale_loyalty/tests/common.py
Normal file
|
|
@ -0,0 +1,352 @@
|
|||
# -*- 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.addons.sale.tests.test_sale_product_attribute_value_config import TestSaleProductAttributeValueCommon
|
||||
|
||||
|
||||
class TestSaleCouponCommon(TestSaleProductAttributeValueCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls, chart_template_ref=None):
|
||||
super().setUpClass(chart_template_ref=chart_template_ref)
|
||||
# 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
|
||||
cls.tax_15pc_excl = cls.env['account.tax'].create({
|
||||
'name': "Tax 15%",
|
||||
'amount_type': 'percent',
|
||||
'amount': 15,
|
||||
'type_tax_use': 'sale',
|
||||
})
|
||||
|
||||
cls.tax_10pc_incl = cls.env['account.tax'].create({
|
||||
'name': "10% Tax incl",
|
||||
'amount_type': 'percent',
|
||||
'amount': 10,
|
||||
'price_include': True,
|
||||
})
|
||||
|
||||
cls.tax_10pc_base_incl = cls.env['account.tax'].create({
|
||||
'name': "10% Tax incl base amount",
|
||||
'amount_type': 'percent',
|
||||
'amount': 10,
|
||||
'price_include': True,
|
||||
'include_base_amount': True,
|
||||
})
|
||||
|
||||
cls.tax_10pc_excl = cls.env['account.tax'].create({
|
||||
'name': "10% Tax excl",
|
||||
'amount_type': 'percent',
|
||||
'amount': 10,
|
||||
'price_include': False,
|
||||
})
|
||||
|
||||
cls.tax_20pc_excl = cls.env['account.tax'].create({
|
||||
'name': "20% Tax excl",
|
||||
'amount_type': 'percent',
|
||||
'amount': 20,
|
||||
'price_include': False,
|
||||
})
|
||||
|
||||
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)],
|
||||
})
|
||||
|
||||
#products
|
||||
cls.product_A = cls.env['product.product'].create({
|
||||
'name': 'Product A',
|
||||
'list_price': 100,
|
||||
'sale_ok': True,
|
||||
'taxes_id': [(6, 0, [cls.tax_15pc_excl.id])],
|
||||
})
|
||||
|
||||
cls.product_B = cls.env['product.product'].create({
|
||||
'name': 'Product B',
|
||||
'list_price': 5,
|
||||
'sale_ok': True,
|
||||
'taxes_id': [(6, 0, [cls.tax_15pc_excl.id])],
|
||||
})
|
||||
|
||||
cls.product_C = cls.env['product.product'].create({
|
||||
'name': 'Product C',
|
||||
'list_price': 100,
|
||||
'sale_ok': True,
|
||||
'taxes_id': [(6, 0, [])],
|
||||
})
|
||||
|
||||
cls.product_D = cls.env['product.product'].create({
|
||||
'name': 'Product D',
|
||||
'list_price': 100,
|
||||
'sale_ok': True,
|
||||
'taxes_id': [(6, 0, [cls.tax_group.id])],
|
||||
})
|
||||
|
||||
cls.product_gift_card = cls.env['product.product'].create({
|
||||
'name': 'Gift Card 50',
|
||||
'detailed_type': 'service',
|
||||
'list_price': 50,
|
||||
'sale_ok': True,
|
||||
'taxes_id': False,
|
||||
})
|
||||
|
||||
# Immediate Program By A + B: get B free
|
||||
# No Conditions
|
||||
cls.program_gift_card = cls.env['loyalty.program'].create({
|
||||
'name': 'Gift Cards',
|
||||
'applies_on': 'future',
|
||||
'program_type': 'gift_card',
|
||||
'trigger': 'auto',
|
||||
'rule_ids': [(0, 0, {
|
||||
'product_ids': cls.product_gift_card,
|
||||
'reward_point_amount': 1,
|
||||
'reward_point_mode': 'money',
|
||||
'reward_point_split': True,
|
||||
})],
|
||||
'reward_ids': [(0, 0, {
|
||||
'reward_type': 'discount',
|
||||
'discount': 1,
|
||||
'discount_mode': 'per_point',
|
||||
'discount_applicability': 'order',
|
||||
})]
|
||||
})
|
||||
cls.immediate_promotion_program = cls.env['loyalty.program'].create({
|
||||
'name': 'Buy A + 1 B, 1 B are free',
|
||||
'program_type': 'promotion',
|
||||
'applies_on': 'current',
|
||||
'company_id': cls.env.company.id,
|
||||
'trigger': 'auto',
|
||||
'rule_ids': [(0, 0, {
|
||||
'product_ids': cls.product_A,
|
||||
'reward_point_amount': 1,
|
||||
'reward_point_mode': 'order',
|
||||
'minimum_qty': 1,
|
||||
})],
|
||||
'reward_ids': [(0, 0, {
|
||||
'reward_type': 'product',
|
||||
'reward_product_id': cls.product_B.id,
|
||||
'reward_product_qty': 1,
|
||||
'required_points': 1,
|
||||
})],
|
||||
})
|
||||
cls.code_promotion_program = cls.env['loyalty.program'].create({
|
||||
'name': 'Buy 1 A + Enter code, 1 A is free',
|
||||
'program_type': 'coupons',
|
||||
'trigger': 'with_code',
|
||||
'applies_on': 'current',
|
||||
'company_id': cls.env.company.id,
|
||||
'rule_ids': [(0, 0, {
|
||||
'product_ids': cls.product_A,
|
||||
'reward_point_amount': 1,
|
||||
'reward_point_mode': 'order',
|
||||
'minimum_qty': 1,
|
||||
})],
|
||||
'reward_ids': [(0, 0, {
|
||||
'reward_type': 'product',
|
||||
'reward_product_id': cls.product_A.id,
|
||||
'reward_product_qty': 1,
|
||||
'required_points': 1,
|
||||
})],
|
||||
})
|
||||
cls.code_promotion_program_with_discount = cls.env['loyalty.program'].create({
|
||||
'name': 'Buy 1 C + Enter code, 10 percent discount on C',
|
||||
'program_type': 'coupons',
|
||||
'trigger': 'with_code',
|
||||
'applies_on': 'current',
|
||||
'company_id': cls.env.company.id,
|
||||
'rule_ids': [(0, 0, {
|
||||
'mode': 'with_code',
|
||||
'code': 'promotion_code_disc',
|
||||
'product_ids': cls.product_C,
|
||||
'reward_point_amount': 1,
|
||||
'reward_point_mode': 'order',
|
||||
'minimum_qty': 1,
|
||||
})],
|
||||
'reward_ids': [(0, 0, {
|
||||
'reward_type': 'discount',
|
||||
'discount_mode': 'percent',
|
||||
'discount': 10,
|
||||
'discount_applicability': 'order',
|
||||
'required_points': 1,
|
||||
})],
|
||||
})
|
||||
|
||||
def _extract_rewards_from_claimable(self, status):
|
||||
rewards = self.env['loyalty.reward']
|
||||
for info in status.values():
|
||||
for reward_count in info['rewards']:
|
||||
rewards |= reward_count[0]
|
||||
|
||||
def _apply_promo_code(self, order, code, no_reward_fail=True):
|
||||
status = order._try_apply_code(code)
|
||||
if 'error' in status:
|
||||
raise ValidationError(status['error'])
|
||||
if not status and no_reward_fail:
|
||||
# Can happen if global discount got filtered out in `_get_claimable_rewards`
|
||||
raise ValidationError('No reward to claim with this coupon')
|
||||
coupons = self.env['loyalty.card']
|
||||
rewards = self.env['loyalty.reward']
|
||||
for coupon, coupon_rewards in status.items():
|
||||
coupons |= coupon
|
||||
rewards |= coupon_rewards
|
||||
if len(coupons) == 1 and len(rewards) == 1:
|
||||
status = order._apply_program_reward(rewards, coupons)
|
||||
if 'error' in status:
|
||||
raise ValidationError(status['error'])
|
||||
|
||||
def _claim_reward(self, order, program, coupon=False):
|
||||
if len(program.reward_ids) != 1:
|
||||
return False
|
||||
coupon = coupon or order.coupon_point_ids.coupon_id.filtered(lambda c: c.program_id == program)
|
||||
if len(coupon) != 1:
|
||||
return False
|
||||
status = order._apply_program_reward(program.reward_ids, coupon)
|
||||
return 'error' not in status
|
||||
|
||||
def _auto_rewards(self, order, programs):
|
||||
order._update_programs_and_rewards()
|
||||
coupons_per_program = defaultdict(lambda: self.env['loyalty.card'])
|
||||
for coupon in order.coupon_point_ids.coupon_id:
|
||||
coupons_per_program[coupon.program_id] |= coupon
|
||||
for program in programs:
|
||||
if len(program.reward_ids) > 1 or len(coupons_per_program[program]) != 1 or not program.active:
|
||||
continue
|
||||
self._claim_reward(order, program, coupons_per_program[program])
|
||||
|
||||
class TestSaleCouponNumbersCommon(TestSaleCouponCommon):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
cls.largeCabinet = cls.env['product.product'].create({
|
||||
'name': 'Large Cabinet',
|
||||
'list_price': 320.0,
|
||||
'taxes_id': False,
|
||||
})
|
||||
cls.conferenceChair = cls.env['product.product'].create({
|
||||
'name': 'Conference Chair',
|
||||
'list_price': 16.5,
|
||||
'taxes_id': False,
|
||||
})
|
||||
cls.pedalBin = cls.env['product.product'].create({
|
||||
'name': 'Pedal Bin',
|
||||
'list_price': 47.0,
|
||||
'taxes_id': False,
|
||||
})
|
||||
cls.drawerBlack = cls.env['product.product'].create({
|
||||
'name': 'Drawer Black',
|
||||
'list_price': 25.0,
|
||||
'taxes_id': False,
|
||||
})
|
||||
cls.largeMeetingTable = cls.env['product.product'].create({
|
||||
'name': 'Large Meeting Table',
|
||||
'list_price': 40000.0,
|
||||
'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',
|
||||
'program_type': 'promotion',
|
||||
'applies_on': 'current',
|
||||
'rule_ids': [(0, 0, {
|
||||
'mode': 'with_code',
|
||||
'code': 'test_10pc',
|
||||
})],
|
||||
'reward_ids': [(0, 0, {
|
||||
'reward_type': 'discount',
|
||||
'discount_mode': 'percent',
|
||||
'discount': 10,
|
||||
'discount_applicability': 'order',
|
||||
'required_points': 1,
|
||||
})],
|
||||
})
|
||||
cls.p2 = cls.env['loyalty.program'].create({
|
||||
'name': 'Buy 3 cabinets, get one for free',
|
||||
'trigger': 'auto',
|
||||
'program_type': 'promotion',
|
||||
'applies_on': 'current',
|
||||
'rule_ids': [(0, 0, {
|
||||
'product_ids': cls.largeCabinet,
|
||||
'reward_point_mode': 'unit',
|
||||
'minimum_qty': 3,
|
||||
})],
|
||||
'reward_ids': [(0, 0, {
|
||||
'reward_type': 'product',
|
||||
'reward_product_id': cls.largeCabinet.id,
|
||||
'reward_product_qty': 1,
|
||||
'required_points': 3,
|
||||
})],
|
||||
})
|
||||
cls.p3 = cls.env['loyalty.program'].create({
|
||||
'name': 'Buy 1 drawer black, get a free Large Meeting Table',
|
||||
'trigger': 'auto',
|
||||
'program_type': 'promotion',
|
||||
'applies_on': 'current',
|
||||
'rule_ids': [(0, 0, {
|
||||
'product_ids': cls.drawerBlack,
|
||||
'reward_point_mode': 'order',
|
||||
'minimum_qty': 1,
|
||||
})],
|
||||
'reward_ids': [(0, 0, {
|
||||
'reward_type': 'product',
|
||||
'reward_product_id': cls.largeMeetingTable.id,
|
||||
'reward_product_qty': 1,
|
||||
'required_points': 1,
|
||||
})],
|
||||
})
|
||||
cls.discount_coupon_program = cls.env['loyalty.program'].create({
|
||||
'name': '$100 coupon',
|
||||
'program_type': 'coupons',
|
||||
'trigger': 'with_code',
|
||||
'applies_on': 'current',
|
||||
'rule_ids': [(0, 0, {
|
||||
'minimum_amount': 100,
|
||||
})],
|
||||
'reward_ids': [(0, 0, {
|
||||
'reward_type': 'discount',
|
||||
'discount_mode': 'per_point',
|
||||
'discount': 100,
|
||||
'discount_applicability': 'order',
|
||||
'required_points': 1,
|
||||
})],
|
||||
})
|
||||
cls.all_programs = cls.env['loyalty.program'].search([])
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import Command
|
||||
|
||||
from odoo.addons.sale_loyalty.tests.common import TestSaleCouponCommon
|
||||
from odoo.tests.common import tagged
|
||||
|
||||
@tagged('-at_install', 'post_install')
|
||||
class TestBuyGiftCard(TestSaleCouponCommon):
|
||||
|
||||
def test_buying_gift_card(self):
|
||||
order = self.empty_order
|
||||
self.immediate_promotion_program.active = False
|
||||
order.write({'order_line': [
|
||||
(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,
|
||||
})
|
||||
]})
|
||||
self.assertEqual(len(order.order_line.ids), 2)
|
||||
self.assertEqual(len(order._get_reward_coupons()), 0)
|
||||
order._update_programs_and_rewards()
|
||||
self.assertEqual(len(order._get_reward_coupons()), 1)
|
||||
order.order_line[1].product_uom_qty = 2
|
||||
order._update_programs_and_rewards()
|
||||
self.assertEqual(len(order._get_reward_coupons()), 2)
|
||||
order.order_line[1].product_uom_qty = 1
|
||||
order._update_programs_and_rewards()
|
||||
self.assertEqual(len(order._get_reward_coupons()), 1)
|
||||
|
||||
def test_gift_card_email_sender(self):
|
||||
"""Ensure that sending gift card emails have a sender.
|
||||
Either the order's salesman if available, otherwise the order's company.
|
||||
"""
|
||||
mail_template = self.env['mail.template'].create({
|
||||
'name': "Gift Card Mail",
|
||||
'model_id': self.env.ref('loyalty.model_loyalty_card').id,
|
||||
'auto_delete': False,
|
||||
})
|
||||
self.program_gift_card.communication_plan_ids = [Command.create({
|
||||
'trigger': 'create',
|
||||
'mail_template_id': mail_template.id,
|
||||
})]
|
||||
order = self.empty_order
|
||||
salesman = order.user_id.partner_id.ensure_one()
|
||||
salesman.email = "sales@company.co"
|
||||
company = order.company_id.partner_id
|
||||
company.email = "noreply@company.co"
|
||||
order.write({
|
||||
'order_line': [Command.create({'product_id': self.product_gift_card.id})],
|
||||
})
|
||||
order._update_programs_and_rewards()
|
||||
|
||||
# Create an order without salesman to test company-based fallback
|
||||
orders = order + order.copy({'user_id': None})
|
||||
|
||||
# Clear out the mailbox before sending mail
|
||||
self.env['mail.mail'].search([]).sudo().unlink()
|
||||
|
||||
# Confirm order as Public User to trigger loyalty mail
|
||||
public_user = self.env.ref('base.public_user')
|
||||
orders.with_user(public_user).with_company(order.company_id).sudo().action_confirm()
|
||||
|
||||
mails = self.env['mail.mail'].search([])
|
||||
self.assertEqual(len(mails), 2)
|
||||
salesman_mail = mails.filtered(lambda m: m.author_id == salesman).ensure_one()
|
||||
company_mail = mails.filtered(lambda m: m.author_id == company).ensure_one()
|
||||
self.assertEqual(salesman_mail.email_from, salesman.email_formatted)
|
||||
self.assertEqual(company_mail.email_from, company.email_formatted)
|
||||
|
|
@ -0,0 +1,915 @@
|
|||
# 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 odoo.tools.float_utils import float_compare
|
||||
|
||||
from odoo.addons.sale_loyalty.tests.common import TestSaleCouponCommon
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestLoyalty(TestSaleCouponCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
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.ewallet_program = cls.env['loyalty.program'].create({
|
||||
'name': 'eWallet Program',
|
||||
'program_type': 'ewallet',
|
||||
'trigger': 'auto',
|
||||
'applies_on': 'future',
|
||||
'reward_ids': [Command.create({
|
||||
'reward_type': 'discount',
|
||||
'discount_mode': 'per_point',
|
||||
'discount': 1,
|
||||
})],
|
||||
'rule_ids': [Command.create({
|
||||
'reward_point_amount': '1',
|
||||
'reward_point_mode': 'money',
|
||||
'product_ids': cls.env.ref('loyalty.ewallet_product_50'),
|
||||
})],
|
||||
'trigger_product_ids': cls.env.ref('loyalty.ewallet_product_50'),
|
||||
})
|
||||
|
||||
cls.ewallet = cls.env['loyalty.card'].create({
|
||||
'program_id': cls.ewallet_program.id,
|
||||
'partner_id': cls.partner_a.id,
|
||||
'points': 10,
|
||||
})
|
||||
cls.ewallet_program.coupon_ids = [Command.set([cls.ewallet.id])]
|
||||
|
||||
cls.user_salemanager = new_test_user(cls.env, login='user_salemanager', groups='sales_team.group_sale_manager')
|
||||
|
||||
cls.promotion_code_10pc = cls.env['loyalty.program'].create({
|
||||
'name': "Code for 10% on orders",
|
||||
'trigger': 'with_code',
|
||||
'program_type': 'promotion',
|
||||
'applies_on': 'current',
|
||||
'rule_ids': [Command.create({
|
||||
'mode': 'with_code',
|
||||
'code': 'test_10pc',
|
||||
})],
|
||||
'reward_ids': [Command.create({
|
||||
'reward_type': 'discount',
|
||||
'discount_mode': 'percent',
|
||||
'discount': 10,
|
||||
'discount_applicability': 'order',
|
||||
'required_points': 1,
|
||||
})],
|
||||
})
|
||||
|
||||
def test_nominative_programs(self):
|
||||
loyalty_program = self.env['loyalty.program'].create({
|
||||
'name': 'Loyalty Program',
|
||||
'program_type': 'loyalty',
|
||||
'trigger': 'auto',
|
||||
'applies_on': 'both',
|
||||
'rule_ids': [(0, 0, {
|
||||
'reward_point_mode': 'unit',
|
||||
'reward_point_amount': 1,
|
||||
'product_ids': [self.product_a.id],
|
||||
})],
|
||||
'reward_ids': [(0, 0, {
|
||||
'reward_type': 'discount',
|
||||
'discount': 1.5,
|
||||
'discount_mode': 'per_point',
|
||||
'discount_applicability': 'order',
|
||||
'required_points': 3,
|
||||
})],
|
||||
})
|
||||
|
||||
order = self.env['sale.order'].create({
|
||||
'partner_id': self.partner_a.id,
|
||||
})
|
||||
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,
|
||||
'points': 10,
|
||||
})
|
||||
self.ewallet.points = 0
|
||||
|
||||
order.write({
|
||||
'order_line': [(0, 0, {
|
||||
'product_id': self.product_a.id,
|
||||
'product_uom_qty': 1,
|
||||
})]
|
||||
})
|
||||
order._update_programs_and_rewards()
|
||||
claimable_rewards = order._get_claimable_rewards()
|
||||
self.assertEqual(len(claimable_rewards), 1, "The ewallet program should not be applicable since the card has no points.")
|
||||
vals = order._get_reward_values_discount(loyalty_program.reward_ids[0], loyalty_card)
|
||||
self.assertEqual(
|
||||
vals[0]['points_cost'] % loyalty_program.reward_ids.required_points,
|
||||
0,
|
||||
"Can only use a whole number of required points",
|
||||
)
|
||||
self.assertEqual(vals[0]['points_cost'], 9, "Use maximum available points for the reward")
|
||||
self.ewallet.points = 50
|
||||
order._update_programs_and_rewards()
|
||||
claimable_rewards = order._get_claimable_rewards()
|
||||
self.assertEqual(len(claimable_rewards), 2, "Now that the ewallet has some points they should both be applicable.")
|
||||
|
||||
def test_cancel_order_with_coupons(self):
|
||||
"""This test ensure that creating an order with coupons will not
|
||||
raise an access error on POS line modele when canceling the order."""
|
||||
|
||||
self.env['loyalty.program'].create({
|
||||
'name': '10% Discount',
|
||||
'program_type': 'coupons',
|
||||
'applies_on': 'current',
|
||||
'trigger': 'auto',
|
||||
'rule_ids': [(0, 0, {})],
|
||||
'reward_ids': [(0, 0, {
|
||||
'reward_type': 'discount',
|
||||
'discount': 10,
|
||||
'discount_mode': 'percent',
|
||||
'discount_applicability': 'order',
|
||||
})]
|
||||
})
|
||||
|
||||
order = self.env['sale.order'].with_user(self.user_salemanager).create({
|
||||
'partner_id': self.partner_a.id,
|
||||
'order_line': [
|
||||
(0, 0, {
|
||||
'product_id': self.product_a.id,
|
||||
})
|
||||
]
|
||||
})
|
||||
|
||||
order._update_programs_and_rewards()
|
||||
self.assertTrue(order.coupon_point_ids)
|
||||
|
||||
# Canceling the order should not raise an access error:
|
||||
# During the cancel process, we are trying to get `use_count` of the coupon,
|
||||
# and we call the `_compute_use_count` that is also in pos_loyalty.
|
||||
# This last one will try to find related POS lines while user have not access to POS.
|
||||
order._action_cancel()
|
||||
self.assertFalse(order.coupon_point_ids)
|
||||
|
||||
def test_distribution_amount_payment_programs(self):
|
||||
"""
|
||||
Check how the amount of a payment reward is distributed.
|
||||
An ewallet should not be used to refund taxes.
|
||||
Its amount must be distributed between the products.
|
||||
"""
|
||||
|
||||
# Create two products
|
||||
product_a, product_b = self.env['product.product'].create([
|
||||
{
|
||||
'name': 'Product A',
|
||||
'list_price': 100,
|
||||
'sale_ok': True,
|
||||
'taxes_id': [Command.set(self.tax_15pc_excl.ids)],
|
||||
},
|
||||
{
|
||||
'name': 'Product B',
|
||||
'list_price': 100,
|
||||
'sale_ok': True,
|
||||
'taxes_id': [Command.set(self.tax_15pc_excl.ids)],
|
||||
},
|
||||
])
|
||||
|
||||
# Create a coupon and a ewallet
|
||||
coupon_program, ewallet_program = self.env['loyalty.program'].create([
|
||||
{
|
||||
'name': 'Coupon Program',
|
||||
'program_type': 'coupons',
|
||||
'trigger': 'with_code',
|
||||
'applies_on': 'both',
|
||||
'reward_ids': [Command.create({
|
||||
'reward_type': 'discount',
|
||||
'discount': 100.0,
|
||||
'discount_applicability': 'specific',
|
||||
'discount_product_domain': '[("name", "=", "Product A")]',
|
||||
})],
|
||||
},
|
||||
{
|
||||
'name': 'eWallet Program',
|
||||
'program_type': 'ewallet',
|
||||
'applies_on': 'future',
|
||||
'trigger': 'auto',
|
||||
'rule_ids': [Command.create({
|
||||
'reward_point_mode': 'money',
|
||||
})],
|
||||
'reward_ids': [Command.create({
|
||||
'discount_mode': 'per_point',
|
||||
'discount': 1,
|
||||
'discount_applicability': 'order',
|
||||
})],
|
||||
}
|
||||
])
|
||||
|
||||
coupon_partner, _ = self.env['loyalty.card'].create([
|
||||
{
|
||||
'program_id': coupon_program.id,
|
||||
'partner_id': self.partner_a.id,
|
||||
'points': 1,
|
||||
'code': '5555',
|
||||
},
|
||||
{
|
||||
'program_id': ewallet_program.id,
|
||||
'partner_id': self.partner_a.id,
|
||||
'points': 115,
|
||||
},
|
||||
])
|
||||
|
||||
# Create the order
|
||||
order = self.env['sale.order'].with_user(self.user_salemanager).create({
|
||||
'partner_id': self.partner_a.id,
|
||||
'order_line': [
|
||||
Command.create({
|
||||
'product_id': product_a.id,
|
||||
}),
|
||||
Command.create({
|
||||
'product_id': product_b.id,
|
||||
}),
|
||||
]
|
||||
})
|
||||
|
||||
self.assertEqual(order.amount_total, 230.0)
|
||||
self.assertEqual(order.amount_untaxed, 200.0)
|
||||
self.assertEqual(order.amount_tax, 30.0)
|
||||
|
||||
# Apply the eWallet
|
||||
order._update_programs_and_rewards()
|
||||
self._claim_reward(order, ewallet_program)
|
||||
|
||||
self.assertEqual(order.amount_total, 115.0)
|
||||
self.assertEqual(order.amount_untaxed, 85.0)
|
||||
self.assertEqual(order.amount_tax, 30.0)
|
||||
self.assertEqual(order.reward_amount, -115.0)
|
||||
|
||||
# Apply the coupon
|
||||
self._apply_promo_code(order, coupon_partner.code)
|
||||
|
||||
self.assertEqual(order.amount_total, 0.0)
|
||||
self.assertEqual(order.amount_untaxed, -15.0)
|
||||
self.assertEqual(order.amount_tax, 15.0)
|
||||
self.assertEqual(order.reward_amount, -215.0)
|
||||
|
||||
def test_discount_max_amount_on_specific_product(self):
|
||||
product_a = self.product_A
|
||||
product_b = self.product_B
|
||||
product_a.write({'taxes_id': [Command.set(self.tax_20pc_excl.ids)]})
|
||||
product_b.write({'list_price': -20, 'taxes_id': [Command.set(self.tax_20pc_excl.ids)]})
|
||||
|
||||
self.env['loyalty.program'].search([]).write({'active': False})
|
||||
promotion = self.env['loyalty.program'].create({
|
||||
'name': '10% Discount',
|
||||
'program_type': 'promotion',
|
||||
'trigger': 'auto',
|
||||
'rule_ids': [Command.create({'reward_point_amount': 1, 'reward_point_mode': 'unit'})],
|
||||
'reward_ids': [Command.create({
|
||||
'discount': 10.0,
|
||||
'discount_max_amount': 9,
|
||||
'discount_applicability': 'specific',
|
||||
'discount_product_ids': [product_a.id],
|
||||
})],
|
||||
})
|
||||
|
||||
order = self.env['sale.order'].create({
|
||||
'partner_id': self.partner_a.id,
|
||||
'order_line': [Command.create({'product_id': product_a.id})],
|
||||
})
|
||||
self.assertEqual(order.reward_amount, 0)
|
||||
|
||||
self._auto_rewards(order, promotion)
|
||||
reward_amount_tax_included = sum(l.price_total for l in order.order_line if l.reward_id)
|
||||
msg = "Max discount amount reached, the reward amount should be the max amount value."
|
||||
self.assertEqual(reward_amount_tax_included, -9, msg)
|
||||
|
||||
order.order_line = [Command.clear(), Command.create({'product_id': product_b.id})]
|
||||
self._auto_rewards(order, promotion)
|
||||
reward_amount_tax_included = sum(l.price_total for l in order.order_line if l.reward_id)
|
||||
msg = "This product is not eligible to the discount."
|
||||
self.assertEqual(reward_amount_tax_included, 0, msg=msg)
|
||||
|
||||
order.order_line = [
|
||||
Command.clear(),
|
||||
Command.create({'product_id': product_a.id}), # price_total = 120
|
||||
Command.create({'product_id': product_b.id}), # price_total = -20
|
||||
]
|
||||
self._auto_rewards(order, promotion)
|
||||
reward_amount_tax_included = sum(l.price_total for l in order.order_line if l.reward_id)
|
||||
msg = "Reward amount above the max amount, the reward should be the max amount value."
|
||||
self.assertEqual(reward_amount_tax_included, -9, msg)
|
||||
|
||||
order.order_line = [
|
||||
Command.clear(),
|
||||
Command.create({'product_id': product_a.id}), # price_total = 120
|
||||
Command.create({'product_id': product_b.id, 'price_unit': -95}), # price_total = -114
|
||||
]
|
||||
self._auto_rewards(order, promotion)
|
||||
reward_amount_tax_included = sum(l.price_total for l in order.order_line if l.reward_id)
|
||||
msg = "Reward amount should never surpass the order's current total amount."
|
||||
self.assertEqual(reward_amount_tax_included, -6, msg)
|
||||
|
||||
order.order_line = [
|
||||
Command.clear(),
|
||||
Command.create({'product_id': product_a.id, 'price_unit': 50}), # price_total = 60
|
||||
Command.create({'product_id': product_b.id, 'price_unit': -5}), # price_total = -6
|
||||
]
|
||||
self._auto_rewards(order, promotion)
|
||||
reward_amount_tax_included = sum(l.price_total for l in order.order_line if l.reward_id)
|
||||
msg = "Reward amount should be the percentage one if under the max amount discount."
|
||||
self.assertEqual(reward_amount_tax_included, -6, msg)
|
||||
|
||||
def test_points_awarded_global_discount_code_no_domain_program(self):
|
||||
"""
|
||||
Check the calculation for points awarded when there is a global discount applied and the
|
||||
loyalty program applies on all products (no domain).
|
||||
"""
|
||||
LoyaltyProgram = self.env['loyalty.program']
|
||||
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,
|
||||
'points': 0,
|
||||
})
|
||||
|
||||
order = self.env['sale.order'].with_user(self.user_salemanager).create({
|
||||
'partner_id': self.partner_a.id,
|
||||
'order_line': [
|
||||
Command.create({
|
||||
'product_id': self.product_A.id,
|
||||
'tax_id': False,
|
||||
}),
|
||||
]
|
||||
})
|
||||
|
||||
promotion_program = self.env['loyalty.program'].create([{
|
||||
'name': "Coupon Program",
|
||||
'program_type': 'promotion',
|
||||
'trigger': 'auto',
|
||||
'applies_on': 'current',
|
||||
'rule_ids': [Command.create({
|
||||
'reward_point_amount': 1,
|
||||
'reward_point_mode': 'order',
|
||||
'minimum_amount': 10,
|
||||
})],
|
||||
'reward_ids': [Command.create({
|
||||
'reward_type': 'discount',
|
||||
'discount': 10.0,
|
||||
'discount_applicability': 'order',
|
||||
'required_points': 1,
|
||||
})],
|
||||
}])
|
||||
|
||||
self.assertEqual(order.amount_total, 100)
|
||||
self._auto_rewards(order, promotion_program)
|
||||
self.assertEqual(order.amount_total, 90)
|
||||
order.action_confirm()
|
||||
self.assertEqual(loyalty_card.points, 90)
|
||||
|
||||
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
|
||||
loyalty program applies on all products (no domain).
|
||||
"""
|
||||
LoyaltyProgram = self.env['loyalty.program']
|
||||
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,
|
||||
'points': 0,
|
||||
})
|
||||
|
||||
order = self.env['sale.order'].with_user(self.user_salemanager).create({
|
||||
'partner_id': self.partner_a.id,
|
||||
'order_line': [
|
||||
Command.create({
|
||||
'product_id': self.product_A.id,
|
||||
'tax_id': False,
|
||||
}),
|
||||
]
|
||||
})
|
||||
|
||||
self.assertEqual(order.amount_total, 100)
|
||||
self._apply_promo_code(order, "test_10pc")
|
||||
self.assertEqual(order.amount_total, 90)
|
||||
order.action_confirm()
|
||||
self.assertEqual(loyalty_card.points, 90)
|
||||
|
||||
def test_points_awarded_general_discount_code_specific_domain_program(self):
|
||||
"""
|
||||
Check the calculation for points awarded when there is a discount coupon applied and the
|
||||
loyalty program applies on a specific domain. The discount code has no domain. The product
|
||||
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
|
||||
self.product_B.list_price = 50
|
||||
|
||||
LoyaltyProgram = self.env['loyalty.program']
|
||||
loyalty_program = LoyaltyProgram.create(LoyaltyProgram._get_template_values()['loyalty'])
|
||||
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,
|
||||
'points': 0,
|
||||
})
|
||||
|
||||
order = self.env['sale.order'].with_user(self.user_salemanager).create({
|
||||
'partner_id': self.partner_a.id,
|
||||
'order_line': [
|
||||
Command.create({
|
||||
'product_id': self.product_A.id,
|
||||
'tax_id': False,
|
||||
}),
|
||||
Command.create({
|
||||
'product_id': self.product_B.id,
|
||||
'tax_id': False,
|
||||
}),
|
||||
]
|
||||
})
|
||||
|
||||
self.assertEqual(order.amount_total, 150)
|
||||
self._apply_promo_code(order, "test_10pc")
|
||||
self.assertEqual(order.amount_total, 135) # (product_A + product_B) * 0.9
|
||||
order.action_confirm()
|
||||
self.assertEqual(loyalty_card.points, 100)
|
||||
|
||||
def test_points_awarded_specific_discount_code_specific_domain_program(self):
|
||||
"""
|
||||
Check the calculation for points awarded when there is a discount coupon applied and the
|
||||
loyalty program applies on a specific domain. The discount code has the same domain as the
|
||||
loyalty program. The product related to that discount code is set up to be included in the
|
||||
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
|
||||
self.product_B.list_price = 50
|
||||
|
||||
LoyaltyProgram = self.env['loyalty.program']
|
||||
loyalty_program = LoyaltyProgram.create(LoyaltyProgram._get_template_values()['loyalty'])
|
||||
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,
|
||||
'points': 0,
|
||||
})
|
||||
|
||||
self.promotion_code_10pc.rule_ids.product_category_id = product_category_food.id
|
||||
self.promotion_code_10pc.reward_ids.discount_applicability = 'specific'
|
||||
self.promotion_code_10pc.reward_ids.discount_product_category_id = product_category_food.id
|
||||
|
||||
discount_product = self.env['product.product'].search([('id', '=', self.promotion_code_10pc.reward_ids.discount_line_product_id.id)])
|
||||
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,
|
||||
'order_line': [
|
||||
Command.create({
|
||||
'product_id': self.product_A.id,
|
||||
'tax_id': False,
|
||||
}),
|
||||
Command.create({
|
||||
'product_id': self.product_B.id,
|
||||
'tax_id': False,
|
||||
}),
|
||||
]
|
||||
})
|
||||
|
||||
self.assertEqual(order.amount_total, 150)
|
||||
self._apply_promo_code(order, "test_10pc")
|
||||
self.assertEqual(order.amount_total, 140) # (product_A * 0.9 ) + product_B
|
||||
order.action_confirm()
|
||||
self.assertEqual(loyalty_card.points, 90)
|
||||
|
||||
def test_points_awarded_ewallet(self):
|
||||
"""
|
||||
Check the calculation for point awarded when using ewallet
|
||||
"""
|
||||
LoyaltyProgram = self.env['loyalty.program']
|
||||
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,
|
||||
'points': 0,
|
||||
})
|
||||
order = self.env['sale.order'].with_user(self.user_salemanager).create({
|
||||
'partner_id': self.partner_a.id,
|
||||
'order_line': [
|
||||
Command.create({
|
||||
'product_id': self.product_A.id,
|
||||
'tax_id': False,
|
||||
}),
|
||||
]
|
||||
})
|
||||
|
||||
self.assertEqual(order.amount_total, 100)
|
||||
order._update_programs_and_rewards()
|
||||
self._claim_reward(order, self.ewallet_program, coupon=self.ewallet)
|
||||
self.assertEqual(order.amount_total, 90)
|
||||
order.action_confirm()
|
||||
self.assertEqual(loyalty_card.points, 100)
|
||||
|
||||
def test_points_awarded_giftcard(self):
|
||||
"""
|
||||
Check the calculation for point awarded when using a gift card
|
||||
"""
|
||||
LoyaltyProgram = self.env['loyalty.program']
|
||||
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,
|
||||
'points': 0,
|
||||
})
|
||||
|
||||
program_gift_card = self.env['loyalty.program'].create({
|
||||
'name': "Gift Cards",
|
||||
'applies_on': 'future',
|
||||
'program_type': 'gift_card',
|
||||
'trigger': 'auto',
|
||||
'reward_ids': [(0, 0, {
|
||||
'reward_type': 'discount',
|
||||
'discount': 1,
|
||||
'discount_mode': 'per_point',
|
||||
'discount_applicability': 'order',
|
||||
})]
|
||||
})
|
||||
|
||||
self.env['loyalty.generate.wizard'].with_context(active_id=program_gift_card.id).create({
|
||||
'coupon_qty': 1,
|
||||
'points_granted': 50,
|
||||
}).generate_coupons()
|
||||
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,
|
||||
'order_line': [
|
||||
Command.create({
|
||||
'product_id': self.product_A.id,
|
||||
'tax_id': False,
|
||||
}),
|
||||
]
|
||||
})
|
||||
|
||||
self.assertEqual(order.amount_total, 100)
|
||||
self._apply_promo_code(order, gift_card.code)
|
||||
self.assertEqual(order.amount_total, 50)
|
||||
order.action_confirm()
|
||||
self.assertEqual(loyalty_card.points, 100)
|
||||
|
||||
def test_multiple_discount_specific(self):
|
||||
"""
|
||||
Check the discount calculation if it is based on the remaining amount
|
||||
"""
|
||||
|
||||
product_A = self.env['product.product'].create({
|
||||
'name': 'Product A',
|
||||
'list_price': 100,
|
||||
'sale_ok': True,
|
||||
'taxes_id': [],
|
||||
})
|
||||
|
||||
coupon_program = self.env['loyalty.program'].create([{
|
||||
'name': 'Coupon Program',
|
||||
'program_type': 'promotion',
|
||||
'trigger': 'auto',
|
||||
'applies_on': 'current',
|
||||
'rule_ids': [Command.create({
|
||||
'reward_point_amount': 1,
|
||||
'reward_point_mode': 'unit',
|
||||
})],
|
||||
'reward_ids': [Command.create({
|
||||
'reward_type': 'discount',
|
||||
'discount': 10.0,
|
||||
'discount_applicability': 'specific',
|
||||
'required_points': 1,
|
||||
})],
|
||||
}])
|
||||
|
||||
order = self.env['sale.order'].with_user(self.user_salemanager).create({
|
||||
'partner_id': self.partner_a.id,
|
||||
'order_line': [Command.create({
|
||||
'product_id': product_A.id,
|
||||
'product_uom_qty': 3,
|
||||
})]
|
||||
})
|
||||
|
||||
self.assertEqual(float_compare(order.amount_total, 300, precision_rounding=3), 0)
|
||||
|
||||
order._update_programs_and_rewards()
|
||||
self._claim_reward(order, coupon_program)
|
||||
self.assertEqual(float_compare(order.amount_total, 270, precision_rounding=3), 0, "300 * 0.9 = 270")
|
||||
|
||||
order._update_programs_and_rewards()
|
||||
self._claim_reward(order, coupon_program)
|
||||
self.assertEqual(float_compare(order.amount_total, 243, precision_rounding=3), 0, "300 * 0.9 * 0.9 = 243")
|
||||
|
||||
order._update_programs_and_rewards()
|
||||
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_specific_promotion_on_free_product(self):
|
||||
|
||||
product_A = self.env['product.product'].create({
|
||||
'name': 'Product A',
|
||||
'list_price': 100,
|
||||
'sale_ok': True,
|
||||
'taxes_id': [],
|
||||
})
|
||||
|
||||
promotion_program = self.env['loyalty.program'].create([{
|
||||
'name': 'Promotion Program',
|
||||
'program_type': 'promotion',
|
||||
'trigger': 'auto',
|
||||
'applies_on': 'current',
|
||||
'rule_ids': [Command.create({
|
||||
'reward_point_amount': 1,
|
||||
'reward_point_mode': 'unit',
|
||||
})],
|
||||
'reward_ids': [Command.create({
|
||||
'reward_type': 'discount',
|
||||
'discount': 10.0,
|
||||
'discount_applicability': 'specific',
|
||||
'discount_product_ids': [product_A.id],
|
||||
'required_points': 1,
|
||||
})],
|
||||
}])
|
||||
|
||||
order = self.env['sale.order'].with_user(self.user_salemanager).create({
|
||||
'partner_id': self.partner_a.id,
|
||||
'order_line': [
|
||||
Command.create({
|
||||
'product_id': product_A.id,
|
||||
}),
|
||||
Command.create({
|
||||
'product_id': product_A.id,
|
||||
'discount': 100,
|
||||
}),
|
||||
]
|
||||
})
|
||||
|
||||
order._update_programs_and_rewards()
|
||||
self._claim_reward(order, promotion_program)
|
||||
self.assertEqual(order.amount_total, 90)
|
||||
|
||||
def test_gift_card_program_without_product(self):
|
||||
product_A = self.env['product.product'].create({
|
||||
'name': 'Product A',
|
||||
'list_price': 100,
|
||||
'sale_ok': True,
|
||||
'taxes_id': [],
|
||||
})
|
||||
|
||||
giftcard_program = self.env['loyalty.program'].create([{
|
||||
'name': 'Gift Card Program',
|
||||
'program_type': 'gift_card',
|
||||
'trigger': 'auto',
|
||||
'applies_on': 'current',
|
||||
'rule_ids': [Command.create({
|
||||
'reward_point_amount': 1,
|
||||
'reward_point_mode': 'unit',
|
||||
})],
|
||||
}])
|
||||
|
||||
order = self.env['sale.order'].with_user(self.user_salemanager).create({
|
||||
'partner_id': self.partner_a.id,
|
||||
'order_line': [
|
||||
Command.create({
|
||||
'product_id': product_A.id,
|
||||
}),
|
||||
]
|
||||
})
|
||||
|
||||
order._update_programs_and_rewards()
|
||||
self._claim_reward(order, giftcard_program)
|
||||
|
||||
self.assertEqual(giftcard_program.coupon_count, 0)
|
||||
|
||||
def test_100_percent_discount(self):
|
||||
"""
|
||||
Check whether a program offering 100% discount on an order reduces the order's total amount
|
||||
to zero.
|
||||
|
||||
Assumes global tax rounding, as there's no good way to ensure the tax of the reward product
|
||||
equals the sum of taxes of the lines when each of them gets rounded.
|
||||
"""
|
||||
self.env.company.tax_calculation_rounding_method = 'round_globally'
|
||||
loyalty_program = self.env['loyalty.program'].create([{
|
||||
'name': 'Full Discount',
|
||||
'program_type': 'loyalty',
|
||||
'trigger': 'auto',
|
||||
'applies_on': 'both',
|
||||
'rule_ids': [(0, 0, {
|
||||
'reward_point_mode': 'unit',
|
||||
'reward_point_amount': 1,
|
||||
'product_ids': [self.product_a.id],
|
||||
})],
|
||||
'reward_ids': [(0, 0, {
|
||||
'reward_type': 'discount',
|
||||
'discount': 100,
|
||||
'discount_mode': 'percent',
|
||||
'discount_applicability': 'order',
|
||||
'required_points': 1,
|
||||
})],
|
||||
}])
|
||||
self.env['loyalty.card'].create({
|
||||
'program_id': loyalty_program.id, 'partner_id': self.partner_a.id, 'points': 2
|
||||
})
|
||||
order = self.env['sale.order'].create({
|
||||
'partner_id': self.partner_a.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)],
|
||||
})
|
||||
|
||||
order._update_programs_and_rewards()
|
||||
self._claim_reward(order, loyalty_program)
|
||||
msg = "100% discount on order should reduce total amount to 0"
|
||||
self.assertEqual(order.amount_total, 0, msg=msg)
|
||||
|
||||
def test_discount_on_taxes_with_child_tax(self):
|
||||
"""
|
||||
Check whether a program discount properly apply when product contain group of tax.
|
||||
"""
|
||||
self.env.company.tax_calculation_rounding_method = 'round_globally'
|
||||
loyalty_program = self.env['loyalty.program'].create([{
|
||||
'name': '90% Discount',
|
||||
'program_type': 'loyalty',
|
||||
'trigger': 'auto',
|
||||
'applies_on': 'both',
|
||||
'rule_ids': [(0, 0, {
|
||||
'reward_point_mode': 'unit',
|
||||
'reward_point_amount': 1,
|
||||
'product_ids': [self.product_a.id],
|
||||
})],
|
||||
'reward_ids': [(0, 0, {
|
||||
'reward_type': 'discount',
|
||||
'discount': 90,
|
||||
'discount_mode': 'percent',
|
||||
'discount_applicability': 'order',
|
||||
'required_points': 1,
|
||||
})],
|
||||
}])
|
||||
self.env['loyalty.card'].create({'program_id': loyalty_program.id, 'partner_id': self.partner_a.id, 'points': 2})
|
||||
order = self.env['sale.order'].create({
|
||||
'partner_id': self.partner_a.id,
|
||||
'order_line': [(0, 0, {'product_id': self.product_D.id, 'product_uom_qty': 1})],
|
||||
})
|
||||
|
||||
order._update_programs_and_rewards()
|
||||
self._claim_reward(order, loyalty_program)
|
||||
msg = "Discountable should take child tax amount into account"
|
||||
self.assertEqual(order.amount_total, 10, msg=msg)
|
||||
|
||||
def test_ewallet_program_without_trigger_product(self):
|
||||
self.ewallet_program.trigger_product_ids = [Command.clear()]
|
||||
self.ewallet.points = 1000
|
||||
|
||||
order = self.env['sale.order'].create({
|
||||
'partner_id': self.partner_a.id,
|
||||
'order_line': [Command.create({
|
||||
'product_id': self.product_a.id,
|
||||
'points_cost': 100,
|
||||
'product_uom_qty': 1,
|
||||
})],
|
||||
})
|
||||
order._update_programs_and_rewards()
|
||||
self._claim_reward(order, self.ewallet_program, coupon=self.ewallet)
|
||||
order.action_confirm()
|
||||
|
||||
self.assertEqual(self.ewallet.points, 900)
|
||||
|
||||
def test_ewallet_applied_ewallet_topup_in_order(self):
|
||||
self.ewallet.points = 10
|
||||
|
||||
order = self.env['sale.order'].create({
|
||||
'partner_id': self.partner_a.id,
|
||||
'order_line': [Command.create({
|
||||
'product_id': self.product_a.id,
|
||||
'points_cost': 100,
|
||||
'product_uom_qty': 1,
|
||||
}), Command.create({
|
||||
'product_id': self.env.ref('loyalty.ewallet_product_50').id,
|
||||
'product_uom_qty': 1,
|
||||
})],
|
||||
})
|
||||
order._update_programs_and_rewards()
|
||||
self._claim_reward(order, self.ewallet_program, coupon=self.ewallet)
|
||||
order.action_confirm()
|
||||
|
||||
self.assertEqual(self.ewallet.points, 50)
|
||||
|
||||
def test_archived_reward_products(self):
|
||||
"""
|
||||
Check that we do not use loyalty rewards that have no active reward product.
|
||||
In the case where the reward is based on reward_product_tag_id we also check
|
||||
the case where at least one reward is active.
|
||||
"""
|
||||
|
||||
LoyaltyProgram = self.env['loyalty.program']
|
||||
loyalty_program = LoyaltyProgram.create(LoyaltyProgram._get_template_values()['loyalty'])
|
||||
loyalty_program_tag = LoyaltyProgram.create(LoyaltyProgram._get_template_values()['loyalty'])
|
||||
|
||||
free_product_tag = self.env['product.tag'].create({'name': 'Free Product'})
|
||||
self.product_b.write({'product_tag_ids': [(4, free_product_tag.id)]})
|
||||
product_c = self.env['product.template'].create(
|
||||
{
|
||||
'name': 'Free Product C',
|
||||
'list_price': 1,
|
||||
'product_tag_ids': [(4, free_product_tag.id)],
|
||||
}
|
||||
)
|
||||
|
||||
loyalty_program.reward_ids[0].write({
|
||||
'reward_type': 'product',
|
||||
'required_points': 1,
|
||||
'reward_product_id': self.product_b,
|
||||
})
|
||||
loyalty_program_tag.reward_ids[0].write({
|
||||
'reward_type': 'product',
|
||||
'required_points': 1,
|
||||
'reward_product_tag_id': free_product_tag.id,
|
||||
})
|
||||
self.product_b.active = False
|
||||
product_c.active = False
|
||||
|
||||
order = self.env['sale.order'].create({
|
||||
'partner_id': self.partner_a.id,
|
||||
'order_line': [
|
||||
Command.create({
|
||||
'product_id': self.product_a.id,
|
||||
}),
|
||||
]
|
||||
})
|
||||
|
||||
order._update_programs_and_rewards()
|
||||
rewards = [value.ids for value in order._get_claimable_rewards().values()]
|
||||
self.assertTrue(all(loyalty_program.reward_ids[0].id not in r for r in rewards))
|
||||
self.assertTrue(all(loyalty_program_tag.reward_ids[0].id not in r for r in rewards))
|
||||
|
||||
product_c.active = True
|
||||
order._update_programs_and_rewards()
|
||||
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):
|
||||
"""
|
||||
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_a.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)
|
||||
|
|
@ -0,0 +1,262 @@
|
|||
# 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('-at_install', 'post_install')
|
||||
class TestPayWithGiftCard(TestSaleCouponCommon):
|
||||
|
||||
def test_paying_with_single_gift_card_over(self):
|
||||
self.env['loyalty.generate.wizard'].with_context(active_id=self.program_gift_card.id).create({
|
||||
'coupon_qty': 1,
|
||||
'points_granted': 100,
|
||||
}).generate_coupons()
|
||||
gift_card = self.program_gift_card.coupon_ids[0]
|
||||
order = self.empty_order
|
||||
order.write({'order_line': [
|
||||
Command.create({
|
||||
'product_id': self.product_A.id,
|
||||
'name': 'Ordinary Product A',
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 1.0,
|
||||
})
|
||||
]})
|
||||
before_gift_card_payment = order.amount_total
|
||||
self.assertNotEqual(before_gift_card_payment, 0)
|
||||
self._apply_promo_code(order, gift_card.code)
|
||||
order.action_confirm()
|
||||
self.assertEqual(before_gift_card_payment - order.amount_total, 100 - gift_card.points)
|
||||
|
||||
def test_paying_with_single_gift_card_under(self):
|
||||
self.env['loyalty.generate.wizard'].with_context(active_id=self.program_gift_card.id).create({
|
||||
'coupon_qty': 1,
|
||||
'points_granted': 100,
|
||||
}).generate_coupons()
|
||||
gift_card = self.program_gift_card.coupon_ids[0]
|
||||
order = self.empty_order
|
||||
order.write({'order_line': [
|
||||
Command.create({
|
||||
'product_id': self.product_B.id,
|
||||
'name': 'Ordinary Product b',
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 1.0,
|
||||
})
|
||||
]})
|
||||
before_gift_card_payment = order.amount_total
|
||||
self.assertNotEqual(before_gift_card_payment, 0)
|
||||
self._apply_promo_code(order, gift_card.code)
|
||||
order.action_confirm()
|
||||
self.assertEqual(before_gift_card_payment - order.amount_total, 100 - gift_card.points)
|
||||
|
||||
def test_paying_with_multiple_gift_card(self):
|
||||
self.env['loyalty.generate.wizard'].with_context(active_id=self.program_gift_card.id).create({
|
||||
'coupon_qty': 2,
|
||||
'points_granted': 100,
|
||||
}).generate_coupons()
|
||||
gift_card_1, gift_card_2 = self.program_gift_card.coupon_ids
|
||||
order = self.empty_order
|
||||
order.write({'order_line': [
|
||||
Command.create({
|
||||
'product_id': self.product_A.id,
|
||||
'name': 'Ordinary Product A',
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 20.0,
|
||||
})
|
||||
]})
|
||||
before_gift_card_payment = order.amount_total
|
||||
self._apply_promo_code(order, gift_card_1.code)
|
||||
self._apply_promo_code(order, gift_card_2.code)
|
||||
self.assertEqual(order.amount_total, before_gift_card_payment - 200)
|
||||
|
||||
def test_paying_with_gift_card_and_discount(self):
|
||||
# Test that discounts take precedence on payment rewards
|
||||
self.env['loyalty.generate.wizard'].with_context(active_id=self.program_gift_card.id).create({
|
||||
'coupon_qty': 1,
|
||||
'points_granted': 50,
|
||||
}).generate_coupons()
|
||||
gift_card_1 = self.program_gift_card.coupon_ids
|
||||
order = self.empty_order
|
||||
order.write({'order_line': [
|
||||
Command.create({
|
||||
'product_id': self.product_C.id,
|
||||
'name': 'Ordinary Product C',
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 1.0,
|
||||
})
|
||||
]})
|
||||
self.env['loyalty.program'].create({
|
||||
'name': 'Code for 10% on orders',
|
||||
'trigger': 'with_code',
|
||||
'program_type': 'promotion',
|
||||
'applies_on': 'current',
|
||||
'rule_ids': [(0, 0, {
|
||||
'mode': 'with_code',
|
||||
'code': 'test_10pc',
|
||||
})],
|
||||
'reward_ids': [(0, 0, {
|
||||
'reward_type': 'discount',
|
||||
'discount_mode': 'percent',
|
||||
'discount': 10,
|
||||
'discount_applicability': 'order',
|
||||
'required_points': 1,
|
||||
})],
|
||||
})
|
||||
self.assertEqual(order.amount_total, 100)
|
||||
self._apply_promo_code(order, gift_card_1.code)
|
||||
self.assertEqual(order.amount_total, 50)
|
||||
self._apply_promo_code(order, "test_10pc")
|
||||
# real flows also have to update the programs and rewards
|
||||
order._update_programs_and_rewards()
|
||||
self.assertEqual(order.amount_total, 40) # 100 - 10% - 50
|
||||
|
||||
def test_paying_with_gift_card_blocking_discount(self):
|
||||
# Test that a payment program making the order total 0 still allows the user to claim discounts
|
||||
self.env['loyalty.generate.wizard'].with_context(active_id=self.program_gift_card.id).create({
|
||||
'coupon_qty': 1,
|
||||
'points_granted': 100,
|
||||
}).generate_coupons()
|
||||
gift_card_1 = self.program_gift_card.coupon_ids
|
||||
order = self.empty_order
|
||||
order.write({'order_line': [
|
||||
Command.create({
|
||||
'product_id': self.product_C.id,
|
||||
'name': 'Ordinary Product C',
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 1.0,
|
||||
})
|
||||
]})
|
||||
self.env['loyalty.program'].create({
|
||||
'name': 'Code for 10% on orders',
|
||||
'trigger': 'with_code',
|
||||
'program_type': 'promotion',
|
||||
'applies_on': 'current',
|
||||
'rule_ids': [(0, 0, {
|
||||
'mode': 'with_code',
|
||||
'code': 'test_10pc',
|
||||
})],
|
||||
'reward_ids': [(0, 0, {
|
||||
'reward_type': 'discount',
|
||||
'discount_mode': 'percent',
|
||||
'discount': 10,
|
||||
'discount_applicability': 'order',
|
||||
'required_points': 1,
|
||||
})],
|
||||
})
|
||||
self.assertEqual(order.amount_total, 100)
|
||||
self._apply_promo_code(order, gift_card_1.code)
|
||||
self.assertEqual(order.amount_total, 0)
|
||||
self._apply_promo_code(order, "test_10pc")
|
||||
# real flows also have to update the programs and rewards
|
||||
order._update_programs_and_rewards()
|
||||
self.assertEqual(order.amount_total, 0) # 100 - 10% - 90
|
||||
|
||||
def test_gift_card_product_has_no_taxes_on_creation(self):
|
||||
gift_card_program = self.env['loyalty.program'].create({
|
||||
'name': 'Gift Cards',
|
||||
'applies_on': 'future',
|
||||
'program_type': 'gift_card',
|
||||
'trigger': 'auto',
|
||||
'rule_ids': [Command.create({
|
||||
'product_ids': self.product_gift_card,
|
||||
'reward_point_amount': 1,
|
||||
'reward_point_mode': 'money',
|
||||
'reward_point_split': True,
|
||||
})],
|
||||
'reward_ids': [Command.create({
|
||||
'reward_type': 'discount',
|
||||
'discount': 1,
|
||||
'discount_mode': 'per_point',
|
||||
'discount_applicability': 'order',
|
||||
})]
|
||||
})
|
||||
self.assertFalse(gift_card_program.reward_ids.discount_line_product_id.taxes_id)
|
||||
|
||||
def test_paying_with_gift_card_uses_gift_card_product_taxes(self):
|
||||
order = self.empty_order
|
||||
order.order_line = [
|
||||
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,
|
||||
})
|
||||
]
|
||||
sol = order.order_line
|
||||
before_gift_card_payment = order.amount_total
|
||||
self.assertNotEqual(before_gift_card_payment, 0)
|
||||
|
||||
self.env['loyalty.generate.wizard'].with_context(active_id=self.program_gift_card.id).create({
|
||||
'coupon_qty': 1,
|
||||
'points_granted': 100,
|
||||
}).generate_coupons()
|
||||
gift_card = self.program_gift_card.coupon_ids[0]
|
||||
|
||||
# TODO check amount total of gift_card_line
|
||||
|
||||
# TAX EXCL
|
||||
self.program_gift_card.reward_ids.discount_line_product_id.taxes_id = [
|
||||
Command.link(self.tax_15pc_excl.id)
|
||||
]
|
||||
self._apply_promo_code(order, gift_card.code)
|
||||
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)
|
||||
|
||||
# TAX INCL
|
||||
gift_card_line.unlink() # Remove gift card
|
||||
self.program_gift_card.reward_ids.discount_line_product_id.taxes_id = [
|
||||
Command.set(self.tax_10pc_incl.ids)
|
||||
]
|
||||
self._apply_promo_code(order, gift_card.code)
|
||||
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)
|
||||
|
||||
# TAX INCL + TAX EXCL
|
||||
gift_card_line.unlink() # Remove gift card
|
||||
self.program_gift_card.reward_ids.discount_line_product_id.taxes_id = [
|
||||
Command.link(self.tax_15pc_excl.id)
|
||||
]
|
||||
self._apply_promo_code(order, gift_card.code)
|
||||
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)
|
||||
|
||||
def test_paying_with_gift_card_fixed_tax(self):
|
||||
""" Test payment of sale order with fixed tax using gift card """
|
||||
self.env['loyalty.generate.wizard'].with_context(active_id=self.program_gift_card.id).create({
|
||||
'coupon_qty': 1,
|
||||
'points_granted': 100,
|
||||
}).generate_coupons()
|
||||
gift_card = self.program_gift_card.coupon_ids[0]
|
||||
|
||||
tax_10_fixed = self.env['account.tax'].create({
|
||||
'name': "10$ Fixed tax",
|
||||
'amount_type': 'fixed',
|
||||
'amount': 10,
|
||||
})
|
||||
self.product_A.write({'list_price': 90})
|
||||
self.product_A.taxes_id = tax_10_fixed
|
||||
|
||||
order = self.empty_order
|
||||
order.write({'order_line': [
|
||||
Command.create({
|
||||
'product_id': self.product_A.id,
|
||||
'name': "Ordinary Product A",
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 1.0,
|
||||
})
|
||||
]})
|
||||
self._apply_promo_code(order, gift_card.code)
|
||||
order.action_confirm()
|
||||
self.assertEqual(order.amount_total, 0, "The order should be totally paid")
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
# -*- 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 import Command
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestSaleCouponMultiCompany(TestSaleCouponCommon):
|
||||
|
||||
def setUp(self):
|
||||
super(TestSaleCouponMultiCompany, self).setUp()
|
||||
|
||||
self.company_a = self.env.company
|
||||
self.company_b = self.env['res.company'].create(dict(name="TEST"))
|
||||
|
||||
self.immediate_promotion_program_c2 = self.env['loyalty.program'].create({
|
||||
'name': 'Buy A + 1 B, 1 B are free',
|
||||
'trigger': 'auto',
|
||||
'program_type': 'promotion',
|
||||
'applies_on': 'current',
|
||||
'company_id': self.company_b.id,
|
||||
'rule_ids': [(0, 0, {
|
||||
'product_ids': self.product_A,
|
||||
'reward_point_amount': 1,
|
||||
'reward_point_mode': 'order',
|
||||
})],
|
||||
'reward_ids': [(0, 0, {
|
||||
'reward_type': 'product',
|
||||
'reward_product_id': self.product_B.id,
|
||||
'reward_product_qty': 1,
|
||||
'required_points': 1,
|
||||
})],
|
||||
})
|
||||
|
||||
def _get_applicable_programs(self, order):
|
||||
return self.env['loyalty.program'].browse(p.id for p in order._get_applicable_program_points())
|
||||
|
||||
def test_applicable_programs(self):
|
||||
|
||||
order = self.empty_order
|
||||
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': 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,
|
||||
})
|
||||
]})
|
||||
order._update_programs_and_rewards()
|
||||
|
||||
self.assertNotIn(self.immediate_promotion_program_c2, self._get_applicable_programs(order))
|
||||
self.assertNotIn(self.immediate_promotion_program_c2, order._get_applied_programs())
|
||||
|
||||
order_b = self.env["sale.order"].create({
|
||||
'company_id': self.company_b.id,
|
||||
'partner_id': order.partner_id.id,
|
||||
})
|
||||
order_b.write({'order_line': [
|
||||
(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,
|
||||
})
|
||||
]})
|
||||
self.assertNotIn(self.immediate_promotion_program, self._get_applicable_programs(order_b))
|
||||
order_b._update_programs_and_rewards()
|
||||
self.assertIn(self.immediate_promotion_program_c2, order_b._get_applied_programs())
|
||||
self.assertNotIn(self.immediate_promotion_program, order_b._get_applied_programs())
|
||||
|
||||
def test_applicable_programs_on_branch(self):
|
||||
# create a branch
|
||||
branch_a = self.env['res.company'].create(
|
||||
{'name': 'Branch A', 'parent_id': self.company_a.id}
|
||||
)
|
||||
|
||||
# create an order
|
||||
order = self.env['sale.order'].create(
|
||||
{'order_line': [
|
||||
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
|
||||
}
|
||||
)
|
||||
|
||||
order._update_programs_and_rewards()
|
||||
self.assertIn(self.immediate_promotion_program, order._get_applied_programs())
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,361 @@
|
|||
# -*- 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
|
||||
|
||||
class TestProgramRules(TestSaleCouponCommon):
|
||||
# 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
|
||||
|
||||
def test_program_rules_minimum_purchased_amount(self):
|
||||
# Test case: Based on the minimum purchased
|
||||
|
||||
self.immediate_promotion_program.rule_ids.write({
|
||||
'product_ids': False,
|
||||
'minimum_amount': 1006,
|
||||
'minimum_amount_tax_mode': 'excl'
|
||||
})
|
||||
|
||||
order = self.empty_order
|
||||
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': 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,
|
||||
})
|
||||
]})
|
||||
order._update_programs_and_rewards()
|
||||
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.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,
|
||||
})
|
||||
]})
|
||||
order._update_programs_and_rewards()
|
||||
self._claim_reward(order, self.immediate_promotion_program)
|
||||
# 10*100 + 5 = 1005
|
||||
self.assertEqual(len(order.order_line.ids), 2, "The promo offer should not be applied as the purchased amount is not enough")
|
||||
|
||||
self.immediate_promotion_program.rule_ids.minimum_amount = 1005
|
||||
order._update_programs_and_rewards()
|
||||
self._claim_reward(order, self.immediate_promotion_program)
|
||||
self.assertEqual(len(order.order_line.ids), 3, "The promo offer should be applied as the purchased amount is now enough")
|
||||
|
||||
# 10*(100*1.15) + (5*1.15) = 10*115 + 5.75 = 1155.75
|
||||
self.immediate_promotion_program.rule_ids.minimum_amount = 1006
|
||||
self.immediate_promotion_program.rule_ids.minimum_amount_tax_mode = 'incl'
|
||||
order._update_programs_and_rewards()
|
||||
self._claim_reward(order, self.immediate_promotion_program)
|
||||
self.assertEqual(len(order.order_line.ids), 3, "The promo offer should be applied as the initial amount required is now tax included")
|
||||
|
||||
def test_program_rules_min_amount_not_reached_and_specific_product(self):
|
||||
"""
|
||||
Test that the discount isn't applied if the min amount isn't reached for the specified
|
||||
product.
|
||||
"""
|
||||
self.env['loyalty.program'].search([]).active = False
|
||||
order = self.empty_order
|
||||
program = self.env['loyalty.program'].create({
|
||||
'name': "Discount on Product A",
|
||||
'program_type': 'promotion',
|
||||
'trigger': 'auto',
|
||||
'applies_on': 'current',
|
||||
'rule_ids': [Command.create({
|
||||
'minimum_amount': 110,
|
||||
'minimum_amount_tax_mode': 'excl',
|
||||
'product_ids': [Command.set(self.product_A.ids)],
|
||||
})],
|
||||
'reward_ids': [Command.create({
|
||||
'reward_type': 'discount',
|
||||
'discount': 10,
|
||||
'discount_mode': 'percent',
|
||||
'discount_applicability': 'specific',
|
||||
'discount_product_ids': [Command.set(self.product_A.ids)],
|
||||
})],
|
||||
})
|
||||
self.env['sale.order.line'].create([{
|
||||
'product_id': self.product_A.id,
|
||||
'product_uom_qty': 1.0,
|
||||
'order_id': order.id,
|
||||
}, {
|
||||
'product_id': self.product_B.id,
|
||||
'product_uom_qty': 40.0,
|
||||
'order_id': order.id,
|
||||
}])
|
||||
self.assertEqual(len(order.order_line), 2)
|
||||
self.assertEqual(order.amount_untaxed, 300)
|
||||
|
||||
order._update_programs_and_rewards()
|
||||
self._claim_reward(order, program)
|
||||
|
||||
self.assertEqual(len(order.order_line), 2)
|
||||
self.assertEqual(order.amount_untaxed, 300)
|
||||
|
||||
def test_program_rules_min_amount_reached_and_specific_product(self):
|
||||
"""
|
||||
Test that the discount is applied if the min amount is reached for the specified product.
|
||||
"""
|
||||
self.env['loyalty.program'].search([]).active = False
|
||||
order = self.empty_order
|
||||
program = self.env['loyalty.program'].create({
|
||||
'name': "Discount on Product A",
|
||||
'program_type': 'promotion',
|
||||
'trigger': 'auto',
|
||||
'applies_on': 'current',
|
||||
'rule_ids': [Command.create({
|
||||
'minimum_amount': 110,
|
||||
'minimum_amount_tax_mode': 'excl',
|
||||
'product_ids': [Command.set(self.product_A.ids)],
|
||||
})],
|
||||
'reward_ids': [Command.create({
|
||||
'reward_type': 'discount',
|
||||
'discount': 10,
|
||||
'discount_mode': 'percent',
|
||||
'discount_applicability': 'specific',
|
||||
'discount_product_ids': [Command.set(self.product_A.ids)],
|
||||
})],
|
||||
})
|
||||
self.env['sale.order.line'].create([{
|
||||
'product_id': self.product_A.id,
|
||||
'product_uom_qty': 2.0,
|
||||
'order_id': order.id,
|
||||
}, {
|
||||
'product_id': self.product_B.id,
|
||||
'product_uom_qty': 20.0,
|
||||
'order_id': order.id,
|
||||
}])
|
||||
self.assertEqual(len(order.order_line), 2)
|
||||
self.assertEqual(order.amount_untaxed, 300)
|
||||
|
||||
order._update_programs_and_rewards()
|
||||
self._claim_reward(order, program)
|
||||
|
||||
self.assertEqual(len(order.order_line), 3)
|
||||
self.assertEqual(order.amount_untaxed, 280)
|
||||
|
||||
def test_program_rules_coupon_qty_and_amount_remove_not_eligible(self):
|
||||
''' This test will:
|
||||
* Check quantity and amount requirements works as expected (since it's slightly different from a promotion_program)
|
||||
* Ensure that if a reward from a coupon_program was allowed and the conditions are not met anymore,
|
||||
the reward will be removed on recompute.
|
||||
'''
|
||||
self.immediate_promotion_program.active = False # Avoid having this program to add rewards on this test
|
||||
order = self.empty_order
|
||||
|
||||
program = self.env['loyalty.program'].create({
|
||||
'name': 'Get 10% discount if buy at least 4 Product A and $320',
|
||||
'program_type': 'coupons',
|
||||
'applies_on': 'current',
|
||||
'trigger': 'with_code',
|
||||
'rule_ids': [(0, 0, {
|
||||
'product_ids': self.product_A,
|
||||
'minimum_qty': 3,
|
||||
'minimum_amount': 320,
|
||||
})],
|
||||
'reward_ids': [(0, 0, {
|
||||
'reward_type': 'discount',
|
||||
'discount_mode': 'percent',
|
||||
'discount': 10,
|
||||
'discount_applicability': 'order',
|
||||
})],
|
||||
})
|
||||
|
||||
sol1 = self.env['sale.order.line'].create({
|
||||
'product_id': self.product_A.id,
|
||||
'name': 'Product A',
|
||||
'product_uom_qty': 2.0,
|
||||
'order_id': order.id,
|
||||
})
|
||||
|
||||
sol2 = self.env['sale.order.line'].create({
|
||||
'product_id': self.product_B.id,
|
||||
'name': 'Product B',
|
||||
'product_uom_qty': 4.0,
|
||||
'order_id': order.id,
|
||||
})
|
||||
|
||||
# Default value for coupon generate wizard is generate by quantity and generate only one coupon
|
||||
self.env['loyalty.generate.wizard'].with_context(active_id=program.id).create({'coupon_qty': 1, 'points_granted': 1}).generate_coupons()
|
||||
coupon = program.coupon_ids[0]
|
||||
|
||||
# Not enough amount since we only have 220 (100*2 + 5*4)
|
||||
with self.assertRaises(ValidationError):
|
||||
self._apply_promo_code(order, coupon.code)
|
||||
|
||||
sol2.product_uom_qty = 24
|
||||
|
||||
# Not enough qty since we only have 3 Product A (Amount is ok: 100*2 + 5*24 = 320)
|
||||
with self.assertRaises(ValidationError):
|
||||
self._apply_promo_code(order, coupon.code)
|
||||
|
||||
sol1.product_uom_qty = 3
|
||||
|
||||
self._apply_promo_code(order, coupon.code)
|
||||
self._claim_reward(order, program, coupon)
|
||||
|
||||
self.assertEqual(len(order.order_line.ids), 3, "The order should contain the Product A line, the Product B line and the discount line")
|
||||
|
||||
sol1.product_uom_qty = 2
|
||||
order._update_programs_and_rewards()
|
||||
|
||||
self.assertEqual(len(order.order_line.ids), 2, "The discount line should have been removed as we don't meet the program requirements")
|
||||
|
||||
def test_program_rules_promotion_use_best(self):
|
||||
''' This test verifies that only the best global discount is applied.
|
||||
'''
|
||||
self.immediate_promotion_program.active = False # Avoid having this program to add rewards on this test
|
||||
order = self.empty_order
|
||||
|
||||
p1 = self.env['loyalty.program'].create({
|
||||
'name': 'Get 5% discount if buy at least 2 Product',
|
||||
'trigger': 'auto',
|
||||
'program_type': 'promotion',
|
||||
'applies_on': 'current',
|
||||
'rule_ids': [(0, 0, {
|
||||
'reward_point_mode': 'order',
|
||||
'minimum_qty': 2,
|
||||
})],
|
||||
'reward_ids': [(0, 0, {
|
||||
'reward_type': 'discount',
|
||||
'discount': 5,
|
||||
'discount_mode': 'percent',
|
||||
'discount_applicability': 'order',
|
||||
'required_points': 1,
|
||||
})],
|
||||
})
|
||||
p2 = self.env['loyalty.program'].create({
|
||||
'name': 'Get 10% discount if buy at least 4 Product',
|
||||
'trigger': 'auto',
|
||||
'program_type': 'promotion',
|
||||
'applies_on': 'current',
|
||||
'rule_ids': [(0, 0, {
|
||||
'reward_point_mode': 'order',
|
||||
'minimum_qty': 4,
|
||||
})],
|
||||
'reward_ids': [(0, 0, {
|
||||
'reward_type': 'discount',
|
||||
'discount': 10,
|
||||
'discount_mode': 'percent',
|
||||
'discount_applicability': 'order',
|
||||
'required_points': 1,
|
||||
})],
|
||||
})
|
||||
sol = self.env['sale.order.line'].create({
|
||||
'product_id': self.product_A.id,
|
||||
'name': 'Product A',
|
||||
'product_uom_qty': 1.0,
|
||||
'order_id': order.id,
|
||||
})
|
||||
|
||||
order._update_programs_and_rewards()
|
||||
self._claim_reward(order, p1)
|
||||
self._claim_reward(order, p2)
|
||||
self.assertEqual(len(order.order_line.ids), 1, "The order should only contains the Product A line")
|
||||
|
||||
sol.product_uom_qty = 3
|
||||
order._update_programs_and_rewards()
|
||||
self._claim_reward(order, p1)
|
||||
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")
|
||||
# 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")
|
||||
|
||||
sol.product_uom_qty = 5
|
||||
order._update_programs_and_rewards()
|
||||
self._claim_reward(order, p1)
|
||||
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")
|
||||
|
||||
def test_program_rules_validity_dates_and_uses(self):
|
||||
# Test case: Based on the validity dates and the number of allowed uses
|
||||
|
||||
self.immediate_promotion_program.write({
|
||||
'date_to': date.today() - timedelta(days=2),
|
||||
'limit_usage': True,
|
||||
'max_usage': 1,
|
||||
})
|
||||
|
||||
order = self.empty_order
|
||||
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': 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.immediate_promotion_program.write({
|
||||
'date_to': date.today() + timedelta(days=2),
|
||||
})
|
||||
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, {
|
||||
'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")
|
||||
|
|
@ -0,0 +1,333 @@
|
|||
# -*- 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
|
||||
|
||||
|
||||
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.
|
||||
|
||||
def test_program_usability(self):
|
||||
# After clicking "Generate coupons", there is no domain so it shows "Match all records".
|
||||
# But when you click, domain is false (default field value; empty string) so it won't generate anything.
|
||||
# This is even more weird because if you add something in the domain and then delete it,
|
||||
# you visually come back to the initial state except the domain became '[]' instead of ''.
|
||||
# In this case, it will generate the coupon for every partner.
|
||||
# Thus, we should ensure that if you leave the domain untouched, it generates a coupon for each partner
|
||||
# as hinted on the screen ('Match all records (X records)')
|
||||
self.env['loyalty.generate.wizard'].with_context(active_id=self.code_promotion_program.id).create({
|
||||
'mode': 'selected',
|
||||
}).generate_coupons()
|
||||
self.assertEqual(len(self.code_promotion_program.coupon_ids), len(self.env['res.partner'].search([])), "It should have generated a coupon for every partner")
|
||||
|
||||
def test_program_basic_operation_coupon_code(self):
|
||||
# Test case: Generate a coupon for my customer, and add a reward then remove it automatically
|
||||
|
||||
self.immediate_promotion_program.active = False
|
||||
self.code_promotion_program.reward_ids.reward_type = 'discount'
|
||||
self.code_promotion_program.reward_ids.discount = 10
|
||||
|
||||
self.env['loyalty.generate.wizard'].with_context(active_id=self.code_promotion_program.id).create({
|
||||
'mode': 'selected',
|
||||
'customer_ids': self.steve,
|
||||
'points_granted': 1,
|
||||
}).generate_coupons()
|
||||
coupon = self.code_promotion_program.coupon_ids
|
||||
|
||||
# Test the valid code on a wrong sales order
|
||||
wrong_partner_order = self.env['sale.order'].create({
|
||||
'partner_id': self.env['res.partner'].create({'name': 'My Partner'}).id,
|
||||
})
|
||||
with self.assertRaises(ValidationError):
|
||||
self._apply_promo_code(wrong_partner_order, coupon.code)
|
||||
|
||||
# Test now on a valid sales order
|
||||
order = self.empty_order
|
||||
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': 1.0,
|
||||
})
|
||||
]})
|
||||
self._apply_promo_code(order, coupon.code)
|
||||
self.assertEqual(len(order.order_line.ids), 2)
|
||||
|
||||
# Remove the product A from the sale order
|
||||
order.write({'order_line': [(2, order.order_line[0].id, False)]})
|
||||
order._update_programs_and_rewards()
|
||||
self.assertEqual(len(order.order_line.ids), 0)
|
||||
|
||||
def test_program_coupon_double_consuming(self):
|
||||
# Test case:
|
||||
# - Generate a coupon
|
||||
# - add to a sale order A, cancel the sale order
|
||||
# - add to a sale order B, confirm the order
|
||||
# - go back to A, reset to draft and confirm
|
||||
|
||||
self.immediate_promotion_program.active = False
|
||||
self.code_promotion_program.applies_on = 'future'
|
||||
self.code_promotion_program.reward_ids.reward_type = 'discount'
|
||||
self.code_promotion_program.reward_ids.discount = 10
|
||||
|
||||
self.env['loyalty.generate.wizard'].with_context(active_id=self.code_promotion_program.id).create({
|
||||
'coupon_qty': 1,
|
||||
'points_granted': 1,
|
||||
}).generate_coupons()
|
||||
coupon = self.code_promotion_program.coupon_ids
|
||||
|
||||
sale_order_a = self.empty_order.copy()
|
||||
sale_order_b = self.empty_order.copy()
|
||||
|
||||
sale_order_a.write({'order_line': [
|
||||
(0, False, {
|
||||
'product_id': self.product_A.id,
|
||||
'name': '1 Product A',
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 1.0,
|
||||
})
|
||||
]})
|
||||
self._apply_promo_code(sale_order_a, coupon.code)
|
||||
self.assertEqual(len(sale_order_a.order_line.ids), 2)
|
||||
|
||||
sale_order_a._action_cancel()
|
||||
|
||||
sale_order_b.write({'order_line': [
|
||||
(0, False, {
|
||||
'product_id': self.product_A.id,
|
||||
'name': '1 Product A',
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 1.0,
|
||||
})
|
||||
]})
|
||||
self._apply_promo_code(sale_order_b, coupon.code)
|
||||
self.assertEqual(len(sale_order_b.order_line.ids), 2)
|
||||
|
||||
sale_order_b.action_confirm()
|
||||
|
||||
sale_order_a.action_draft()
|
||||
sale_order_a.action_confirm()
|
||||
# reward line removed automatically
|
||||
self.assertEqual(len(sale_order_a.order_line.ids), 1)
|
||||
|
||||
def test_coupon_code_with_pricelist(self):
|
||||
# Test case: Generate a coupon (10% discount) and apply it on an order with a specific pricelist (10% discount)
|
||||
|
||||
self.code_promotion_program_with_discount.applies_on = 'future'
|
||||
self.env['loyalty.generate.wizard'].with_context(active_id=self.code_promotion_program_with_discount.id).create({
|
||||
'coupon_qty': 1,
|
||||
'points_granted': 1,
|
||||
}).generate_coupons()
|
||||
coupon = self.code_promotion_program_with_discount.coupon_ids
|
||||
|
||||
first_pricelist = self.env['product.pricelist'].create({
|
||||
'name': 'First pricelist',
|
||||
'discount_policy': 'with_discount',
|
||||
'item_ids': [(0, 0, {
|
||||
'compute_price': 'percentage',
|
||||
'base': 'list_price',
|
||||
'percent_price': 10,
|
||||
'applied_on': '3_global',
|
||||
'name': 'First discount'
|
||||
})]
|
||||
})
|
||||
|
||||
order = self.empty_order
|
||||
order.pricelist_id = first_pricelist
|
||||
order.write({'order_line': [
|
||||
(0, False, {
|
||||
'product_id': self.product_C.id,
|
||||
'name': '1 Product C',
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 1.0,
|
||||
})
|
||||
]})
|
||||
self._apply_promo_code(order, coupon.code)
|
||||
self.assertEqual(len(order.order_line.ids), 2)
|
||||
self.assertEqual(order.amount_total, 81, "SO total should be 81: (10% of 100 with pricelist) + 10% of 90 with coupon code")
|
||||
|
||||
def test_on_next_order_reward_promotion_program(self):
|
||||
# The flow:
|
||||
# 1. Create a program `A` that gives a free `Product B` on next order if you buy a an `product A`
|
||||
# This program should be code_needed with code `free_B_on_next_order`
|
||||
# 2. Create a program `B` that gives 10% discount on next order automatically
|
||||
# 3. Create a SO with a `third product` and recompute coupon, you SHOULD get a coupon (from program `B`) for your next order that will discount 10%
|
||||
# 4. Try to apply `A`, it should error since we did not buy any product A.
|
||||
# 5. Add a product A to the cart and try to apply `A` again, this time it should work
|
||||
# 6. Verify you have 2 generated coupons and validate the SO (so the 2 generated coupons will be valid)
|
||||
# 7. Create a new SO (with the same partner)
|
||||
# 8. Add a Product B in the cart
|
||||
# 9. Try to apply once again coupon generated by `A`, it should give you the free product B
|
||||
# 10. Try to apply coupon generated by `B`, it should give you 10% discount.
|
||||
# => SO will then be 0$ until we recompute the order lines
|
||||
|
||||
# 1.
|
||||
self.immediate_promotion_program.write({
|
||||
'applies_on': 'future',
|
||||
'trigger': 'with_code',
|
||||
})
|
||||
self.immediate_promotion_program.rule_ids.write({
|
||||
'mode': 'with_code',
|
||||
'code': 'free_B_on_next_order',
|
||||
})
|
||||
# 2.
|
||||
self.p1 = self.env['loyalty.program'].create({
|
||||
'name': 'Code for 10% on next order',
|
||||
'program_type': 'promotion',
|
||||
'applies_on': 'future',
|
||||
'trigger': 'auto',
|
||||
'rule_ids': [(0, 0, {})],
|
||||
'reward_ids': [(0, 0, {
|
||||
'reward_type': 'discount',
|
||||
'discount': 10,
|
||||
'discount_mode': 'percent',
|
||||
'discount_applicability': 'order',
|
||||
})],
|
||||
})
|
||||
# 3.
|
||||
order = self.empty_order.copy()
|
||||
self.third_product = self.env['product.product'].create({
|
||||
'name': 'Thrid Product',
|
||||
'list_price': 5,
|
||||
'sale_ok': True
|
||||
})
|
||||
order.write({'order_line': [
|
||||
(0, False, {
|
||||
'product_id': self.third_product.id,
|
||||
'name': '1 Third Product',
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 1.0,
|
||||
})
|
||||
]})
|
||||
order._update_programs_and_rewards()
|
||||
self.assertEqual(len(self.p1.coupon_ids.ids), 1, "You should get a coupon for you next order that will offer 10% discount")
|
||||
# 4.
|
||||
with self.assertRaises(ValidationError):
|
||||
self._apply_promo_code(order, 'free_B_on_next_order')
|
||||
# 5.
|
||||
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': 1.0,
|
||||
})
|
||||
]})
|
||||
self._apply_promo_code(order, 'free_B_on_next_order', no_reward_fail=False)
|
||||
# 6.
|
||||
self.assertEqual(len(order._get_reward_coupons()), 2, "You should get a second coupon for your next order that will offer a free Product B")
|
||||
order.action_confirm()
|
||||
# 7.
|
||||
order_bis = self.empty_order
|
||||
|
||||
# 8.
|
||||
order_bis.write({'order_line': [
|
||||
(0, False, {
|
||||
'product_id': self.product_B.id,
|
||||
'name': '1 Product B',
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 1.0,
|
||||
})
|
||||
]})
|
||||
# 9.
|
||||
self._apply_promo_code(order_bis, order._get_reward_coupons()[1].code)
|
||||
self.assertEqual(len(order_bis.order_line), 2, "You should get a free Product B")
|
||||
# 10.
|
||||
self._apply_promo_code(order_bis, order._get_reward_coupons()[0].code)
|
||||
self.assertEqual(len(order_bis.order_line), 3, "You should get a 10% discount line")
|
||||
self.assertAlmostEqual(order_bis.amount_total, order_bis.order_line[0].price_total * 0.9, 2, "SO total should be null: (Paid product - Free product = 0) + 10% of nothing")
|
||||
|
||||
def test_on_next_order_reward_promotion_program_with_requirements(self):
|
||||
self.immediate_promotion_program.write({
|
||||
'applies_on': 'future',
|
||||
'trigger': 'with_code',
|
||||
})
|
||||
self.immediate_promotion_program.rule_ids.write({
|
||||
'minimum_amount': 700,
|
||||
'minimum_amount_tax_mode': 'excl',
|
||||
'mode': 'with_code',
|
||||
'code': 'free_B_on_next_order',
|
||||
})
|
||||
order = self.empty_order.copy()
|
||||
self.product_A.lst_price = 700
|
||||
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': 1.0,
|
||||
})
|
||||
]})
|
||||
self._apply_promo_code(order, 'free_B_on_next_order', no_reward_fail=False)
|
||||
self.assertEqual(len(self.immediate_promotion_program.coupon_ids.ids), 1, "You should get a coupon for you next order that will offer a free product B")
|
||||
order_bis = self.empty_order
|
||||
order_bis.write({'order_line': [
|
||||
(0, False, {
|
||||
'product_id': self.product_B.id,
|
||||
'name': '1 Product B',
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 1.0,
|
||||
})
|
||||
]})
|
||||
with self.assertRaises(ValidationError):
|
||||
# It should error since we did not validate the previous SO, so the coupon is `reserved` but not `new`
|
||||
self._apply_promo_code(order_bis, order._get_reward_coupons()[0].code)
|
||||
order.action_confirm()
|
||||
# It should not error even if the SO does not have the requirements (700$ and 1 product A), since these requirements where only used to generate the coupon that we are now applying
|
||||
self._apply_promo_code(order_bis, order._get_reward_coupons()[0].code, no_reward_fail=False)
|
||||
self.assertEqual(len(order_bis.order_line), 2, "You should get 1 regular product_B and 1 free product_B")
|
||||
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_edit_and_reapply_promotion_program(self):
|
||||
# The flow:
|
||||
# 1. Create a program auto applied, giving a fixed amount discount
|
||||
# 2. Create a SO and apply the program
|
||||
# 3. Change the program, requiring a mandatory code
|
||||
# 4. Reapply the program on the same SO via code
|
||||
|
||||
self.immediate_promotion_program.active = False
|
||||
# 1.
|
||||
self.p1 = self.env['loyalty.program'].create({
|
||||
'name': 'Promo fixed amount',
|
||||
'trigger': 'auto',
|
||||
'program_type': 'promotion',
|
||||
'rule_ids': [(0, 0, {})],
|
||||
'reward_ids': [(0, 0, {
|
||||
'reward_type': 'discount',
|
||||
'discount': 10,
|
||||
'discount_mode': 'per_point',
|
||||
'discount_applicability': 'order',
|
||||
})]
|
||||
})
|
||||
# 2.
|
||||
order = self.empty_order.copy()
|
||||
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': 1.0,
|
||||
})
|
||||
]})
|
||||
order._update_programs_and_rewards()
|
||||
self._claim_reward(order, self.p1)
|
||||
self.assertEqual(len(order.order_line), 2, "You should get a discount line") # product + discount
|
||||
# 3.
|
||||
self.p1.write({
|
||||
'trigger': 'with_code',
|
||||
})
|
||||
self.p1.rule_ids.write({
|
||||
'mode': 'with_code',
|
||||
'code': 'test',
|
||||
})
|
||||
order._update_programs_and_rewards()
|
||||
self.assertEqual(len(order.order_line), 1, "You loose a discount line")
|
||||
# 4.
|
||||
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
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.sale_loyalty.tests.common import TestSaleCouponCommon
|
||||
|
||||
|
||||
class TestProgramWithoutCodeOperations(TestSaleCouponCommon):
|
||||
# Test some basic operation (create, write, unlink) on an immediate coupon program on which we should
|
||||
# apply or remove the reward automatically, as there's no program code.
|
||||
|
||||
def test_immediate_program_basic_operation(self):
|
||||
|
||||
# 2 products A are needed
|
||||
self.immediate_promotion_program.rule_ids.write({'minimum_qty': 2.0})
|
||||
order = self.empty_order
|
||||
# Test case 1 (1 A): Assert that no reward is given, as the product B is missing
|
||||
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': 1.0,
|
||||
})
|
||||
]})
|
||||
order._update_programs_and_rewards()
|
||||
self._claim_reward(order, self.immediate_promotion_program)
|
||||
self.assertEqual(len(order.order_line.ids), 1, "The promo offer shouldn't have been applied as the product B isn't in the order")
|
||||
|
||||
# Test case 2 (1 A 1 B): Assert that no reward is given, as the product A is not present in the correct quantity
|
||||
order.write({'order_line': [
|
||||
(0, False, {
|
||||
'product_id': self.product_B.id,
|
||||
'name': '2 Product B',
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 1.0,
|
||||
})
|
||||
]})
|
||||
order._update_programs_and_rewards()
|
||||
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 2 product A aren't in the order")
|
||||
|
||||
# Test case 3 (2 A 1 B): Assert that the reward is given as the product B is now in the order
|
||||
order.write({'order_line': [(1, order.order_line[0].id, {'product_uom_qty': 2.0})]})
|
||||
order._update_programs_and_rewards()
|
||||
self._claim_reward(order, self.immediate_promotion_program)
|
||||
self.assertEqual(len(order.order_line.ids), 3, "The promo offer should have been applied, the discount is not created")
|
||||
|
||||
# Test case 4 (1 A 1 B): Assert that the reward is removed as we don't buy 2 products B anymore
|
||||
order.write({'order_line': [(1, order.order_line[0].id, {'product_uom_qty': 1.0})]})
|
||||
order._update_programs_and_rewards()
|
||||
self._claim_reward(order, self.immediate_promotion_program)
|
||||
self.assertEqual(len(order.order_line.ids), 2, "The promo reward should have been removed as the rules are not matched anymore")
|
||||
self.assertEqual(order.order_line[0].product_id.id, self.product_A.id, "The wrong line has been removed")
|
||||
self.assertEqual(order.order_line[1].product_id.id, self.product_B.id, "The wrong line has been removed")
|
||||
|
||||
# Test case 5 (1 B): Assert that the reward is removed when the order is modified and doesn't match the rules anymore
|
||||
order.write({'order_line': [
|
||||
(1, order.order_line[0].id, {'product_uom_qty': 2.0}),
|
||||
(2, order.order_line[0].id, False)
|
||||
]})
|
||||
order._update_programs_and_rewards()
|
||||
self._claim_reward(order, self.immediate_promotion_program)
|
||||
self.assertEqual(len(order.order_line.ids), 1, "The promo reward should have been removed as the rules are not matched anymore")
|
||||
self.assertEqual(order.order_line.product_id.id, self.product_B.id, "The wrong line has been removed")
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
# -*- 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
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestSaleInvoicing(TestSaleCouponCommon):
|
||||
|
||||
def test_invoicing_order_with_promotions(self):
|
||||
discount_coupon_program = self.env['loyalty.program'].create({
|
||||
'name': '10% Discount',
|
||||
'program_type': 'coupons',
|
||||
'applies_on': 'current',
|
||||
'trigger': 'auto',
|
||||
'rule_ids': [(0, 0, {})],
|
||||
'reward_ids': [(0, 0, {
|
||||
'reward_type': 'discount',
|
||||
'discount': 10,
|
||||
'discount_mode': 'percent',
|
||||
'discount_applicability': 'order',
|
||||
})]
|
||||
})
|
||||
|
||||
product = self.env['product.product'].create({
|
||||
'invoice_policy': 'delivery',
|
||||
'name': 'Product invoiced on delivery',
|
||||
'lst_price': 500,
|
||||
})
|
||||
|
||||
order = self.empty_order
|
||||
order.write({
|
||||
'order_line': [
|
||||
(0, 0, {
|
||||
'product_id': product.id,
|
||||
})
|
||||
]
|
||||
})
|
||||
|
||||
#Check default invoice_policy on discount product
|
||||
self.assertEqual(discount_coupon_program.reward_ids.discount_line_product_id.invoice_policy, 'order')
|
||||
|
||||
order._update_programs_and_rewards()
|
||||
self._claim_reward(order, discount_coupon_program)
|
||||
# Order is not confirmed, there shouldn't be any invoiceable line
|
||||
invoiceable_lines = order._get_invoiceable_lines()
|
||||
self.assertEqual(len(invoiceable_lines), 0)
|
||||
|
||||
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()
|
||||
self.assertEqual(order.invoice_status, 'no')
|
||||
self.assertEqual(len(invoiceable_lines), 0)
|
||||
with self.assertRaises(UserError):
|
||||
order._create_invoices()
|
||||
|
||||
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)
|
||||
account_move = order._create_invoices()
|
||||
self.assertEqual(len(account_move.invoice_line_ids), 2)
|
||||
|
||||
def test_coupon_on_order_sequence(self):
|
||||
discount_coupon_program = self.env['loyalty.program'].create({
|
||||
'name': '10% Discount',
|
||||
'program_type': 'coupons',
|
||||
'applies_on': 'current',
|
||||
'trigger': 'auto',
|
||||
'rule_ids': [(0, 0, {})],
|
||||
'reward_ids': [(0, 0, {
|
||||
'reward_type': 'discount',
|
||||
'discount': 10,
|
||||
'discount_mode': 'percent',
|
||||
'discount_applicability': 'order',
|
||||
})]
|
||||
})
|
||||
|
||||
order = self.empty_order
|
||||
|
||||
product_6 = self.env['product.product'].create({
|
||||
'name': 'Large Cabinet',
|
||||
})
|
||||
# orderline1
|
||||
self.env['sale.order.line'].create({
|
||||
'product_id': product_6.id,
|
||||
'name': 'largeCabinet',
|
||||
'product_uom_qty': 1.0,
|
||||
'order_id': order.id,
|
||||
})
|
||||
|
||||
#Check default invoice_policy on discount product
|
||||
self.assertEqual(discount_coupon_program.reward_ids.discount_line_product_id.invoice_policy, 'order')
|
||||
|
||||
self._auto_rewards(order, discount_coupon_program)
|
||||
|
||||
self.assertEqual(len(order.order_line), 2, 'Coupon correctly applied')
|
||||
|
||||
product_11 = self.env['product.product'].create({
|
||||
'name': 'Conference Chair',
|
||||
})
|
||||
|
||||
# orderline2
|
||||
self.env['sale.order.line'].create({
|
||||
'product_id': product_11.id,
|
||||
'name': 'conferenceChair',
|
||||
'product_uom_qty': 1.0,
|
||||
'order_id': order.id,
|
||||
})
|
||||
|
||||
order._update_programs_and_rewards()
|
||||
self.assertEqual(len(order.order_line), 3, 'Coupon correctly applied')
|
||||
|
||||
self.assertTrue(order.order_line.sorted(lambda x: x.sequence)[-1].is_reward_line, 'Global coupons appear on the last line')
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import Command
|
||||
|
||||
from odoo.addons.sale_loyalty.tests.common import TestSaleCouponCommon
|
||||
from odoo.tests.common import tagged
|
||||
|
||||
|
||||
@tagged('-at_install', 'post_install')
|
||||
class TestUnlinkReward(TestSaleCouponCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.promotion_program = cls.env['loyalty.program'].create({
|
||||
'name': 'Buy A + 1 B, 1 B are free',
|
||||
'program_type': 'promotion',
|
||||
'applies_on': 'current',
|
||||
'company_id': cls.env.company.id,
|
||||
'trigger': 'auto',
|
||||
'rule_ids': [Command.create({
|
||||
'product_ids': cls.product_A,
|
||||
'reward_point_amount': 1,
|
||||
'reward_point_mode': 'order',
|
||||
'minimum_qty': 1,
|
||||
})],
|
||||
})
|
||||
cls.reward = cls.env['loyalty.reward'].create({
|
||||
'program_id': cls.promotion_program.id,
|
||||
'reward_type': 'discount',
|
||||
})
|
||||
|
||||
def test_sale_unlink_reward(self):
|
||||
order = self.empty_order
|
||||
order.write({'order_line': [
|
||||
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,
|
||||
}),
|
||||
]})
|
||||
order._update_programs_and_rewards()
|
||||
self._claim_reward(order, self.promotion_program)
|
||||
self.reward.unlink()
|
||||
|
||||
# Check that the reward is archived and not deleted
|
||||
self.assertTrue(self.reward.exists())
|
||||
self.assertFalse(self.reward.active)
|
||||
Loading…
Add table
Add a link
Reference in a new issue