19.0 vanilla

This commit is contained in:
Ernad Husremovic 2026-03-09 09:32:12 +01:00
parent 79f83631d5
commit 73afc09215
6267 changed files with 1534193 additions and 1130106 deletions

View file

@ -1,8 +1,12 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import test_apply_pending_coupon
from . import test_ewallet
from . import test_concurrent_promo_code
from . import test_free_product_reward
from . import test_sale_coupon_multiwebsite
from . import test_shop_loyalty_payment
from . import test_shop_multi_reward
from . import test_shop_sale_coupon
from . import test_website_sale_auto_invoice
from . import test_website_sale_loyalty_delivery

View file

@ -1,24 +1,23 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import http
from odoo.tests import tagged, HttpCase
from odoo.tests import tagged
from odoo.addons.sale_loyalty.tests.common import TestSaleCouponNumbersCommon
from odoo.addons.website.tools import MockRequest
from odoo.addons.website_sale.tests.common import MockRequest, WebsiteSaleCommon
from odoo.addons.website_sale_loyalty.controllers.cart import Cart
from odoo.addons.website_sale_loyalty.controllers.main import WebsiteSale
@tagged('-at_install', 'post_install')
class TestSaleCouponApplyPending(HttpCase, TestSaleCouponNumbersCommon):
class TestSaleCouponApplyPending(TestSaleCouponNumbersCommon, WebsiteSaleCommon):
def setUp(self):
super().setUp()
@classmethod
def setUpClass(cls):
super().setUpClass()
self.WebsiteSaleController = WebsiteSale()
self.website = self.env['website'].browse(1)
self.global_program = self.p1
self.coupon_program = self.env['loyalty.program'].create({
cls.global_program = cls.p1
cls.coupon_program = cls.env['loyalty.program'].create({
'name': 'One Free Product',
'program_type': 'coupons',
'rule_ids': [(0, 0, {
@ -26,42 +25,75 @@ class TestSaleCouponApplyPending(HttpCase, TestSaleCouponNumbersCommon):
})],
'reward_ids': [(0, 0, {
'reward_type': 'product',
'reward_product_id': self.largeCabinet.id,
'reward_product_id': cls.largeCabinet.id,
})]
})
self.env['loyalty.generate.wizard'].with_context(active_id=self.coupon_program.id).create({
cls.env['loyalty.generate.wizard'].with_context(active_id=cls.coupon_program.id).create({
'coupon_qty': 1,
'points_granted': 1,
}).generate_coupons()
self.coupon = self.coupon_program.coupon_ids[0]
installed_modules = set(self.env['ir.module.module'].search([
cls.coupon = cls.coupon_program.coupon_ids[0]
installed_modules = set(cls.env['ir.module.module'].search([
('state', '=', 'installed'),
]).mapped('name'))
for _ in http._generate_routing_rules(installed_modules, nodb_only=False):
pass
def setUp(self):
super().setUp()
self.WebsiteSaleController = WebsiteSale()
self.WebsiteSaleCartController = Cart()
def test_01_activate_coupon_with_existing_program(self):
order = self.empty_order
order = self.empty_cart
self.env['product.pricelist.item'].search([]).unlink()
with MockRequest(self.env, website=self.website, sale_order_id=order.id, website_sale_current_pl=1) as request:
self.WebsiteSaleController.cart_update_json(self.largeCabinet.id, set_qty=2)
with MockRequest(self.env, website=self.website, sale_order_id=order.id) as request:
self.WebsiteSaleCartController.add_to_cart(
product_template_id=self.largeCabinet.product_tmpl_id,
product_id=self.largeCabinet.id,
quantity=2,
)
self.WebsiteSaleController.pricelist(self.global_program.rule_ids.code)
self.assertEqual(order.amount_total, 576, "The order total should equal 576: 2*320 - 10% discount ")
self.assertEqual(len(order.order_line), 2, "There should be 2 lines 1 for the product and 1 for the discount")
self.assertEqual(
order.amount_total,
576,
"The order total should equal 576: 2*320 - 10% discount "
)
self.assertEqual(
len(order.order_line),
2,
"There should be 2 lines 1 for the product and 1 for the discount"
)
self.WebsiteSaleController.activate_coupon(self.coupon.code)
promo_code = request.session.get('pending_coupon_code')
self.assertFalse(promo_code, "The promo code should be removed from the pending coupon dict")
self.assertEqual(order.amount_total, 576, "The order total should equal 576: 2*320 - 0 (free product) - 10%")
self.assertEqual(len(order.order_line), 3, "There should be 3 lines 1 for the product, 1 for the free product and 1 for the discount")
self.assertFalse(
promo_code,
"The promo code should be removed from the pending coupon dict"
)
self.assertEqual(
order.amount_total,
576,
"The order total should equal 576: 2*320 - 0 (free product) - 10%"
)
self.assertEqual(
len(order.order_line),
3,
"There should be 3 lines 1 for the product, 1 for the free product and 1 for the discount"
)
def test_02_pending_coupon_with_existing_program(self):
order = self.empty_order
order = self.empty_cart
self.env['product.pricelist.item'].search([]).unlink()
with MockRequest(self.env, website=self.website, sale_order_id=order.id, website_sale_current_pl=1) as request:
self.WebsiteSaleController.cart_update_json(self.largeCabinet.id, set_qty=1)
with MockRequest(self.env, website=self.website, sale_order_id=order.id) as request:
self.WebsiteSaleCartController.add_to_cart(
product_template_id=self.largeCabinet.product_tmpl_id,
product_id=self.largeCabinet.id,
quantity=1,
)
self.WebsiteSaleController.pricelist(self.global_program.rule_ids.code)
self.assertEqual(self.largeCabinet.lst_price, 320)
cabinet_sol = order.order_line.filtered(lambda sol: sol.product_id == self.largeCabinet)
@ -78,12 +110,31 @@ class TestSaleCouponApplyPending(HttpCase, TestSaleCouponNumbersCommon):
promo_code = request.session.get('pending_coupon_code')
self.assertEqual(order.amount_tax, 0)
self.assertEqual(order.cart_quantity, 1)
self.assertEqual(order.amount_total, 288, "The order total should still equal 288 as the coupon for free product can't be applied since it requires 2 min qty")
self.assertEqual(promo_code, self.coupon.code, "The promo code should be set in the pending coupon dict as it couldn't be applied, we save it for later reuse")
self.assertEqual(
order.amount_total,
288,
"The order total should still equal 288 as the coupon for free product can't be applied since it requires 2 min qty"
)
self.assertEqual(
promo_code,
self.coupon.code,
"The promo code should be set in the pending coupon dict as it couldn't be applied, we save it for later reuse"
)
self.WebsiteSaleController.cart_update_json(self.largeCabinet.id, add_qty=1)
self.WebsiteSaleCartController.add_to_cart(
product_template_id=self.largeCabinet.product_tmpl_id,
product_id=self.largeCabinet.id,
quantity=1,
)
promo_code = request.session.get('pending_coupon_code')
self.assertFalse(promo_code, "The promo code should be removed from the pending coupon dict as it should have been applied")
self.assertFalse(
promo_code,
"The promo code should be removed from the pending coupon dict as it should have been applied"
)
self.assertEqual(order.amount_tax, 0)
self.assertEqual(order.cart_quantity, 2)
self.assertEqual(order.amount_total, 576, "The order total should equal 576: 2*320 - 0 (free product) - 10%")
self.assertEqual(
order.amount_total,
576,
"The order total should equal 576: 2*320 - 0 (free product) - 10%"
)

View file

@ -0,0 +1,133 @@
import threading
from concurrent.futures import ThreadPoolExecutor
from psycopg2 import OperationalError
from odoo import SUPERUSER_ID, api
from odoo.modules.registry import Registry
from odoo.tests import tagged
from odoo.tests.common import BaseCase, get_db_name
from odoo.tools import mute_logger
@tagged('-standard', '-at_install', 'post_install', 'database_breaking')
class TestConcurrencyPromoCode(BaseCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.registry = Registry(get_db_name())
with cls.registry.cursor() as cr:
env = api.Environment(cr, SUPERUSER_ID, {})
cls.promo_code = "AZERTY123456"
cls.promo_code_program = env['loyalty.program'].create({
'name': 'FREE FOR ONE',
'program_type': 'promo_code',
'limit_usage': True,
'max_usage': 1,
'rule_ids': [(0, 0, {
'minimum_qty': 0,
'code': cls.promo_code,
})],
'reward_ids': [(0, 0, {
'reward_type': 'discount',
'discount_mode': 'percent',
'discount_applicability': 'order',
'discount': 100.0,
})]
})
cls.partner_1 = env['res.partner'].create([{
'name': 'Mitchel Notadmin',
'email': 'mitch.el@example.com',
}])
cls.partner_2 = env['res.partner'].create({
'name': 'John Smith',
'email': 'john.smith@example.com',
})
cls.product = env['product.product'].create({
'name': "TEST PRODUCT",
'standard_price': 100,
})
cls.order_partner_1 = env['sale.order'].create({'partner_id': cls.partner_1.id})
cls.order_partner_2 = env['sale.order'].create({'partner_id': cls.partner_2.id})
cls.order_lines = env['sale.order.line'].create([{
'order_id': cls.order_partner_1.id,
'product_id': cls.product.id,
'product_uom_qty': 1,
}, {
'order_id': cls.order_partner_2.id,
'product_id': cls.product.id,
'product_uom_qty': 3,
},
])
cls._released_signal = threading.Event()
cls.envs = [
api.Environment(cls.registry.cursor(), SUPERUSER_ID, {}),
api.Environment(cls.registry.cursor(), SUPERUSER_ID, {}),
]
cr.commit()
def reset():
for env in cls.envs:
env.cr.close()
with cls.registry.cursor() as cr:
cr.execute("""
DELETE FROM loyalty_card WHERE program_id = %(program_id)s;
DELETE FROM loyalty_rule WHERE program_id = %(program_id)s;
DELETE FROM loyalty_reward WHERE program_id = %(program_id)s;
DELETE FROM loyalty_program WHERE id = %(program_id)s;
DELETE FROM sale_order_line WHERE id IN %(sol_ids)s;
DELETE FROM sale_order WHERE id IN %(so_ids)s;
DELETE FROM res_partner WHERE id IN %(partner_ids)s;
DELETE FROM product_product WHERE id = %(product_id)s;
""", {
'program_id': cls.promo_code_program.id,
'sol_ids': tuple(cls.order_lines.ids),
'so_ids': (cls.order_partner_1.id, cls.order_partner_2.id),
'partner_ids': (cls.partner_1.id, cls.partner_2.id),
'product_id': cls.product.id,
})
cls.addClassCleanup(reset)
@mute_logger('odoo.sql_db')
def test_lock_concurrent_promo_code(self):
""" Test that two cursors cannot lock the same row simultaneously """
# A simple barrier to make sure threads start roughly at the same time
start_barrier = threading.Barrier(2)
def run(env, order_id):
env.cr.execute("SELECT id FROM loyalty_rule WHERE code = %s", (self.promo_code,))
self.assertTrue(env.cr.fetchone())
order = env['sale.order'].browse(order_id)
# Wait for the other threads to be ready
start_barrier.wait()
try:
order._try_apply_code(self.promo_code)
self._released_signal.wait(timeout=20) # Hold the lock for a moment to ensure overlap
return True
except OperationalError: # This catches the Postgres error when a row is locked
self._released_signal.set() # Signal to release the lock
return False
with ThreadPoolExecutor(max_workers=2) as executor:
future_1 = executor.submit(run, self.envs[0], self.order_partner_1.id)
future_2 = executor.submit(run, self.envs[1], self.order_partner_2.id)
# One should go through, the other should be locked (does not matter
# which thread)
res_1 = future_1.result(timeout=3)
res_2 = future_2.result(timeout=3)
self.assertNotEqual(res_1, res_2)

View file

@ -0,0 +1,70 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import http
from odoo.fields import Command
from odoo.tests import HttpCase, tagged
from odoo.addons.website_sale.tests.common import MockRequest, WebsiteSaleCommon
from odoo.addons.website_sale_loyalty.controllers.cart import Cart
from odoo.addons.website_sale_loyalty.controllers.main import WebsiteSale
@tagged('post_install', '-at_install')
class TestEwallet(HttpCase, WebsiteSaleCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.WebsiteSaleController = WebsiteSale()
cls.WebsiteSaleCartController = Cart()
cls.product.write({'taxes_id': [Command.clear()]})
cls.topup = cls.env['product.product'].create({
'name': 'Ewallet Top up',
'list_price': 50.0,
'website_published': True,
})
cls.ewallet_program = cls.env['loyalty.program'].create([{
'name': 'E-wallet Card Program',
'program_type': 'ewallet',
'trigger': 'auto',
'applies_on': 'future',
'rule_ids': [Command.create({
'reward_point_mode': 'money',
'reward_point_amount': 10,
'product_ids': cls.topup,
})],
'reward_ids': [Command.create({
'discount_mode': 'per_point',
'discount': 1,
'discount_applicability': 'order',
})],
}])
installed_modules = set(cls.env['ir.module.module'].search([
('state', '=', 'installed'),
]).mapped('name'))
for _ in http._generate_routing_rules(installed_modules, nodb_only=False):
pass
def test_ewallet(self):
self.env['loyalty.generate.wizard'].create({
'program_id': self.ewallet_program.id,
'coupon_qty': 1,
'points_granted': 10,
}).generate_coupons()
self.ewallet_program.coupon_ids[0].partner_id = self.env.user.partner_id
order = self.empty_cart
with MockRequest(self.env, website=self.website, sale_order_id=order.id):
self.WebsiteSaleCartController.add_to_cart(
product_template_id=self.product.product_tmpl_id,
product_id=self.product.id,
quantity=1,
)
self.assertEqual(order.amount_total, 20)
self.WebsiteSaleController.claim_reward(self.ewallet_program.reward_ids[0].id)
self.assertEqual(order.amount_total, 10)

View file

@ -1,81 +1,118 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import http
from odoo.tests.common import HttpCase
from odoo import Command, http
from odoo.tests import tagged
from odoo.addons.website.tools import MockRequest
from odoo.addons.base.tests.common import HttpCaseWithUserPortal
from odoo.addons.website_sale.tests.common import MockRequest, WebsiteSaleCommon
from odoo.addons.website_sale_loyalty.controllers.cart import Cart
from odoo.addons.website_sale_loyalty.controllers.main import WebsiteSale
@tagged('post_install', '-at_install')
class TestFreeProductReward(HttpCase):
class TestFreeProductReward(HttpCaseWithUserPortal, WebsiteSaleCommon):
def setUp(self):
super().setUp()
@classmethod
def setUpClass(cls):
super().setUpClass()
self.WebsiteSaleController = WebsiteSale()
self.website = self.env['website'].browse(1)
cls.WebsiteSaleCartController = Cart()
cls.WebsiteSaleController = WebsiteSale()
self.sofa = self.env['product.product'].create({
'name': 'Test Sofa',
'list_price': 2950.0,
'website_published': True,
})
cls.website = cls.website.with_user(cls.user_portal)
cls.empty_cart.partner_id = cls.partner_portal
self.carpet = self.env['product.product'].create({
'name': 'Test Carpet',
'list_price': 500.0,
'website_published': True,
})
cls.sofa, cls.carpet = cls.env['product.product'].create([
{
'name': "Test Sofa",
'list_price': 2950.0,
'website_published': True,
},
{
'name': "Test Carpet",
'list_price': 500.0,
'website_published': True,
},
])
# Disable any other program
self.program = self.env['loyalty.program'].search([]).write({'active': False})
cls.program = cls.env['loyalty.program'].search([]).write({'active': False})
self.program = self.env['loyalty.program'].create({
cls.program = cls.env['loyalty.program'].create({
'name': 'Get a product for free',
'program_type': 'promotion',
'applies_on': 'current',
'trigger': 'auto',
'rule_ids': [(0, 0, {
'rule_ids': [Command.create({
'minimum_qty': 1,
'minimum_amount': 0.00,
'reward_point_amount': 1,
'reward_point_mode': 'order',
'product_ids': self.sofa,
'product_ids': cls.sofa,
})],
'reward_ids': [(0, 0, {
'reward_ids': [Command.create({
'reward_type': 'product',
'reward_product_id': self.carpet.id,
'reward_product_id': cls.carpet.id,
'reward_product_qty': 1,
'required_points': 1,
})],
})
self.steve = self.env['res.partner'].create({
'name': 'Steve Bucknor',
'email': 'steve.bucknor@example.com',
})
self.empty_order = self.env['sale.order'].create({
'partner_id': self.steve.id
})
installed_modules = set(self.env['ir.module.module'].search([
('state', '=', 'installed'),
]).mapped('name'))
for _ in http._generate_routing_rules(installed_modules, nodb_only=False):
installed_modules = cls.env['ir.module.module'].search([('state', '=', 'installed')])
for _ in http._generate_routing_rules(installed_modules.mapped('name'), nodb_only=False):
pass
def test_add_product_to_cart_when_it_exist_as_free_product(self):
# This test the flow when we claim a reward in the cart page and then we
# want to add the product again
order = self.empty_order
with MockRequest(self.env, website=self.website, sale_order_id=order.id, website_sale_current_pl=1):
self.WebsiteSaleController.cart_update_json(self.sofa.id, set_qty=1)
order = self.empty_cart
with MockRequest(self.website.env, website=self.website, sale_order_id=order.id):
self.WebsiteSaleCartController.add_to_cart(
product_template_id=self.sofa.product_tmpl_id,
product_id=self.sofa.id,
quantity=1,
)
self.WebsiteSaleController.claim_reward(self.program.reward_ids[0].id)
self.WebsiteSaleController.cart_update_json(self.carpet.id, set_qty=1)
self.WebsiteSaleCartController.add_to_cart(
product_template_id=self.carpet.product_tmpl_id,
product_id=self.carpet.id,
quantity=1,
)
sofa_line = order.order_line.filtered(lambda line: line.product_id.id == self.sofa.id)
carpet_reward_line = order.order_line.filtered(lambda line: line.product_id.id == self.carpet.id and line.is_reward_line)
carpet_line = order.order_line.filtered(lambda line: line.product_id.id == self.carpet.id and not line.is_reward_line)
self.assertEqual(sofa_line.product_uom_qty, 1, "Should have only 1 qty of Sofa")
self.assertEqual(carpet_reward_line.product_uom_qty, 1, "Should have only 1 qty for the carpet as reward")
self.assertEqual(carpet_line.product_uom_qty, 1, "Should have only 1 qty for carpet as non reward")
def test_get_claimable_free_shipping(self):
cart = self.empty_cart
self.program.write({
'program_type': 'next_order_coupons',
'applies_on': 'future',
'coupon_ids': [
Command.clear(),
Command.create({'partner_id': cart.partner_id.id, 'points': 100}),
],
'reward_ids': [Command.update(self.program.reward_ids.id, {
'reward_type': 'shipping',
'reward_product_id': None,
})],
})
coupon = self.program.coupon_ids
with MockRequest(self.website.env, website=self.website, sale_order_id=cart.id):
self.assertDictEqual(cart._get_claimable_and_showable_rewards(), {
coupon: self.program.reward_ids,
})
self.WebsiteSaleCartController.add_to_cart(
product_template_id=self.sofa.product_tmpl_id,
product_id=self.sofa.id,
quantity=1,
)
self.WebsiteSaleController.claim_reward(self.program.reward_ids.id, code=coupon.code)
self.assertTrue(cart.order_line.reward_id)
self.assertFalse(
cart._get_claimable_and_showable_rewards(),
"Rewards should no longer be claimable if already claimed",
)

View file

@ -1,10 +1,11 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.addons.sale_loyalty.tests.common import TestSaleCouponNumbersCommon
from odoo.addons.website.tools import MockRequest
from odoo.exceptions import UserError
from odoo.tests import tagged
from odoo.addons.sale_loyalty.tests.common import TestSaleCouponNumbersCommon
from odoo.addons.website_sale.tests.common import MockRequest
@tagged('-at_install', 'post_install')
class TestSaleCouponMultiwebsite(TestSaleCouponNumbersCommon):

View file

@ -4,7 +4,7 @@ from datetime import date, timedelta
from freezegun import freeze_time
from odoo import Command
from odoo.tests import tagged
from odoo.tests import JsonRpcException, tagged
from odoo.tools import mute_logger
from odoo.addons.payment.tests.http_common import PaymentHttpCommon
@ -12,7 +12,7 @@ from odoo.addons.sale_loyalty.tests.common import TestSaleCouponCommon
@tagged('post_install', '-at_install')
class TestShopLoyaltyTransaction(PaymentHttpCommon, TestSaleCouponCommon):
class TestShopLoyaltyPayment(PaymentHttpCommon, TestSaleCouponCommon):
@classmethod
def setUpClass(cls):
@ -29,8 +29,7 @@ class TestShopLoyaltyTransaction(PaymentHttpCommon, TestSaleCouponCommon):
order = self.empty_order
program = self.program_gift_card
program.date_to = date.today() # set program to expire after today
self.product_a.type = 'service' # prevent need for delivery method
program.date_to = date.today() + timedelta(days=1) # set program to expire after tomorrow
self.env['loyalty.generate.wizard'].with_context(active_id=program.id).create({
'coupon_qty': 1,
@ -42,33 +41,51 @@ class TestShopLoyaltyTransaction(PaymentHttpCommon, TestSaleCouponCommon):
'website_id': self.website.id,
'message_partner_ids': self.portal_partner.ids,
'order_line': [Command.create({
'product_id': self.product_a.id,
'product_id': self.service_product.id,
'tax_ids': None,
})],
})
self._apply_promo_code(order, program.coupon_ids.code)
with freeze_time(program.date_to + timedelta(days=1)):
with freeze_time(program.date_to + timedelta(days=2)):
self.authenticate(self.portal_user.login, self.portal_user.login)
tx_response = self._make_json_rpc_request(
with self.assertRaises(
JsonRpcException,
msg="Payment shouldn't succeed with expired reward still applied",
):
self.make_jsonrpc_request(
self._build_url(f'/shop/payment/transaction/{order.id}'),
{
'order_id': order.id,
'access_token': None,
'amount': order.amount_total,
'provider_id': self.provider.id,
'payment_method_id': self.payment_method.id,
'flow': 'direct',
'token_id': None,
'tokenization_requested': False,
'landing_route': order.get_portal_url(),
},
)
# Update rewards & retry transaction
order._update_programs_and_rewards()
tx_response = self.make_jsonrpc_request(
self._build_url(f'/shop/payment/transaction/{order.id}'),
{
'order_id': order.id,
'access_token': None,
'amount': order.amount_total,
'currency_id': order.currency_id.id,
'payment_option_id': self.provider.id,
'provider_id': self.provider.id,
'payment_method_id': self.payment_method.id,
'flow': 'direct',
'token_id': None,
'tokenization_requested': False,
'landing_route': order.get_portal_url(),
},
).json()
self.assertIn(
'error',
tx_response,
"Attempting to initate payment with an expired reward should raise an error.",
)
self.assertEqual(
tx_response['error']['data']['message'],
"Cannot process payment: applied reward was changed or has expired.",
)
)
self.assertEqual(
tx_response['amount'],
self.service_product.list_price,
"Payment should succeed after removing expired reward",
)

View file

@ -1,39 +1,43 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details
from odoo.fields import Command
from odoo.tests import TransactionCase, tagged
from odoo import Command, http
from odoo.tests import tagged
from odoo.addons.website.tools import MockRequest
from odoo.addons.website_sale.tests.common import MockRequest, WebsiteSaleCommon
from odoo.addons.website_sale_loyalty.controllers.main import WebsiteSale
@tagged('post_install', '-at_install')
class TestClaimReward(TransactionCase):
class TestClaimReward(WebsiteSaleCommon):
def test_claim_reward_with_multi_product(self):
WebsiteSaleController = WebsiteSale()
@classmethod
def setUpClass(cls):
super().setUpClass()
tag = self.env['product.tag'].create({
cls.WebsiteSaleController = WebsiteSale()
cls.user_portal = cls._create_new_portal_user()
cls.partner_portal = cls.user_portal.partner_id
cls.env['product.pricelist'].search([]).action_archive()
tag = cls.env['product.tag'].create({
'name': 'multi reward',
})
product1, product2 = self.env['product.product'].create([
cls.product1, cls.product2 = cls.env['product.product'].create([
{
'name': 'Test Product',
'list_price': 10.0,
'taxes_id': False,
'product_tag_ids': tag,
}, {
'name': 'Test Product 2',
'list_price': 20.0,
'taxes_id': False,
'product_tag_ids': tag,
}])
partner = self.env['res.partner'].create({
'name': 'Test Customer',
'email': 'test@example.com',
})
promo_program = self.env['loyalty.program'].create({
cls.promo_program, cls.coupon_program = cls.env['loyalty.program'].create([{
'name': 'Free Products',
'program_type': 'promotion',
'applies_on': 'current',
@ -48,22 +52,100 @@ class TestClaimReward(TransactionCase):
'reward_product_tag_id': tag.id,
'reward_product_qty': 1,
'required_points': 1,
})]
})
website = self.env['website'].browse(1)
order = self.env['sale.order'].create({
'website_id': website.id,
'partner_id': partner.id,
'order_line': [Command.create({
'product_id': product1.id,
'product_uom_qty': 1,
})],
})
}, {
'name': "Multi-reward coupons",
'program_type': 'coupons',
'applies_on': 'current',
'trigger': 'with_code',
'reward_ids': [
Command.create({
'reward_type': 'product',
'reward_product_tag_id': tag.id,
'reward_product_qty': 1,
'required_points': 1,
'discount': None,
}),
Command.create({
'reward_type': 'discount',
'discount': 10.0,
'discount_mode': 'percent',
'required_points': 1,
}),
],
'coupon_ids': [Command.create({'points': 1})],
}])
cls.coupon = cls.coupon_program.coupon_ids
installed_modules = set(cls.env['ir.module.module'].search([
('state', '=', 'installed'),
]).mapped('name'))
for _ in http._generate_routing_rules(installed_modules, nodb_only=False):
pass
def test_claim_reward_with_multi_products(self):
product1, product2 = self.product2, self.product2
order = self.empty_cart
order.order_line = [Command.create({'product_id': product1.id})]
order._update_programs_and_rewards()
with MockRequest(self.env, website=website, sale_order_id=order.id):
WebsiteSaleController.claim_reward(promo_program.reward_ids[:1].id, product_id=str(product2.id))
with MockRequest(self.env, website=self.website, sale_order_id=order.id):
self.WebsiteSaleController.claim_reward(
self.promo_program.reward_ids.id,
product_id=str(product2.id),
)
self.assertEqual(len(order.order_line), 2, 'reward line should be added to order')
self.assertEqual(order.order_line[1].product_id, product2, 'added reward line should should contain product 2')
def test_apply_coupon_with_multiple_rewards_claim_discount(self):
cart = self.empty_cart
cart.update({
'partner_id': self.partner_portal.id,
'order_line': [Command.create({'product_id': self.product1.id})],
})
cart._update_programs_and_rewards()
website = cart.website_id.with_user(self.user_portal)
discount_reward = self.coupon_program.reward_ids.filtered('discount')
with MockRequest(website.env, website=website, sale_order_id=cart.id):
self.WebsiteSaleController.pricelist(promo=self.coupon.code)
self.assertFalse(cart.order_line.reward_id)
self.WebsiteSaleController.claim_reward(discount_reward.id, code=self.coupon.code)
self.assertTrue(cart.order_line.reward_id)
self.assertEqual(
discount_reward, cart.order_line.reward_id,
"Discount reward should be added to order",
)
self.assertAlmostEqual(
cart.amount_untaxed, self.product1.list_price * 0.9,
delta=cart.currency_id.rounding,
msg="10% discount should be applied",
)
def test_apply_coupon_with_multiple_rewards_claim_multiproduct(self):
cart = self.empty_cart
cart.update({
'partner_id': self.partner_portal.id,
'order_line': [Command.create({'product_id': self.product1.id})],
})
cart._update_programs_and_rewards()
website = cart.website_id.with_user(self.user_portal)
multiproduct_reward = self.coupon_program.reward_ids.filtered('reward_product_tag_id')
with MockRequest(website.env, website=website, sale_order_id=cart.id):
self.WebsiteSaleController.pricelist(promo=self.coupon.code)
self.assertFalse(cart.order_line.reward_id)
self.WebsiteSaleController.claim_reward(
multiproduct_reward.id,
code=self.coupon.code,
product_id=str(self.product1.id),
)
self.assertEqual(
multiproduct_reward, cart.order_line.reward_id,
"Product reward should be added",
)
self.assertIn(
self.product1, cart.order_line.product_id,
"Chosen reward product should be added to order",
)

View file

@ -1,18 +1,20 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import timedelta
from odoo import fields
from odoo import fields, http
from odoo.exceptions import ValidationError
from odoo.fields import Command
from odoo.tests import HttpCase, TransactionCase, tagged
from odoo.tests import HttpCase, tagged
from odoo.addons.sale.tests.test_sale_product_attribute_value_config import (
TestSaleProductAttributeValueCommon,
)
from odoo.addons.sale.tests.common import TestSaleCommon
from odoo.addons.website_sale.tests.common import MockRequest, WebsiteSaleCommon
from odoo.addons.website_sale_loyalty.controllers.cart import Cart
from odoo.addons.website_sale_loyalty.controllers.main import WebsiteSale
@tagged('post_install', '-at_install')
class WebsiteSaleLoyaltyTestUi(TestSaleProductAttributeValueCommon, HttpCase):
class WebsiteSaleLoyaltyTestUi(TestSaleCommon, HttpCase):
@classmethod
def setUpClass(cls):
@ -21,6 +23,7 @@ class WebsiteSaleLoyaltyTestUi(TestSaleProductAttributeValueCommon, HttpCase):
'company_id': cls.env.company.id,
'company_ids': [(4, cls.env.company.id)],
'name': 'Mitchell Admin',
'email': 'mitchell.admin@example.com',
'street': '215 Vine St',
'phone': '+1 555-555-5555',
'city': 'Scranton',
@ -30,16 +33,7 @@ class WebsiteSaleLoyaltyTestUi(TestSaleProductAttributeValueCommon, HttpCase):
})
cls.env.ref('base.user_admin').sudo().partner_id.company_id = cls.env.company
cls.env.ref('website.default_website').company_id = cls.env.company
# set currency to not rely on demo data and avoid possible race condition
cls.currency_ratio = 1.0
pricelist = cls.env.ref('product.list0')
new_currency = cls._setup_currency(cls.currency_ratio)
pricelist.currency_id = new_currency
cls.env.user.partner_id.write({
'property_product_pricelist': pricelist.id,
})
(cls.env['product.pricelist'].search([]) - pricelist).write({'active': False})
cls.env.flush_all()
cls.public_category = cls.env['product.public.category'].create({'name': 'Public Category'})
def test_01_admin_shop_sale_loyalty_tour(self):
if self.env['ir.module.module']._get('payment_custom').state != 'installed':
@ -53,16 +47,13 @@ class WebsiteSaleLoyaltyTestUi(TestSaleProductAttributeValueCommon, HttpCase):
})
transfer_provider._transfer_ensure_pending_msg_is_set()
# pre enable "Show # found" option to avoid race condition...
public_category = self.env['product.public.category'].create({'name': 'Public Category'})
large_cabinet = self.env['product.product'].create({
'name': 'Small Cabinet',
'list_price': 320.0,
'type': 'consu',
'is_published': True,
'sale_ok': True,
'public_categ_ids': [(4, public_category.id)],
'public_categ_ids': [(4, self.public_category.id)],
'taxes_id': False,
})
@ -74,7 +65,6 @@ class WebsiteSaleLoyaltyTestUi(TestSaleProductAttributeValueCommon, HttpCase):
'purchase_ok': False,
'invoice_policy': 'order',
'default_code': 'FREELARGECABINET',
'categ_id': self.env.ref('product.product_category_all').id,
'taxes_id': False,
})
@ -86,7 +76,6 @@ class WebsiteSaleLoyaltyTestUi(TestSaleProductAttributeValueCommon, HttpCase):
'purchase_ok': False,
'invoice_policy': 'order',
'default_code': '10PERCENTDISC',
'categ_id': self.env.ref('product.product_category_all').id,
'taxes_id': False,
})
@ -151,16 +140,13 @@ class WebsiteSaleLoyaltyTestUi(TestSaleProductAttributeValueCommon, HttpCase):
self.start_tour("/", 'shop_sale_loyalty', login="admin")
def test_02_admin_shop_gift_card_tour(self):
# pre enable "Show # found" option to avoid race condition...
public_category = self.env['product.public.category'].create({'name': 'Public Category'})
gift_card = self.env['product.product'].create({
'name': 'TEST - Gift Card',
'list_price': 50,
'type': 'service',
'is_published': True,
'sale_ok': True,
'public_categ_ids': [(4, public_category.id)],
'public_categ_ids': [(4, self.public_category.id)],
'taxes_id': False,
})
self.env['product.product'].create({
@ -169,7 +155,7 @@ class WebsiteSaleLoyaltyTestUi(TestSaleProductAttributeValueCommon, HttpCase):
'type': 'consu',
'is_published': True,
'sale_ok': True,
'public_categ_ids': [(4, public_category.id)],
'public_categ_ids': [(4, self.public_category.id)],
'taxes_id': False,
})
# Disable any other program
@ -225,13 +211,44 @@ class WebsiteSaleLoyaltyTestUi(TestSaleProductAttributeValueCommon, HttpCase):
self.assertEqual(len(gift_card_program.coupon_ids), 2, 'There should be two coupons, one with points, one without')
self.assertEqual(len(gift_card_program.coupon_ids.filtered('points')), 1, 'There should be two coupons, one with points, one without')
def test_03_admin_shop_ewallet_tour(self):
self.env['product.product'].create({
'name': "TEST - Gift Card",
'list_price': 50,
'type': 'service',
'is_published': True,
'sale_ok': True,
'public_categ_ids': [(4, self.public_category.id)],
'taxes_id': False,
})
# Disable any other program
self.env['loyalty.program'].search([]).write({'active': False})
ewallet_programs = self.env['loyalty.program'].create([{
'name': f"ewallet - test - {ecommerce_ok=}",
'applies_on': 'future',
'trigger': 'auto',
'program_type': 'ewallet',
'ecommerce_ok': ecommerce_ok,
'reward_ids': [Command.create({
'reward_type': 'discount',
'discount_mode': 'per_point',
'discount': 1,
})],
} for ecommerce_ok in (True, False)])
self.env['loyalty.card'].create([{
'partner_id': self.env.ref('base.partner_admin').id,
'program_id': program_id,
'points': 1000,
} for program_id in ewallet_programs.ids])
self.start_tour('/', 'shop_sale_ewallet', login='admin')
@tagged('post_install', '-at_install')
class TestWebsiteSaleCoupon(TransactionCase):
class TestWebsiteSaleCoupon(HttpCase, WebsiteSaleCommon):
@classmethod
def setUpClass(cls):
super(TestWebsiteSaleCoupon, cls).setUpClass()
super().setUpClass()
program = cls.env['loyalty.program'].create({
'name': '10% TEST Discount',
'trigger': 'with_code',
@ -250,14 +267,6 @@ class TestWebsiteSaleCoupon(TransactionCase):
}).generate_coupons()
cls.coupon = program.coupon_ids[0]
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
})
def _apply_promo_code(self, order, code, no_reward_fail=True):
status = order._try_apply_code(code)
if 'error' in status:
@ -277,18 +286,17 @@ class TestWebsiteSaleCoupon(TransactionCase):
def test_01_gc_coupon(self):
# 1. Simulate a frontend order (website, product)
order = self.empty_order
order.website_id = self.env['website'].browse(1)
self.env['sale.order.line'].create({
'product_id': self.env['product.product'].create({
'name': 'Product A',
'list_price': 100,
'sale_ok': True,
}).id,
'name': 'Product A',
'product_uom_qty': 2.0,
'order_id': order.id,
})
order = self.empty_cart
order.order_line = [
Command.create({
'product_id': self.env['product.product'].create({
'name': 'Product A',
'list_price': 100,
'sale_ok': True,
}).id,
'product_uom_qty': 2.0,
})
]
# 2. Apply the coupon
self._apply_promo_code(order, self.coupon.code)
@ -306,7 +314,7 @@ class TestWebsiteSaleCoupon(TransactionCase):
icp_validity = ICP.create({'key': 'website_sale_coupon.abandonned_coupon_validity', 'value': 5})
self.env.flush_all()
query = """UPDATE %s SET write_date = %%s WHERE id = %%s""" % (order._table,)
self.env.cr.execute(query, (fields.Datetime.to_string(fields.datetime.now() - timedelta(days=4, hours=2)), order.id))
self.env.cr.execute(query, (fields.Datetime.to_string(fields.Datetime.now() - timedelta(days=4, hours=2)), order.id))
order._gc_abandoned_coupons()
self.assertEqual(len(order.applied_coupon_ids), 1, "The coupon shouldn't have been removed from the order the order is 4 days old but icp validity is 5 days")
@ -317,17 +325,52 @@ class TestWebsiteSaleCoupon(TransactionCase):
self.assertEqual(len(order.applied_coupon_ids), 0, "The coupon should've been removed from the order as more than 4 days")
def test_02_remove_coupon(self):
# 1. Simulate a frontend order (website, product)
order = self.empty_order
order.website_id = self.env['website'].browse(1)
self.env['sale.order.line'].create({
'product_id': self.env['product.product'].create({
'name': 'Product A', 'list_price': 100, 'sale_ok': True
}).id,
'name': 'Product A',
'order_id': order.id,
def test_02_apply_discount_code_program_multi_rewards(self):
"""
Check the triggering of a promotion program based on a promo code with multiple rewards
"""
self.env['loyalty.program'].search([]).write({'active': False})
chair = self.env['product.product'].create({
'name': 'Super Chair', 'list_price': 1000, 'website_published': True
})
self.discount_code_program_multi_rewards = self.env['loyalty.program'].create({
'name': 'Discount code program',
'program_type': 'promo_code',
'applies_on': 'current',
'trigger': 'with_code',
'rule_ids': [(0, 0, {
'code': '12345',
'reward_point_amount': 1,
'reward_point_mode': 'order',
})],
'reward_ids': [
(0, 0, {
'reward_type': 'discount',
'discount': 10,
'discount_applicability': 'specific',
'required_points': 1,
'discount_product_ids': chair,
}),
(0, 0, {
'reward_type': 'discount',
'discount': 50,
'discount_applicability': 'order',
'required_points': 1,
}),
],
})
self.start_tour('/', 'apply_discount_code_program_multi_rewards', login='admin')
def test_03_remove_coupon(self):
# 1. Simulate a frontend order (website, product)
order = self.empty_cart
order.order_line = [
Command.create({
'product_id': self.env['product.product'].create({
'name': 'Product A', 'list_price': 100, 'sale_ok': True
}).id,
})
]
# 2. Apply the coupon
self._apply_promo_code(order, self.coupon.code)
@ -337,11 +380,58 @@ class TestWebsiteSaleCoupon(TransactionCase):
lambda l: l.coupon_id and l.coupon_id.id == self.coupon.id
)
order._cart_update(coupon_line.product_id.id, add_qty=None)
website = self.website
with MockRequest(website.env, website=self.website, sale_order_id=order.id):
Cart().update_cart(
line_id=None, quantity=0.0, product_id=coupon_line.product_id.id,
)
msg = "The coupon should've been removed from the order"
self.assertEqual(len(order.applied_coupon_ids), 0, msg=msg)
def test_04_apply_coupon_code_twice(self):
"""This test ensures that applying a coupon with code twice will:
1. Raise an error
2. Not delete the coupon
"""
# Create product
product = self.env['product.product'].create({
'name': 'Product',
'list_price': 100,
'sale_ok': True,
'taxes_id': [],
})
order = self.empty_cart
order.order_line = [Command.create({'product_id': product.id})]
WebsiteSaleController = WebsiteSale()
installed_modules = set(self.env['ir.module.module'].search([
('state', '=', 'installed'),
]).mapped('name'))
for _ in http._generate_routing_rules(installed_modules, nodb_only=False):
pass
with MockRequest(self.env, website=self.website, sale_order_id=order.id) as request:
# Check the base cart value
self.assertEqual(order.amount_total, 100.0, "The base cart value is incorrect.")
# Apply coupon for the first time
WebsiteSaleController.pricelist(promo=self.coupon.code)
# Check that the coupon has been applied
self.assertEqual(order.amount_total, 90.0, "The coupon is not applied.")
# Apply the coupon again
WebsiteSaleController.pricelist(promo=self.coupon.code)
Cart().cart()
error_msg = request.session.get('error_promo_code')
# Check that the coupon stay applied
self.assertEqual(bool(error_msg), True, "Apply a coupon twice should display an error message")
self.assertEqual(order.amount_total, 90.0, "Apply a coupon twice shouldn't delete it")
def test_03_remove_coupon_with_different_taxes_on_products(self):
"""
Tests the removal of a coupon from an order containing products with various tax rates,
@ -394,11 +484,8 @@ class TestWebsiteSaleCoupon(TransactionCase):
} for name, taxes_id in products_data]
)
order = self.empty_order
order.write({
'website_id': self.env['website'].browse(1),
'order_line': [Command.create({'product_id': product.id}) for product in products],
})
order = self.empty_cart
order.order_line = [Command.create({'product_id': product.id}) for product in products]
msg = "There should only be 4 lines for the 4 products."
self.assertEqual(len(order.order_line), 4, msg=msg)
@ -416,12 +503,11 @@ class TestWebsiteSaleCoupon(TransactionCase):
coupon_line = order.website_order_line.filtered(
lambda line: line.coupon_id and line.coupon_id.id == self.coupon.id
)
order._cart_update(
product_id=coupon_line.product_id.id,
line_id=None,
add_qty=None,
set_qty=0,
)
website = self.website
with MockRequest(website.env, website=self.website, sale_order_id=order.id):
Cart().update_cart(
line_id=None, quantity=0.0, product_id=coupon_line.product_id.id,
)
msg = "All coupon lines should have been removed from the order."
self.assertEqual(len(order.applied_coupon_ids), 0, msg=msg)

View file

@ -0,0 +1,45 @@
# 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.website_sale.controllers.main import WebsiteSale
from odoo.addons.website_sale.tests.common import MockRequest, WebsiteSaleCommon
@tagged('post_install', '-at_install')
class TestWebsiteSaleAutoInvoice(WebsiteSaleCommon):
def test_automatic_invoice_on_zero_amount(self):
# Set automatic invoice
self.env['ir.config_parameter'].sudo().set_param('sale.automatic_invoice', 'True')
Controller = WebsiteSale()
# Create a discount code
program = self.env['loyalty.program'].sudo().create({
'name': '100discount',
'program_type': 'promo_code',
'rule_ids': [
Command.create({
'code': "100code",
'minimum_amount': 0,
})
],
'reward_ids': [
Command.create({
'discount': 100,
})
]
})
self.cart.carrier_id = self.free_delivery
# Apply discount
self.cart._try_apply_code("100code")
self.cart._apply_program_reward(program.reward_ids, program.coupon_ids)
with MockRequest(self.env, website=self.website, sale_order_id=self.cart.id):
Controller.shop_payment_validate()
self.assertTrue(
self.cart.invoice_ids, "Invoices should be generated for orders with zero total amount",
)

View file

@ -0,0 +1,269 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.fields import Command
from odoo.tests import HttpCase, tagged
from odoo.exceptions import ValidationError
from odoo.addons.base.tests.common import DISABLED_MAIL_CONTEXT
from odoo.addons.website_sale.tests.common import MockRequest, WebsiteSaleCommon
from odoo.addons.payment import utils as payment_utils
from odoo.addons.website_sale_loyalty.controllers.cart import Cart
from odoo.addons.website_sale_loyalty.controllers.delivery import WebsiteSaleLoyaltyDelivery
@tagged('post_install', '-at_install')
class TestWebsiteSaleDelivery(HttpCase, WebsiteSaleCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.Controller = WebsiteSaleLoyaltyDelivery()
# Disable mail logic
cls.env = cls.env['base'].with_context(**DISABLED_MAIL_CONTEXT).env
# Disable existing pricelists
cls.env['product.pricelist'].with_context(active_test=False).search([]).unlink()
# Disable existing reward programs
cls.env['loyalty.program'].search([]).active = False
# Remove taxes completely during the following tests.
cls.env.companies.account_sale_tax_id = False
cls.user_admin = cls.env.ref('base.user_admin')
cls.partner_admin = cls.user_admin.partner_id
cls.partner_admin.write(cls.dummy_partner_address_values)
cls.website2 = cls.env['website'].create({'name': 'website 2'})
cls.product_plumbus = cls.env['product.product'].create({
'name': "Plumbus",
'list_price': 100.0,
'type': 'consu',
'website_published': True,
})
cls.product_gift_card = cls.env['product.product'].create({
'name': 'TEST - Gift Card',
'list_price': 50,
'type': 'service',
'is_published': True,
})
gift_card_program = cls.env['loyalty.program'].create({
'name': 'Gift Cards',
'program_type': 'gift_card',
'applies_on': 'future',
'trigger': 'auto',
'rule_ids': [Command.create({
'reward_point_amount': 1,
'reward_point_mode': 'money',
'reward_point_split': True,
'product_ids': cls.product_gift_card.ids,
})],
'reward_ids': [Command.create({
'reward_type': 'discount',
'discount_mode': 'per_point',
'discount': 1,
'discount_applicability': 'order',
'required_points': 1,
'description': 'PAY WITH GIFT CARD',
})],
})
# Create a gift card to be used
cls.gift_card = cls.env['loyalty.card'].create({
'program_id': gift_card_program.id,
'points': 50000,
'code': '123456',
})
# Create a 50% discount on order code
cls.promo_discount_code = cls.env['loyalty.program'].create({
'name': "50% discount code",
'program_type': 'promo_code',
'trigger': 'with_code',
'applies_on': 'current',
'rule_ids': [Command.create({
'code': "test-50pc",
})],
'reward_ids': [Command.create({
'reward_type': 'discount',
'discount': 50.0,
'discount_mode': 'percent',
'discount_applicability': 'order',
})],
})
ewallet_program = cls.env['loyalty.program'].create({
'name': "eWallet",
'program_type': 'ewallet',
'applies_on': 'future',
'trigger': 'auto',
'reward_ids': [Command.create({
'description': "Pay with eWallet",
'reward_type': 'discount',
'discount_mode': 'per_point',
'discount': 1,
})],
})
cls.ewallet = cls.env['loyalty.card'].create({
'program_id': ewallet_program.id,
'partner_id': cls.partner_admin.id,
'points': 1000000,
})
delivery_product1, delivery_product2 = cls.env['product.product'].create([{
'name': "Delivery 1",
'invoice_policy': 'order',
'type': 'service',
}, {
'name': "Delivery 2",
'invoice_policy': 'order',
'type': 'service',
}])
cls.normal_delivery, cls.normal_delivery2 = cls.env['delivery.carrier'].create([{
'name': 'delivery1',
'fixed_price': 5,
'delivery_type': 'fixed',
'website_published': True,
'product_id': delivery_product1.id,
}, {
'name': 'delivery2',
'fixed_price': 10,
'delivery_type': 'fixed',
'website_published': True,
'product_id': delivery_product2.id,
}])
def create_program_with_code(self, code, website_id):
return self.env['loyalty.program'].create({
'name': "Discount delivery",
'program_type': 'promo_code',
'website_id': website_id.id,
'rule_ids': [Command.create({
'code': code,
'minimum_amount': 0,
})],
})
def test_shop_sale_gift_card_keep_delivery(self):
# Get admin user and set his preferred shipping method to normal delivery
# This test also tests that we can indeed pay delivery fees with gift cards/ewallet
self.partner_admin.property_delivery_carrier_id = self.normal_delivery
self.start_tour("/", 'shop_sale_loyalty_delivery', login='admin')
def test_shipping_discount(self):
"""
Check display of shipping discount promotion on checkout,
combined with another reward (eWallet).
"""
self.env['loyalty.program'].create({
'name': "Buy 3, get up to $6 discount on shipping!",
'program_type': 'promotion',
'applies_on': 'current',
'trigger': 'auto',
'rule_ids': [Command.create({
'minimum_qty': 3.0,
})],
'reward_ids': [Command.create({
'reward_type': 'shipping',
'discount_max_amount': 6.0,
})],
})
self.start_tour("/", 'check_shipping_discount', login="admin")
def test_update_shipping_after_discount(self):
"""
Verify that after applying a discount code, any `free_over` shipping gets recalculated.
"""
self.free_delivery.action_archive()
self.normal_delivery.write({'free_over': True, 'amount': 75.0})
self.start_tour("/shop", 'update_shipping_after_discount', login=self.user_admin.login)
def test_express_checkout_shipping_discount(self):
"""
Check display of shipping discount promotion in express checkout form by ensuring is present
in the values returned to the form.
"""
# Create a discount code
program = self.env['loyalty.program'].sudo().create({
'name': 'Free Shipping',
'program_type': 'promo_code',
'rule_ids': [
Command.create({
'code': "FREE",
'minimum_amount': 0,
})
],
'reward_ids': [
Command.create({
'reward_type': 'shipping',
'discount_max_amount': 6.0,
})
]
})
# Apply discount
self.cart._try_apply_code("FREE")
self.cart._apply_program_reward(program.reward_ids, program.coupon_ids)
with MockRequest(self.env, sale_order_id=self.cart.id, website=self.website):
result = self.Controller.shop_set_delivery_method(self.normal_delivery2.id)
self.assertEqual(result['delivery_discount_minor_amount'], -600)
def test_express_checkout_does_not_count_delivery_discount_in_payment_values(self):
"""Test that the amount to pay does not include the free delivery amount in express
checkout."""
program = self.env['loyalty.program'].sudo().create({
'name': 'Discount delivery',
'program_type': 'promo_code',
'rule_ids': [Command.create({
'code': "FREE",
'minimum_amount': 0,
})],
'reward_ids': [Command.create({
'reward_type': 'shipping',
'discount_max_amount': 2.0,
})]
})
amount_without_delivery = payment_utils.to_minor_currency_units(
self.cart.amount_total, self.cart.currency_id
)
self.cart.set_delivery_line(self.normal_delivery, self.normal_delivery.fixed_price)
self.cart._try_apply_code("FREE")
self.cart._apply_program_reward(program.reward_ids, program.coupon_ids)
with MockRequest(self.env, sale_order_id=self.cart.id, website=self.website):
payment_values = Cart()._get_express_shop_payment_values(self.cart)
self.assertEqual(payment_values['minor_amount'], amount_without_delivery)
def test_prevent_unarchive_when_conflicting_active_program_exists_on_same_website(self):
"""Unarchiving a program should fail if another active program already has the same
rule code on the same website."""
program = self.create_program_with_code("FREE", self.website)
program.action_archive()
self.create_program_with_code("FREE", self.website)
with self.assertRaises(ValidationError):
program.action_unarchive()
def test_unarchive_when_conflicting_active_program_exists_on_different_website(self):
"""Unarchiving a program should succeed when another active program already has the
same rule code on a different website."""
program = self.create_program_with_code("FREE", self.website)
program.action_archive()
self.create_program_with_code("FREE", self.website2)
program.action_unarchive()
def test_prevent_unarchive_when_batch_contains_duplicate_codes_on_same_website(self):
"""Unarchiving multiple programs at once should fail if they share the same rule code
on the same website."""
program1 = self.create_program_with_code("FREE", self.website)
program1.action_archive()
program2 = self.create_program_with_code("FREE", self.website)
program2.action_archive()
with self.assertRaises(ValidationError):
(program1 + program2).action_unarchive()