mirror of
https://github.com/bringout/oca-ocb-sale.git
synced 2026-04-27 18:12:04 +02:00
Initial commit: Sale packages
This commit is contained in:
commit
14e3d26998
6469 changed files with 2479670 additions and 0 deletions
|
|
@ -0,0 +1,8 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import test_apply_pending_coupon
|
||||
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
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import http
|
||||
from odoo.tests import tagged, HttpCase
|
||||
|
||||
from odoo.addons.sale_loyalty.tests.common import TestSaleCouponNumbersCommon
|
||||
from odoo.addons.website.tools import MockRequest
|
||||
from odoo.addons.website_sale_loyalty.controllers.main import WebsiteSale
|
||||
|
||||
|
||||
@tagged('-at_install', 'post_install')
|
||||
class TestSaleCouponApplyPending(HttpCase, TestSaleCouponNumbersCommon):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
self.WebsiteSaleController = WebsiteSale()
|
||||
|
||||
self.website = self.env['website'].browse(1)
|
||||
self.global_program = self.p1
|
||||
self.coupon_program = self.env['loyalty.program'].create({
|
||||
'name': 'One Free Product',
|
||||
'program_type': 'coupons',
|
||||
'rule_ids': [(0, 0, {
|
||||
'minimum_qty': 2,
|
||||
})],
|
||||
'reward_ids': [(0, 0, {
|
||||
'reward_type': 'product',
|
||||
'reward_product_id': self.largeCabinet.id,
|
||||
})]
|
||||
})
|
||||
self.env['loyalty.generate.wizard'].with_context(active_id=self.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([
|
||||
('state', '=', 'installed'),
|
||||
]).mapped('name'))
|
||||
for _ in http._generate_routing_rules(installed_modules, nodb_only=False):
|
||||
pass
|
||||
|
||||
def test_01_activate_coupon_with_existing_program(self):
|
||||
order = self.empty_order
|
||||
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)
|
||||
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.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")
|
||||
|
||||
def test_02_pending_coupon_with_existing_program(self):
|
||||
order = self.empty_order
|
||||
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)
|
||||
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)
|
||||
promo_sol = (order.order_line - cabinet_sol)
|
||||
self.assertTrue(cabinet_sol)
|
||||
self.assertEqual(cabinet_sol.price_unit, 320)
|
||||
self.assertEqual(cabinet_sol.price_total, 320)
|
||||
self.assertEqual(promo_sol.price_total, -32)
|
||||
self.assertEqual(order.amount_tax, 0)
|
||||
self.assertEqual(order.cart_quantity, 1)
|
||||
self.assertEqual(order.amount_total, 288, "The order total should equal 288: 320 - 10%")
|
||||
|
||||
self.WebsiteSaleController.activate_coupon(self.coupon.code)
|
||||
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.WebsiteSaleController.cart_update_json(self.largeCabinet.id, add_qty=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.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%")
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import http
|
||||
from odoo.tests.common import HttpCase
|
||||
from odoo.tests import tagged
|
||||
from odoo.addons.website.tools import MockRequest
|
||||
from odoo.addons.website_sale_loyalty.controllers.main import WebsiteSale
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestFreeProductReward(HttpCase):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
self.WebsiteSaleController = WebsiteSale()
|
||||
self.website = self.env['website'].browse(1)
|
||||
|
||||
self.sofa = self.env['product.product'].create({
|
||||
'name': 'Test Sofa',
|
||||
'list_price': 2950.0,
|
||||
'website_published': True,
|
||||
})
|
||||
|
||||
self.carpet = self.env['product.product'].create({
|
||||
'name': 'Test Carpet',
|
||||
'list_price': 500.0,
|
||||
'website_published': True,
|
||||
})
|
||||
|
||||
# Disable any other program
|
||||
self.program = self.env['loyalty.program'].search([]).write({'active': False})
|
||||
|
||||
self.program = self.env['loyalty.program'].create({
|
||||
'name': 'Get a product for free',
|
||||
'program_type': 'promotion',
|
||||
'applies_on': 'current',
|
||||
'trigger': 'auto',
|
||||
'rule_ids': [(0, 0, {
|
||||
'minimum_qty': 1,
|
||||
'minimum_amount': 0.00,
|
||||
'reward_point_amount': 1,
|
||||
'reward_point_mode': 'order',
|
||||
'product_ids': self.sofa,
|
||||
})],
|
||||
'reward_ids': [(0, 0, {
|
||||
'reward_type': 'product',
|
||||
'reward_product_id': self.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):
|
||||
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)
|
||||
self.WebsiteSaleController.claim_reward(self.program.reward_ids[0].id)
|
||||
self.WebsiteSaleController.cart_update_json(self.carpet.id, set_qty=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")
|
||||
|
|
@ -0,0 +1,172 @@
|
|||
# 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
|
||||
|
||||
|
||||
@tagged('-at_install', 'post_install')
|
||||
class TestSaleCouponMultiwebsite(TestSaleCouponNumbersCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.website = cls.env['website'].browse(1)
|
||||
cls.website2 = cls.env['website'].create({'name': 'website 2'})
|
||||
|
||||
def test_01_multiwebsite_checks(self):
|
||||
""" Ensure the multi website compliance of programs and coupons, both in
|
||||
backend and frontend.
|
||||
"""
|
||||
order = self.empty_order
|
||||
self.env['sale.order.line'].create({
|
||||
'product_id': self.largeCabinet.id,
|
||||
'name': 'Large Cabinet',
|
||||
'product_uom_qty': 2.0,
|
||||
'order_id': order.id,
|
||||
})
|
||||
|
||||
def _remove_reward():
|
||||
order.order_line.filtered('is_reward_line').unlink()
|
||||
self.assertEqual(len(order.order_line.ids), 1, "Program should have been removed")
|
||||
|
||||
def _apply_code(code, backend=True):
|
||||
try:
|
||||
self._apply_promo_code(order, code)
|
||||
except UserError as e:
|
||||
if backend:
|
||||
raise e
|
||||
|
||||
# ==========================================
|
||||
# ========== Programs (with code) ==========
|
||||
# ==========================================
|
||||
|
||||
# 1. Backend - Generic
|
||||
_apply_code(self.p1.rule_ids.code)
|
||||
self.assertEqual(len(order.order_line.ids), 2, "Should get the discount line as it is a generic promo program")
|
||||
_remove_reward()
|
||||
|
||||
# 2. Frontend - Generic
|
||||
with MockRequest(self.env, website=self.website):
|
||||
_apply_code(self.p1.rule_ids.code, False)
|
||||
self.assertEqual(len(order.order_line.ids), 2, "Should get the discount line as it is a generic promo program (2)")
|
||||
_remove_reward()
|
||||
|
||||
# make program specific
|
||||
self.p1.website_id = self.website.id
|
||||
# 3. Backend - Specific - sale_ok disabled
|
||||
self.p1.sale_ok = False
|
||||
with self.assertRaises(UserError):
|
||||
_apply_code(self.p1.rule_ids.code) # the program is not enabled for Sales (backend)
|
||||
|
||||
# 3.5. Backend - Specific - sale_ok enabled
|
||||
self.p1.sale_ok = True
|
||||
_apply_code(self.p1.rule_ids.code)
|
||||
self.assertEqual(len(order.order_line.ids), 2, "Should get the discount line as it is enabled for Sales(backend)")
|
||||
_remove_reward()
|
||||
|
||||
# 4. Frontend - Specific - Correct website
|
||||
order.website_id = self.website.id
|
||||
with MockRequest(self.env, website=self.website):
|
||||
_apply_code(self.p1.rule_ids.code, False)
|
||||
self.assertEqual(len(order.order_line.ids), 2, "Should get the discount line as it is a specific promo program for the correct website")
|
||||
_remove_reward()
|
||||
|
||||
# 5. Frontend - Specific - Wrong website
|
||||
self.p1.website_id = self.website2.id
|
||||
with MockRequest(self.env, website=self.website):
|
||||
_apply_code(self.p1.rule_ids.code, False)
|
||||
self.assertEqual(len(order.order_line.ids), 1, "Should not get the reward as wrong website")
|
||||
|
||||
# ==============================
|
||||
# =========== Coupons ==========
|
||||
# ==============================
|
||||
|
||||
order.website_id = False
|
||||
self.env['loyalty.generate.wizard'].with_context(active_id=self.discount_coupon_program.id).create({
|
||||
'coupon_qty': 4,
|
||||
'points_granted': 1,
|
||||
}).generate_coupons()
|
||||
coupons = self.discount_coupon_program.coupon_ids
|
||||
|
||||
# 1. Backend - Generic
|
||||
_apply_code(coupons[0].code)
|
||||
self.assertEqual(len(order.order_line.ids), 2, "Should get the discount line as it is a generic coupon program")
|
||||
_remove_reward()
|
||||
|
||||
# 2. Frontend - Generic
|
||||
with MockRequest(self.env, website=self.website):
|
||||
_apply_code(coupons[1].code, False)
|
||||
self.assertEqual(len(order.order_line.ids), 2, "Should get the discount line as it is a generic coupon program (2)")
|
||||
_remove_reward()
|
||||
|
||||
# make program specific
|
||||
self.discount_coupon_program.website_id = self.website.id
|
||||
# 3. Backend - Specific - sale_ok disabled
|
||||
self.discount_coupon_program.sale_ok = False
|
||||
with self.assertRaises(UserError):
|
||||
_apply_code(coupons[2].code) # the program is not enabled for Sales (backend)
|
||||
|
||||
# 3.5. Backend - Specific - sale_ok enabled
|
||||
self.discount_coupon_program.sale_ok = True
|
||||
_apply_code(coupons[2].code)
|
||||
self.assertEqual(len(order.order_line.ids), 2, "Should get the discount line as it is enabled for Sales(backend)")
|
||||
_remove_reward()
|
||||
|
||||
# 4. Frontend - Specific - Correct website
|
||||
order.website_id = self.website.id
|
||||
with MockRequest(self.env, website=self.website):
|
||||
_apply_code(coupons[2].code, False)
|
||||
self.assertEqual(len(order.order_line.ids), 2, "Should get the discount line as it is a specific coupon program for the correct website")
|
||||
_remove_reward()
|
||||
|
||||
# 5. Frontend - Specific - Wrong website
|
||||
self.discount_coupon_program.website_id = self.website2.id
|
||||
with MockRequest(self.env, website=self.website):
|
||||
_apply_code(coupons[3].code, False)
|
||||
self.assertEqual(len(order.order_line.ids), 1, "Should not get the reward as wrong website")
|
||||
|
||||
# ========================================
|
||||
# ========== Programs (no code) ==========
|
||||
# ========================================
|
||||
|
||||
order.website_id = False
|
||||
self.p1.website_id = False
|
||||
self.p1.rule_ids.code = False
|
||||
self.p1.trigger = 'auto'
|
||||
self.p1.rule_ids.mode = 'auto'
|
||||
|
||||
# 1. Backend - Generic
|
||||
all_programs = self.env['loyalty.program'].search([])
|
||||
self._auto_rewards(order, all_programs)
|
||||
self.assertEqual(len(order.order_line.ids), 2, "Should get the discount line as it is a generic promo program")
|
||||
|
||||
# 2. Frontend - Generic
|
||||
with MockRequest(self.env, website=self.website):
|
||||
self._auto_rewards(order, all_programs)
|
||||
self.assertEqual(len(order.order_line.ids), 2, "Should get the discount line as it is a generic promo program (2)")
|
||||
|
||||
# make program specific
|
||||
self.p1.website_id = self.website.id
|
||||
# 3. Backend - Specific
|
||||
self.p1.sale_ok = False
|
||||
self._auto_rewards(order, all_programs)
|
||||
self.assertEqual(len(order.order_line.ids), 1, "The program is not enabled for Sales (backend)")
|
||||
|
||||
# 3.5. Backend - Specific - sale_ok enabled
|
||||
self.p1.sale_ok = True
|
||||
self._auto_rewards(order, all_programs)
|
||||
self.assertEqual(len(order.order_line.ids), 2, "Should get the discount line as it is a generic promo program")
|
||||
|
||||
# 4. Frontend - Specific - Correct website
|
||||
order.website_id = self.website.id
|
||||
with MockRequest(self.env, website=self.website):
|
||||
self._auto_rewards(order, all_programs)
|
||||
self.assertEqual(len(order.order_line.ids), 2, "Should get the discount line as it is a specific promo program for the correct website")
|
||||
|
||||
# 5. Frontend - Specific - Wrong website
|
||||
self.p1.website_id = self.website2.id
|
||||
with MockRequest(self.env, website=self.website):
|
||||
self._auto_rewards(order, all_programs)
|
||||
self.assertEqual(len(order.order_line.ids), 1, "Should not get the reward as wrong website")
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from datetime import date, timedelta
|
||||
from freezegun import freeze_time
|
||||
|
||||
from odoo import Command
|
||||
from odoo.tests import tagged
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
from odoo.addons.payment.tests.http_common import PaymentHttpCommon
|
||||
from odoo.addons.sale_loyalty.tests.common import TestSaleCouponCommon
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestShopLoyaltyTransaction(PaymentHttpCommon, TestSaleCouponCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
cls.website = cls.env.company.website_id
|
||||
if not cls.website:
|
||||
cls.website = cls.env.ref('website.default_website')
|
||||
cls.website.company_id = cls.env.company
|
||||
|
||||
@mute_logger('odoo.http')
|
||||
def test_expired_reward_validation(self):
|
||||
"""Ensure payments don't process if any applied reward is no longer valid."""
|
||||
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
|
||||
|
||||
self.env['loyalty.generate.wizard'].with_context(active_id=program.id).create({
|
||||
'coupon_qty': 1,
|
||||
'points_granted': 100,
|
||||
}).generate_coupons()
|
||||
|
||||
order.write({
|
||||
'partner_id': self.portal_partner.id,
|
||||
'website_id': self.website.id,
|
||||
'message_partner_ids': self.portal_partner.ids,
|
||||
'order_line': [Command.create({
|
||||
'product_id': self.product_a.id,
|
||||
})],
|
||||
})
|
||||
self._apply_promo_code(order, program.coupon_ids.code)
|
||||
|
||||
with freeze_time(program.date_to + timedelta(days=1)):
|
||||
self.authenticate(self.portal_user.login, self.portal_user.login)
|
||||
tx_response = self._make_json_rpc_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,
|
||||
'flow': 'direct',
|
||||
'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.",
|
||||
)
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
# 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.addons.website.tools import MockRequest
|
||||
from odoo.addons.website_sale_loyalty.controllers.main import WebsiteSale
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestClaimReward(TransactionCase):
|
||||
|
||||
def test_claim_reward_with_multi_product(self):
|
||||
WebsiteSaleController = WebsiteSale()
|
||||
|
||||
tag = self.env['product.tag'].create({
|
||||
'name': 'multi reward',
|
||||
})
|
||||
|
||||
product1, product2 = self.env['product.product'].create([
|
||||
{
|
||||
'name': 'Test Product',
|
||||
'list_price': 10.0,
|
||||
'product_tag_ids': tag,
|
||||
}, {
|
||||
'name': 'Test Product 2',
|
||||
'list_price': 20.0,
|
||||
'product_tag_ids': tag,
|
||||
}])
|
||||
|
||||
partner = self.env['res.partner'].create({
|
||||
'name': 'Test Customer',
|
||||
'email': 'test@example.com',
|
||||
})
|
||||
|
||||
promo_program = self.env['loyalty.program'].create({
|
||||
'name': 'Free Products',
|
||||
'program_type': 'promotion',
|
||||
'applies_on': 'current',
|
||||
'trigger': 'auto',
|
||||
'rule_ids': [Command.create({
|
||||
'minimum_qty': 1,
|
||||
'minimum_amount': 0.00,
|
||||
'reward_point_amount': 3,
|
||||
})],
|
||||
'reward_ids': [Command.create({
|
||||
'reward_type': 'product',
|
||||
'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,
|
||||
})],
|
||||
})
|
||||
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))
|
||||
|
||||
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')
|
||||
|
|
@ -0,0 +1,475 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
from datetime import timedelta
|
||||
|
||||
from odoo import fields
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.fields import Command
|
||||
from odoo.tests import HttpCase, TransactionCase, tagged
|
||||
|
||||
from odoo.addons.sale.tests.test_sale_product_attribute_value_config import (
|
||||
TestSaleProductAttributeValueCommon,
|
||||
)
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class WebsiteSaleLoyaltyTestUi(TestSaleProductAttributeValueCommon, HttpCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.env.ref('base.user_admin').write({
|
||||
'company_id': cls.env.company.id,
|
||||
'company_ids': [(4, cls.env.company.id)],
|
||||
'name': 'Mitchell Admin',
|
||||
'street': '215 Vine St',
|
||||
'phone': '+1 555-555-5555',
|
||||
'city': 'Scranton',
|
||||
'zip': '18503',
|
||||
'country_id': cls.env.ref('base.us').id,
|
||||
'state_id': cls.env.ref('base.state_us_39').id,
|
||||
})
|
||||
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()
|
||||
|
||||
def test_01_admin_shop_sale_loyalty_tour(self):
|
||||
if self.env['ir.module.module']._get('payment_custom').state != 'installed':
|
||||
self.skipTest("Transfer provider is not installed")
|
||||
|
||||
transfer_provider = self.env.ref('payment.payment_provider_transfer')
|
||||
transfer_provider.sudo().write({
|
||||
'state': 'enabled',
|
||||
'is_published': True,
|
||||
'company_id': self.env.company.id,
|
||||
})
|
||||
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)],
|
||||
'taxes_id': False,
|
||||
})
|
||||
|
||||
free_large_cabinet = self.env['product.product'].create({
|
||||
'name': 'Free Product - Small Cabinet',
|
||||
'type': 'service',
|
||||
'supplier_taxes_id': False,
|
||||
'sale_ok': False,
|
||||
'purchase_ok': False,
|
||||
'invoice_policy': 'order',
|
||||
'default_code': 'FREELARGECABINET',
|
||||
'categ_id': self.env.ref('product.product_category_all').id,
|
||||
'taxes_id': False,
|
||||
})
|
||||
|
||||
ten_percent = self.env['product.product'].create({
|
||||
'name': '10.0% discount on total amount',
|
||||
'type': 'service',
|
||||
'supplier_taxes_id': False,
|
||||
'sale_ok': False,
|
||||
'purchase_ok': False,
|
||||
'invoice_policy': 'order',
|
||||
'default_code': '10PERCENTDISC',
|
||||
'categ_id': self.env.ref('product.product_category_all').id,
|
||||
'taxes_id': False,
|
||||
})
|
||||
|
||||
self.env['loyalty.program'].search([]).write({'active': False})
|
||||
|
||||
self.env['loyalty.program'].create({
|
||||
'name': 'Buy 4 Small Cabinets, get one for free',
|
||||
'trigger': 'auto',
|
||||
'rule_ids': [(0, 0, {
|
||||
'minimum_qty': 4,
|
||||
'product_ids': large_cabinet,
|
||||
})],
|
||||
'reward_ids': [(0, 0, {
|
||||
'reward_type': 'product',
|
||||
'reward_product_id': large_cabinet.id,
|
||||
'discount_line_product_id': free_large_cabinet.id,
|
||||
})]
|
||||
})
|
||||
|
||||
self.env['loyalty.program'].create({
|
||||
'name': 'Code for 10% on orders',
|
||||
'trigger': 'with_code',
|
||||
'rule_ids': [(0, 0, {
|
||||
'mode': 'with_code',
|
||||
'code': 'testcode',
|
||||
})],
|
||||
'reward_ids': [(0, 0, {
|
||||
'reward_type': 'discount',
|
||||
'discount': 10,
|
||||
'discount_mode': 'percent',
|
||||
'discount_applicability': 'order',
|
||||
'discount_line_product_id': ten_percent.id,
|
||||
})],
|
||||
})
|
||||
|
||||
vip_program = self.env['loyalty.program'].create({
|
||||
'name': 'VIP',
|
||||
'trigger': 'auto',
|
||||
'program_type': 'loyalty',
|
||||
'portal_visible': True,
|
||||
'applies_on': 'both',
|
||||
'rule_ids': [(0, 0, {
|
||||
'mode': 'auto',
|
||||
})],
|
||||
'reward_ids': [(0, 0, {
|
||||
'reward_type': 'discount',
|
||||
'discount': 21,
|
||||
'discount_mode': 'percent',
|
||||
'discount_applicability': 'order',
|
||||
'required_points': 50,
|
||||
})],
|
||||
})
|
||||
|
||||
self.env['loyalty.card'].create({
|
||||
'partner_id': self.env.ref('base.partner_admin').id,
|
||||
'program_id': vip_program.id,
|
||||
'point_name': "Points",
|
||||
'points': 371.03,
|
||||
})
|
||||
|
||||
self.env.ref("website_sale.reduction_code").write({"active": True})
|
||||
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)],
|
||||
'taxes_id': False,
|
||||
})
|
||||
self.env['product.product'].create({
|
||||
'name': 'TEST - Small Drawer',
|
||||
'list_price': 50,
|
||||
'type': 'consu',
|
||||
'is_published': True,
|
||||
'sale_ok': True,
|
||||
'public_categ_ids': [(4, public_category.id)],
|
||||
'taxes_id': False,
|
||||
})
|
||||
# Disable any other program
|
||||
self.env['loyalty.program'].search([]).write({'active': False})
|
||||
|
||||
gift_card_program = self.env['loyalty.program'].create({
|
||||
'name': 'Gift Cards',
|
||||
'program_type': 'gift_card',
|
||||
'applies_on': 'future',
|
||||
'trigger': 'auto',
|
||||
'rule_ids': [(0, 0, {
|
||||
'reward_point_amount': 1,
|
||||
'reward_point_mode': 'money',
|
||||
'reward_point_split': True,
|
||||
'product_ids': gift_card,
|
||||
})],
|
||||
'reward_ids': [(0, 0, {
|
||||
'reward_type': 'discount',
|
||||
'discount_mode': 'per_point',
|
||||
'discount': 1,
|
||||
'discount_applicability': 'order',
|
||||
'required_points': 1,
|
||||
'description': 'PAY WITH GIFT CARD',
|
||||
})],
|
||||
})
|
||||
# Another program for good measure
|
||||
self.env['loyalty.program'].create({
|
||||
'name': '10% Discount',
|
||||
'applies_on': 'current',
|
||||
'trigger': 'with_code',
|
||||
'program_type': 'promotion',
|
||||
'rule_ids': [(0, 0, {
|
||||
'mode': 'with_code',
|
||||
'code': '10PERCENT',
|
||||
})],
|
||||
'reward_ids': [(0, 0, {
|
||||
'reward_type': 'discount',
|
||||
'discount': 10,
|
||||
'discount_mode': 'percent',
|
||||
'discount_applicability': 'order',
|
||||
})],
|
||||
})
|
||||
# Create a gift card to be used
|
||||
self.env['loyalty.card'].create({
|
||||
'program_id': gift_card_program.id,
|
||||
'points': 50,
|
||||
'code': 'GIFT_CARD',
|
||||
})
|
||||
|
||||
self.env.ref("website_sale.reduction_code").write({"active": True})
|
||||
self.start_tour('/', 'shop_sale_gift_card', login='admin')
|
||||
|
||||
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')
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestWebsiteSaleCoupon(TransactionCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestWebsiteSaleCoupon, cls).setUpClass()
|
||||
program = cls.env['loyalty.program'].create({
|
||||
'name': '10% TEST Discount',
|
||||
'trigger': 'with_code',
|
||||
'applies_on': 'current',
|
||||
'rule_ids': [(0, 0, {})],
|
||||
'reward_ids': [(0, 0, {
|
||||
'reward_type': 'discount',
|
||||
'discount': 10,
|
||||
'discount_mode': 'percent',
|
||||
})],
|
||||
})
|
||||
|
||||
cls.env['loyalty.generate.wizard'].with_context(active_id=program.id).create({
|
||||
'coupon_qty': 1,
|
||||
'points_granted': 1
|
||||
}).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:
|
||||
raise ValidationError(status['error'])
|
||||
if not status and no_reward_fail:
|
||||
# Can happen if global discount got filtered out in `_get_claimable_rewards`
|
||||
raise ValidationError('No reward to claim with this coupon')
|
||||
coupons = self.env['loyalty.card']
|
||||
rewards = self.env['loyalty.reward']
|
||||
for coupon, coupon_rewards in status.items():
|
||||
coupons |= coupon
|
||||
rewards |= coupon_rewards
|
||||
if len(coupons) == 1 and len(rewards) == 1:
|
||||
status = order._apply_program_reward(rewards, coupons)
|
||||
if 'error' in status:
|
||||
raise ValidationError(status['error'])
|
||||
|
||||
def 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,
|
||||
})
|
||||
|
||||
# 2. Apply the coupon
|
||||
self._apply_promo_code(order, self.coupon.code)
|
||||
|
||||
self.assertEqual(len(order.applied_coupon_ids), 1, "The coupon should've been applied on the order")
|
||||
self.assertEqual(self.coupon, order.applied_coupon_ids)
|
||||
|
||||
# 3. Test recent order -> Should not be removed
|
||||
order._gc_abandoned_coupons()
|
||||
|
||||
self.assertEqual(len(order.applied_coupon_ids), 1, "The coupon shouldn't have been removed from the order no more than 4 days")
|
||||
|
||||
# 4. Test order not older than ICP validity -> Should not be removed
|
||||
ICP = self.env['ir.config_parameter']
|
||||
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))
|
||||
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")
|
||||
|
||||
# 5. Test order with no ICP and older then 4 default days -> Should be removed
|
||||
icp_validity.unlink()
|
||||
order._gc_abandoned_coupons()
|
||||
|
||||
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,
|
||||
})
|
||||
|
||||
# 2. Apply the coupon
|
||||
self._apply_promo_code(order, self.coupon.code)
|
||||
|
||||
# 3. Remove the coupon
|
||||
coupon_line = order.website_order_line.filtered(
|
||||
lambda l: l.coupon_id and l.coupon_id.id == self.coupon.id
|
||||
)
|
||||
|
||||
order._cart_update(coupon_line.product_id.id, add_qty=None)
|
||||
|
||||
msg = "The coupon should've been removed from the order"
|
||||
self.assertEqual(len(order.applied_coupon_ids), 0, msg=msg)
|
||||
|
||||
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,
|
||||
ensuring that the system correctly handles multiple coupon lines created
|
||||
for each unique tax scenario.
|
||||
|
||||
Background:
|
||||
An order may include products with different tax implications,
|
||||
such as non-taxed products, products with a single tax rate,
|
||||
and products with multiple tax rates. When a coupon is applied,
|
||||
it creates separate coupon lines for each distinct tax situation
|
||||
(non-taxed, individual taxes, and combinations of taxes).
|
||||
This test verifies that the coupon deletion process accurately removes
|
||||
all associated coupon lines, maintaining the financial accuracy of the order.
|
||||
|
||||
Steps:
|
||||
1. Create an order with products subject to different tax scenarios:
|
||||
- Non-taxed product 'Product A'
|
||||
- Product 'Product B' with Tax A
|
||||
- Product 'Product C' with Tax B
|
||||
- Product 'Product D' subject to both Tax A and Tax B
|
||||
2. Apply a coupon, which generates four distinct coupon lines
|
||||
to reflect each tax scenario.
|
||||
3. Remove the coupon and verify that all coupon lines are removed and
|
||||
that no coupons remain applied.
|
||||
"""
|
||||
# Create 2 Taxes
|
||||
tax_a = self.env['account.tax'].create({
|
||||
'name': 'Tax A',
|
||||
'type_tax_use': 'sale',
|
||||
'amount_type': 'percent',
|
||||
'amount': 15,
|
||||
})
|
||||
tax_b = tax_a.copy({'name': 'Tax B'})
|
||||
|
||||
# Create 4 products subject to different tax
|
||||
products_data = [
|
||||
('Product A', []),
|
||||
('Product B', [tax_a.id]),
|
||||
('Product C', [tax_b.id]),
|
||||
('Product D', [tax_a.id, tax_b.id]),
|
||||
]
|
||||
|
||||
products = self.env['product.product'].create(
|
||||
[{
|
||||
'name': name,
|
||||
'list_price': 100,
|
||||
'sale_ok': True,
|
||||
'taxes_id': [Command.set(taxes_id)],
|
||||
} 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],
|
||||
})
|
||||
|
||||
msg = "There should only be 4 lines for the 4 products."
|
||||
self.assertEqual(len(order.order_line), 4, msg=msg)
|
||||
|
||||
# 2. Apply the coupon
|
||||
self._apply_promo_code(order, self.coupon.code)
|
||||
|
||||
msg = (
|
||||
"4 additional lines should have been added to the sale orders"
|
||||
"after application of the coupon for each separate tax situation."
|
||||
)
|
||||
self.assertEqual(len(order.order_line), 8, msg=msg)
|
||||
|
||||
# 3. Remove the coupon
|
||||
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,
|
||||
)
|
||||
|
||||
msg = "All coupon lines should have been removed from the order."
|
||||
self.assertEqual(len(order.applied_coupon_ids), 0, msg=msg)
|
||||
self.assertEqual(len(order.order_line), 4, msg=msg)
|
||||
|
||||
def test_confirm_points_as_public_user(self):
|
||||
test_product = self.env['product.product'].create({
|
||||
'name': "Test Product",
|
||||
'list_price': 100,
|
||||
'sale_ok': True,
|
||||
})
|
||||
test_partner = self.env['res.partner'].create({
|
||||
'name': 'Test Partner'
|
||||
})
|
||||
|
||||
loyalty_program = self.env['loyalty.program'].create({
|
||||
'name': "Loyalty Program",
|
||||
'program_type': 'loyalty',
|
||||
'trigger': 'auto',
|
||||
'applies_on': 'both',
|
||||
'rule_ids': [Command.create({
|
||||
'reward_point_mode': 'unit',
|
||||
'reward_point_amount': 1,
|
||||
'product_ids': [test_product.id],
|
||||
})],
|
||||
'reward_ids': [Command.create({
|
||||
'reward_type': 'discount',
|
||||
'discount': 1.5,
|
||||
'discount_mode': 'per_point',
|
||||
'discount_applicability': 'order',
|
||||
'required_points': 3,
|
||||
})],
|
||||
})
|
||||
|
||||
order = self.env['sale.order'].create({
|
||||
'partner_id': test_partner.id,
|
||||
'order_line': [Command.create({
|
||||
'product_id': test_product.id,
|
||||
'product_uom_qty': 1,
|
||||
})]
|
||||
})
|
||||
|
||||
loyalty_card = self.env['loyalty.card'].create({
|
||||
'program_id': loyalty_program.id,
|
||||
'partner_id': test_partner.id,
|
||||
'points': 0,
|
||||
})
|
||||
public_user = self.env.ref('base.public_user')
|
||||
order.action_quotation_send()
|
||||
order.with_context(access_token=order.access_token, user=public_user).action_confirm()
|
||||
self.assertEqual(loyalty_card.points, 1)
|
||||
Loading…
Add table
Add a link
Reference in a new issue