mirror of
https://github.com/bringout/oca-ocb-sale.git
synced 2026-04-27 17:52:01 +02:00
19.0 vanilla
This commit is contained in:
parent
79f83631d5
commit
73afc09215
6267 changed files with 1534193 additions and 1130106 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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%"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
@ -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",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
)
|
||||
|
|
@ -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()
|
||||
Loading…
Add table
Add a link
Reference in a new issue