mirror of
https://github.com/bringout/oca-ocb-sale.git
synced 2026-04-26 20:12:04 +02:00
19.0 vanilla
This commit is contained in:
parent
79f83631d5
commit
73afc09215
6267 changed files with 1534193 additions and 1130106 deletions
|
|
@ -1,26 +1,46 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import test_address
|
||||
from . import test_common
|
||||
from . import test_customize
|
||||
from . import test_delivery_controller
|
||||
from . import test_delivery_ui
|
||||
from . import test_dynamic_snippet_category
|
||||
from . import test_ecommerce_access
|
||||
from . import test_express_checkout_flows
|
||||
from . import test_fuzzy
|
||||
from . import test_main_controller
|
||||
from . import test_product_public_category
|
||||
from . import test_sale_order
|
||||
from . import test_sale_process
|
||||
from . import test_sitemap
|
||||
from . import test_website_editor
|
||||
from . import test_website_sale_add_to_cart_snippet
|
||||
from . import test_website_sale_cart_abandoned
|
||||
from . import test_website_sale_cart_payment
|
||||
from . import test_website_sale_cart_popover
|
||||
from . import test_website_sale_cart_recovery
|
||||
from . import test_website_sale_cart
|
||||
from . import test_website_sale_checkout_steps
|
||||
from . import test_website_sale_cart_abandoned
|
||||
from . import test_website_sale_cart_notification
|
||||
from . import test_website_sale_cart_payment
|
||||
from . import test_website_sale_cart_recovery
|
||||
from . import test_website_sale_combo_configurator
|
||||
from . import test_website_sale_gmc
|
||||
from . import test_website_sale_image
|
||||
from . import test_website_sale_invoice
|
||||
from . import test_website_sale_mail
|
||||
from . import test_website_sale_mail_template
|
||||
from . import test_website_sale_pricelist
|
||||
from . import test_website_sale_product_attribute_value_config
|
||||
from . import test_website_sale_image
|
||||
from . import test_website_sequence
|
||||
from . import test_website_sale_show_compare_list_price
|
||||
from . import test_website_sale_visitor
|
||||
from . import test_website_sale_product
|
||||
from . import test_website_editor
|
||||
from . import test_website_sale_product_configurator
|
||||
from . import test_website_sale_product_filters
|
||||
from . import test_website_sale_product_page
|
||||
from . import test_website_sale_product_template
|
||||
from . import test_website_sale_reorder_from_portal
|
||||
from . import test_website_sale_seo
|
||||
from . import test_website_sale_settings
|
||||
from . import test_website_sale_shop_redirects
|
||||
from . import test_website_sale_show_compare_list_price
|
||||
from . import test_website_sale_snippets
|
||||
from . import test_website_sale_fiscal_position
|
||||
from . import test_website_sale_invoice
|
||||
from . import test_website_sale_visitor
|
||||
from . import test_website_sale_technical_page
|
||||
from . import test_website_sequence
|
||||
from . import test_website_sale_product_ribbon
|
||||
|
|
|
|||
154
odoo-bringout-oca-ocb-website_sale/website_sale/tests/common.py
Normal file
154
odoo-bringout-oca-ocb-website_sale/website_sale/tests/common.py
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import base64
|
||||
from contextlib import contextmanager
|
||||
import io
|
||||
from PIL import Image
|
||||
|
||||
from odoo.fields import Command
|
||||
from odoo.tools import lazy
|
||||
|
||||
from odoo.addons.delivery.tests.common import DeliveryCommon
|
||||
from odoo.addons.product.tests.common import ProductCommon
|
||||
from odoo.addons.http_routing.tests.common import MockRequest as websiteMockRequest
|
||||
from odoo.addons.website_sale.models.website import (
|
||||
CART_SESSION_CACHE_KEY,
|
||||
FISCAL_POSITION_SESSION_CACHE_KEY,
|
||||
PRICELIST_SESSION_CACHE_KEY,
|
||||
PRICELIST_SELECTED_SESSION_CACHE_KEY,
|
||||
)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def MockRequest(
|
||||
*args,
|
||||
sale_order_id=None,
|
||||
website_sale_current_pl=None,
|
||||
fiscal_position_id=None,
|
||||
website_sale_selected_pl_id=None,
|
||||
**kwargs,
|
||||
):
|
||||
with websiteMockRequest(*args, **kwargs) as request:
|
||||
if sale_order_id is not None:
|
||||
request.session[CART_SESSION_CACHE_KEY] = sale_order_id
|
||||
request.cart = lazy(request.website._get_and_cache_current_cart)
|
||||
|
||||
if website_sale_current_pl is not None:
|
||||
request.session[PRICELIST_SESSION_CACHE_KEY] = website_sale_current_pl
|
||||
request.pricelist = lazy(request.website._get_and_cache_current_pricelist)
|
||||
|
||||
if website_sale_selected_pl_id is not None:
|
||||
request.session[PRICELIST_SELECTED_SESSION_CACHE_KEY] = website_sale_selected_pl_id
|
||||
|
||||
if fiscal_position_id is not None:
|
||||
request.session[FISCAL_POSITION_SESSION_CACHE_KEY] = fiscal_position_id
|
||||
request.fiscal_position = lazy(request.website._get_and_cache_current_fiscal_position)
|
||||
|
||||
yield request
|
||||
|
||||
|
||||
class WebsiteSaleCommon(ProductCommon, DeliveryCommon):
|
||||
# Not based on SaleCommon as there is no need for SalesTeamCommon nor standard SaleCommon data
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
cls.website = cls.env.company.website_id
|
||||
if not cls.website:
|
||||
cls.website = cls.env['website'].create({
|
||||
'name': 'Test Website',
|
||||
'company_id': cls.env.company.id,
|
||||
})
|
||||
|
||||
cls.public_user = cls.website.user_id
|
||||
cls.public_partner = cls.public_user.partner_id
|
||||
|
||||
cls.empty_cart = cls.env['sale.order'].create({
|
||||
'partner_id': cls.partner.id,
|
||||
'website_id': cls.website.id,
|
||||
})
|
||||
cls.cart = cls.env['sale.order'].create({
|
||||
'partner_id': cls.partner.id,
|
||||
'website_id': cls.website.id,
|
||||
'order_line': [
|
||||
Command.create({
|
||||
'product_id': cls.product.id,
|
||||
'product_uom_qty': 5.0,
|
||||
}),
|
||||
Command.create({
|
||||
'product_id': cls.service_product.id,
|
||||
'product_uom_qty': 12.5,
|
||||
})
|
||||
]
|
||||
})
|
||||
|
||||
# Publish tests products
|
||||
(
|
||||
cls.product
|
||||
+ cls.service_product
|
||||
).website_published = True
|
||||
cls.pricelist.website_id = cls.website
|
||||
|
||||
cls.country_be = cls.quick_ref('base.be')
|
||||
cls.country_us = cls.quick_ref('base.us')
|
||||
cls.country_us_state_id = cls.env['ir.model.data']._xmlid_to_res_id('base.state_us_39')
|
||||
cls.dummy_partner_address_values = {
|
||||
'street': '215 Vine St',
|
||||
'city': 'Scranton',
|
||||
'zip': '18503',
|
||||
'country_id': cls.country_us.id,
|
||||
'state_id': cls.country_us_state_id,
|
||||
'phone': '+1 555-555-5555',
|
||||
'email': 'admin@yourcompany.example.com',
|
||||
}
|
||||
|
||||
def _create_so(self, **values):
|
||||
default_values = {
|
||||
'partner_id': self.partner.id,
|
||||
'website_id': self.website.id,
|
||||
'order_line': [
|
||||
Command.create({
|
||||
'product_id': self.product.id,
|
||||
}),
|
||||
],
|
||||
}
|
||||
return self.env['sale.order'].create(dict(default_values, **values))
|
||||
|
||||
@classmethod
|
||||
def _prepare_carrier(cls, product, website_published=True, **values):
|
||||
""" Override of `delivery` to auto-publish test delivery methods. """
|
||||
return super()._prepare_carrier(product, website_published=website_published, **values)
|
||||
|
||||
@classmethod
|
||||
def _create_product(cls, **kwargs):
|
||||
""" Override of `product` to auto-publish test products by default. """
|
||||
if 'website_published' not in kwargs:
|
||||
kwargs['website_published'] = True
|
||||
return super()._create_product(**kwargs)
|
||||
|
||||
@classmethod
|
||||
def _create_public_category(cls, list_vals):
|
||||
"""Create a hierarchical chain of `public.product.category`.
|
||||
|
||||
For example::
|
||||
|
||||
# Furnitures / Sofas
|
||||
self._create_public_category([
|
||||
{'name': 'Furnitures'}, {'name': 'Sofas'}
|
||||
])
|
||||
|
||||
:return: The created categories.
|
||||
:rtype: public.product.category
|
||||
"""
|
||||
categs = cls.env['product.public.category'].create(list_vals)
|
||||
for i in range(0, len(categs) - 1):
|
||||
categs[i].parent_id = categs[i + 1]
|
||||
return categs
|
||||
|
||||
@classmethod
|
||||
def _create_image(cls, color):
|
||||
f = io.BytesIO()
|
||||
Image.new('RGB', (1920, 1080), color).save(f, 'JPEG')
|
||||
f.seek(0)
|
||||
return base64.b64encode(f.read())
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import time
|
||||
|
||||
from odoo.fields import Command
|
||||
|
||||
from odoo.addons.website_sale.controllers.product_feed import ProductFeed
|
||||
from odoo.addons.product.tests.common import ProductVariantsCommon
|
||||
from odoo.addons.website_sale.tests.common import MockRequest, WebsiteSaleCommon
|
||||
|
||||
|
||||
class WebsiteSaleGMCCommon(ProductVariantsCommon, WebsiteSaleCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.ProductFeedController = ProductFeed()
|
||||
cls.website.enabled_gmc_src = True
|
||||
|
||||
cls.gmc_feed = cls.env['product.feed'].create({
|
||||
'name': "GMC",
|
||||
'website_id': cls.website.id,
|
||||
})
|
||||
|
||||
# Prepare products
|
||||
cls.product_template_sofa.list_price = 1000.0
|
||||
(cls.red_sofa, cls.blue_sofa) = cls.product_template_sofa.product_variant_ids[:2]
|
||||
cls.red_sofa.default_code = 'SOFA-R'
|
||||
cls.blue_sofa.product_template_attribute_value_ids.filtered(
|
||||
lambda v: v.name == 'blue'
|
||||
).price_extra = 200.0
|
||||
cls.blanket = cls._create_product(name="Blanket")
|
||||
combos = cls.env['product.combo'].create([
|
||||
{
|
||||
'name': "Sofa Combo",
|
||||
'combo_item_ids': [
|
||||
Command.create({'product_id': cls.red_sofa.id}),
|
||||
Command.create({'product_id': cls.blue_sofa.id})
|
||||
]
|
||||
},
|
||||
{
|
||||
'name': "Blanket Combo",
|
||||
'combo_item_ids': [
|
||||
Command.create({'product_id': cls.blanket.id}),
|
||||
]
|
||||
}
|
||||
])
|
||||
cls.sofa_bundle = cls._create_product(
|
||||
name="Sofa + Blanket",
|
||||
type='combo',
|
||||
combo_ids=[Command.set(combos.ids)],
|
||||
list_price=1099.0
|
||||
)
|
||||
cls.products = cls.red_sofa + cls.blue_sofa + cls.blanket + cls.sofa_bundle
|
||||
cls.products.website_published = True
|
||||
|
||||
# Prepare pricelists
|
||||
cls.eur_currency = cls.env.ref('base.EUR')
|
||||
cls.eur_currency.write({
|
||||
'active': True,
|
||||
'rate_ids': [
|
||||
Command.clear(),
|
||||
Command.create({'name': time.strftime('%Y-%m-%d'), 'rate': 1.1})
|
||||
],
|
||||
})
|
||||
cls.eur_pricelist = cls._create_pricelist(
|
||||
name="EUR",
|
||||
currency_id=cls.eur_currency.id,
|
||||
selectable=True,
|
||||
)
|
||||
|
||||
def update_items(self, feed=None):
|
||||
feed = feed or self.gmc_feed
|
||||
feed = feed.with_context(lang=feed.lang_id.code)
|
||||
with MockRequest(
|
||||
feed.env,
|
||||
website=feed.website_id,
|
||||
website_sale_current_pl=feed.pricelist_id.id,
|
||||
):
|
||||
self.items = feed._prepare_gmc_items()
|
||||
|
||||
self.red_sofa_item = self.items[self.red_sofa]
|
||||
self.blue_sofa_item = self.items[self.blue_sofa]
|
||||
|
|
@ -0,0 +1,691 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import json
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
from werkzeug.exceptions import Forbidden
|
||||
|
||||
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 TestCheckoutAddress(WebsiteSaleCommon):
|
||||
"""Test the address management part of the checkout process:
|
||||
|
||||
* address creation (/shop/address)
|
||||
* address update (/shop/address)
|
||||
* address choice (/shop/checkout)
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.country_id = cls.country_be.id
|
||||
cls.user_portal = cls._create_new_portal_user()
|
||||
cls.user_internal = cls._create_new_internal_user()
|
||||
cls.user_internal.partner_id.company_id = cls.env.company
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.WebsiteSaleController = WebsiteSale()
|
||||
self.default_address_values = {
|
||||
'name': 'a res.partner address',
|
||||
'email': 'email@email.email',
|
||||
'street': 'ooo',
|
||||
'city': 'ooo',
|
||||
'zip': '1200',
|
||||
'country_id': self.country_id,
|
||||
'phone': '+333333333333333',
|
||||
'address_type': 'delivery',
|
||||
}
|
||||
self.default_billing_address_values = dict(
|
||||
self.default_address_values, address_type='billing',
|
||||
)
|
||||
|
||||
def _get_last_address(self, partner):
|
||||
""" Useful to retrieve the last created address (shipping or billing) """
|
||||
return partner.child_ids.sorted('id', reverse=True)[0]
|
||||
|
||||
# TEST WEBSITE
|
||||
def test_01_create_shipping_address_specific_user_account(self):
|
||||
''' Ensure `website_id` is correctly set (specific_user_account) '''
|
||||
p = self.env.user.partner_id
|
||||
p.active = True
|
||||
so = self._create_so(partner_id=p.id)
|
||||
|
||||
with MockRequest(self.env, website=self.website, sale_order_id=so.id) as req:
|
||||
req.httprequest.method = "POST"
|
||||
self.WebsiteSaleController.shop_address_submit(**self.default_address_values)
|
||||
self.assertFalse(self._get_last_address(p).website_id, "New shipping address should not have a website set on it (no specific_user_account).")
|
||||
|
||||
self.website.specific_user_account = True
|
||||
|
||||
self.WebsiteSaleController.shop_address_submit(**self.default_address_values)
|
||||
self.assertEqual(self._get_last_address(p).website_id, self.website, "New shipping address should have a website set on it (specific_user_account).")
|
||||
|
||||
# TEST COMPANY
|
||||
def _setUp_multicompany_env(self):
|
||||
''' Have 2 companies A & B.
|
||||
Have 1 website 1 which company is B
|
||||
Have admin on company A
|
||||
'''
|
||||
self.company_a, self.company_b, self.company_c = self.env['res.company'].create([
|
||||
{'name': 'Company A'},
|
||||
{'name': 'Company B'},
|
||||
{'name': 'Company C'},
|
||||
])
|
||||
self.website.company_id = self.company_b
|
||||
self.env.user.company_id = self.company_a
|
||||
|
||||
self.demo_user = self.user_internal
|
||||
self.demo_user.company_ids += self.company_b
|
||||
self.demo_user.company_id = self.company_b
|
||||
self.demo_partner = self.demo_user.partner_id
|
||||
|
||||
self.portal_user = self.user_portal
|
||||
self.portal_partner = self.portal_user.partner_id
|
||||
|
||||
def test_02_demo_address_and_company(self):
|
||||
''' This test ensure that the company_id of the address (partner) is
|
||||
correctly set and also, is not wrongly changed.
|
||||
eg: new shipping should use the company of the website and not the
|
||||
one from the admin, and editing a billing should not change its
|
||||
company.
|
||||
'''
|
||||
self._setUp_multicompany_env()
|
||||
so = self._create_so(partner_id=self.demo_partner.id)
|
||||
|
||||
website = self.website.with_user(self.demo_user).with_context({})
|
||||
with MockRequest(website.env, website=website, sale_order_id=so.id) as req:
|
||||
req.httprequest.method = "POST"
|
||||
|
||||
# 1. Logged in user, new shipping
|
||||
self.WebsiteSaleController.shop_address_submit(**self.default_address_values)
|
||||
new_shipping = self._get_last_address(self.demo_partner)
|
||||
self.assertTrue(new_shipping.company_id != self.env.user.company_id, "Logged in user new shipping should not get the company of the sudo() neither the one from it's partner..")
|
||||
self.assertEqual(new_shipping.company_id, self.website.company_id, ".. but the one from the website.")
|
||||
|
||||
# 2. Logged in user/internal user, should not edit name or email address of billing
|
||||
self.default_address_values['partner_id'] = self.demo_partner.id
|
||||
self.WebsiteSaleController.shop_address_submit(**self.default_billing_address_values)
|
||||
self.assertEqual(self.demo_partner.company_id, self.company_b, "Logged in user edited billing (the partner itself) should not get its company modified.")
|
||||
self.assertNotEqual(self.demo_partner.name, self.default_address_values['name'], "Employee cannot change their name during the checkout process.")
|
||||
self.assertNotEqual(self.demo_partner.email, self.default_address_values['email'], "Employee cannot change their email during the checkout process.")
|
||||
|
||||
def test_03_public_user_address_and_company(self):
|
||||
''' Same as test_02 but with public user '''
|
||||
self._setUp_multicompany_env()
|
||||
so = self._create_so(partner_id=self.website.user_id.partner_id.id)
|
||||
|
||||
website = self.website.with_user(self.public_user).with_context({})
|
||||
with MockRequest(website.env, website=website, sale_order_id=so.id) as req:
|
||||
req.httprequest.method = "POST"
|
||||
|
||||
# 1. Public user, new billing
|
||||
self.default_address_values['partner_id'] = -1
|
||||
self.WebsiteSaleController.shop_address_submit(**self.default_address_values)
|
||||
new_partner = so.partner_id
|
||||
self.assertNotEqual(new_partner, self.website.user_id.partner_id, "New billing should have created a new partner and assign it on the SO")
|
||||
self.assertEqual(new_partner.company_id, self.website.company_id, "The new partner should get the company of the website")
|
||||
|
||||
# 2. Public user, edit billing
|
||||
self.default_address_values['partner_id'] = new_partner.id
|
||||
self.WebsiteSaleController.shop_address_submit(**self.default_billing_address_values)
|
||||
self.assertEqual(new_partner.company_id, self.website.company_id, "Public user edited billing (the partner itself) should not get its company modified.")
|
||||
|
||||
def test_03_carrier_rate_on_shipping_address_change(self):
|
||||
""" Test that when a shipping address is changed the price of delivery is recalculated
|
||||
and updated on the order."""
|
||||
shipping_partner = self.env['res.partner'].create({
|
||||
'name': 'dummy',
|
||||
'parent_id': self.partner.id,
|
||||
'type': 'delivery',
|
||||
})
|
||||
self.cart.write({
|
||||
'carrier_id': self.carrier.id,
|
||||
'partner_shipping_id': shipping_partner.id
|
||||
})
|
||||
website = self.website.with_user(self.public_user).with_context({})
|
||||
with (
|
||||
MockRequest(website.env, website=website, sale_order_id=self.cart.id),
|
||||
patch(
|
||||
'odoo.addons.delivery.models.delivery_carrier.DeliveryCarrier.rate_shipment',
|
||||
return_value={'success': True, 'price': 10, 'warning_message': ''}
|
||||
) as rate_shipment_mock
|
||||
):
|
||||
# Change a shipping address of the order in the checkout.
|
||||
shipping_partner2 = shipping_partner.copy()
|
||||
self.WebsiteSaleController.shop_update_address(
|
||||
partner_id=shipping_partner2.id, address_type='delivery'
|
||||
)
|
||||
self.assertGreaterEqual(
|
||||
rate_shipment_mock.call_count,
|
||||
1,
|
||||
msg="The carrier rate must be recalculated when shipping address is changed.",
|
||||
)
|
||||
self.assertEqual(
|
||||
self.cart.order_line.filtered('is_delivery')[0].price_unit,
|
||||
10,
|
||||
msg="The recalculated delivery price must be updated on the order.",
|
||||
)
|
||||
|
||||
def test_04_apply_empty_pl(self):
|
||||
''' Ensure empty pl code reset the applied pl '''
|
||||
self._enable_pricelists()
|
||||
so = self._create_so(partner_id=self.env.user.partner_id.id)
|
||||
eur_pl = self.env['product.pricelist'].create({
|
||||
'name': 'EUR_test',
|
||||
'website_id': self.website.id,
|
||||
'code': 'EUR_test',
|
||||
})
|
||||
|
||||
with MockRequest(self.env, website=self.website, sale_order_id=so.id):
|
||||
self.WebsiteSaleController.pricelist('EUR_test')
|
||||
self.assertEqual(so.pricelist_id, eur_pl, "Ensure EUR_test is applied")
|
||||
|
||||
self.WebsiteSaleController.pricelist('')
|
||||
self.assertNotEqual(so.pricelist_id, eur_pl, "Pricelist should be removed when sending an empty pl code")
|
||||
|
||||
def test_04_pl_reset_on_login(self):
|
||||
self._enable_pricelists()
|
||||
"""Check that after login, the SO pricelist is correctly recomputed."""
|
||||
test_user = self.env['res.users'].create({
|
||||
'name': 'Toto',
|
||||
'login': 'long_enough_password',
|
||||
'password': 'long_enough_password',
|
||||
})
|
||||
pl_with_code = self.env['product.pricelist'].create({
|
||||
'name': 'EUR_test',
|
||||
'website_id': self.website.id,
|
||||
'code': 'EUR_test',
|
||||
})
|
||||
self.website.user_id.partner_id.property_product_pricelist = self.pricelist
|
||||
test_user.partner_id.property_product_pricelist = pl_with_code
|
||||
|
||||
public_user_env = self.env(user=self.website.user_id)
|
||||
so = self._create_so(partner_id=public_user_env.user.partner_id.id)
|
||||
self.assertEqual(so.pricelist_id, self.pricelist)
|
||||
|
||||
with MockRequest(
|
||||
public_user_env,
|
||||
website=self.website.with_env(public_user_env),
|
||||
sale_order_id=so.id,
|
||||
website_sale_current_pl=so.pricelist_id.id
|
||||
) as request:
|
||||
self.assertEqual(request.pricelist, self.pricelist)
|
||||
order = request.cart
|
||||
self.assertEqual(order, so)
|
||||
self.assertEqual(order.pricelist_id, self.pricelist)
|
||||
|
||||
order_b = request.website.with_user(test_user)._get_and_cache_current_cart()
|
||||
self.assertEqual(order, order_b)
|
||||
self.assertEqual(order_b.pricelist_id, pl_with_code)
|
||||
|
||||
# TEST WEBSITE & MULTI COMPANY
|
||||
|
||||
def test_05_create_so_with_website_and_multi_company(self):
|
||||
''' This test ensure that the company_id of the website set on the order
|
||||
is the same as the env company or the one set on the order.
|
||||
'''
|
||||
self._setUp_multicompany_env()
|
||||
# No company on the SO
|
||||
so = self._create_so(partner_id=self.demo_partner.id)
|
||||
self.assertEqual(so.company_id, self.website.company_id)
|
||||
|
||||
# Same company on the SO and the env user company but no website
|
||||
with self.assertRaises(ValueError, msg="Should not be able to create SO with company different than the website company"):
|
||||
self._create_so(partner_id=self.demo_partner.id, company_id=self.company_a.id)
|
||||
|
||||
# Same company on the SO and the website company
|
||||
so = self._create_so(partner_id=self.demo_partner.id, company_id=self.company_b.id)
|
||||
self.assertEqual(so.company_id, self.website.company_id)
|
||||
|
||||
# Different company on the SO and the env user company
|
||||
with self.assertRaises(ValueError, msg="Should not be able to create SO with company different than the website company"):
|
||||
self._create_so(partner_id=self.demo_partner.id, company_id=self.company_c.id)
|
||||
|
||||
def test_06_portal_user_address_and_company(self):
|
||||
''' Same as test_03 but with portal user '''
|
||||
self._setUp_multicompany_env()
|
||||
so = self._create_so(partner_id=self.portal_partner.id)
|
||||
|
||||
website = self.website.with_user(self.portal_user).with_context({})
|
||||
with MockRequest(website.env, website=website, sale_order_id=so.id) as req:
|
||||
req.httprequest.method = "POST"
|
||||
|
||||
# 1. Portal user, new shipping, same with the log in user
|
||||
self.WebsiteSaleController.shop_address_submit(**self.default_address_values)
|
||||
new_shipping = self._get_last_address(self.portal_partner)
|
||||
self.assertTrue(new_shipping.company_id != self.env.user.company_id, "Portal user new shipping should not get the company of the sudo() neither the one from it's partner..")
|
||||
self.assertEqual(new_shipping.company_id, self.website.company_id, ".. but the one from the website.")
|
||||
|
||||
# 2. Portal user, edit billing
|
||||
self.default_address_values['partner_id'] = self.portal_partner.id
|
||||
self.WebsiteSaleController.shop_address_submit(**self.default_billing_address_values)
|
||||
# Name cannot be changed if there are issued invoices
|
||||
self.assertNotEqual(self.portal_partner.name, self.default_address_values['name'], "Portal User should not be able to change the name if they have invoices under their name.")
|
||||
|
||||
def test_07_change_fiscal_position(self):
|
||||
"""
|
||||
Check that the sale order is updated when you change fiscal position.
|
||||
Change fiscal position by modifying address during checkout process.
|
||||
"""
|
||||
self.env.company.country_id = self.country_us
|
||||
be_address_POST, nl_address_POST = [
|
||||
{
|
||||
'name': 'Test name', 'email': 'test@email.com', 'street': 'test', 'phone': '+333333333333333',
|
||||
'city': 'test', 'zip': '3000', 'country_id': self.env.ref('base.be').id, 'submitted': 1,
|
||||
'partner_id': self.partner.id,
|
||||
'callback': '/shop/checkout',
|
||||
},
|
||||
{
|
||||
'name': 'Test name', 'email': 'test@email.com', 'street': 'test', 'phone': '+333333333333333',
|
||||
'city': 'test', 'zip': '3000', 'country_id': self.env.ref('base.nl').id, 'submitted': 1,
|
||||
'partner_id': self.partner.id,
|
||||
'callback': '/shop/checkout',
|
||||
},
|
||||
]
|
||||
|
||||
fpos_be, fpos_nl = self.env['account.fiscal.position'].create([
|
||||
{
|
||||
'sequence': 1,
|
||||
'name': 'BE',
|
||||
'auto_apply': True,
|
||||
'country_id': self.env.ref('base.be').id,
|
||||
},
|
||||
{
|
||||
'sequence': 2,
|
||||
'name': 'NL',
|
||||
'auto_apply': True,
|
||||
'country_id': self.env.ref('base.nl').id,
|
||||
},
|
||||
])
|
||||
tax_10_incl, tax_20_excl, tax_15_incl = self.env['account.tax'].create([
|
||||
{'name': 'Tax 10% incl', 'amount': 10, 'price_include_override': 'tax_included'},
|
||||
{'name': 'Tax 20% excl', 'amount': 20, 'price_include_override': 'tax_excluded', 'fiscal_position_ids': fpos_be},
|
||||
{'name': 'Tax 15% incl', 'amount': 15, 'price_include_override': 'tax_included', 'fiscal_position_ids': fpos_nl},
|
||||
])
|
||||
(tax_20_excl | tax_15_incl).original_tax_ids = tax_10_incl
|
||||
|
||||
product = self.env['product.product'].create({
|
||||
'name': 'Product test',
|
||||
'list_price': 100,
|
||||
'website_published': True,
|
||||
'sale_ok': True,
|
||||
'taxes_id': [tax_10_incl.id]
|
||||
})
|
||||
so = self.env['sale.order'].create({
|
||||
'partner_id': self.partner.id,
|
||||
'website_id': self.website.id,
|
||||
'order_line': [Command.create({
|
||||
'product_id': product.id,
|
||||
'name': 'Product test',
|
||||
})]
|
||||
})
|
||||
|
||||
self.assertEqual(
|
||||
[so.amount_untaxed, so.amount_tax, so.amount_total],
|
||||
[90.91, 9.09, 100.0]
|
||||
)
|
||||
|
||||
website = self.website.with_user(self.public_user).with_context({})
|
||||
with MockRequest(website.env, website=website, sale_order_id=so.id) as req:
|
||||
req.httprequest.method = "POST"
|
||||
|
||||
self.WebsiteSaleController.shop_address_submit(**be_address_POST)
|
||||
self.assertEqual(
|
||||
so.fiscal_position_id,
|
||||
fpos_be,
|
||||
)
|
||||
self.assertEqual(
|
||||
[so.amount_untaxed, so.amount_tax, so.amount_total],
|
||||
[90.91, 18.18, 109.09] # (100 : (1 + 10%)) * (1 + 20%) = 109.09
|
||||
)
|
||||
|
||||
self.WebsiteSaleController.shop_address_submit(**nl_address_POST)
|
||||
self.assertEqual(
|
||||
so.fiscal_position_id,
|
||||
fpos_nl,
|
||||
)
|
||||
self.assertEqual(
|
||||
[so.amount_untaxed, so.amount_tax, so.amount_total],
|
||||
[90.91, 13.64, 104.55] # (100 : (1 + 10%)) * (1 + 15%) = 104.55
|
||||
)
|
||||
|
||||
def test_08_new_user_address_state(self):
|
||||
''' Test the billing and shipping addresses creation values. '''
|
||||
self._setUp_multicompany_env()
|
||||
so = self._create_so(partner_id=self.demo_partner.id)
|
||||
|
||||
website = self.website.with_user(self.demo_user).with_context({})
|
||||
with MockRequest(website.env, website=website, sale_order_id=so.id) as req:
|
||||
req.httprequest.method = "POST"
|
||||
|
||||
# check the default values
|
||||
self.assertEqual(self.demo_partner, so.partner_invoice_id)
|
||||
self.assertEqual(self.demo_partner, so.partner_shipping_id)
|
||||
|
||||
# 1. Logged-in user, new shipping
|
||||
self.WebsiteSaleController.shop_address_submit(**self.default_address_values)
|
||||
new_shipping = self._get_last_address(self.demo_partner)
|
||||
msg = "New shipping address should have its type set as 'delivery'"
|
||||
self.assertTrue(new_shipping.type == 'delivery', msg)
|
||||
|
||||
# 2. Logged-in user, new billing
|
||||
self.WebsiteSaleController.shop_address_submit(**self.default_billing_address_values)
|
||||
new_billing = self._get_last_address(self.demo_partner)
|
||||
msg = "New billing should have its type set as 'invoice'"
|
||||
self.assertTrue(new_billing.type == 'invoice', msg)
|
||||
self.assertNotEqual(new_billing, self.demo_partner)
|
||||
|
||||
# 3. Check that invoice/shipping address of so changed
|
||||
self.assertEqual(new_billing, so.partner_invoice_id)
|
||||
self.assertEqual(new_shipping, so.partner_shipping_id)
|
||||
|
||||
# 4. Logged-in user, new delivery, use delivery as billing
|
||||
use_delivery_as_billing = (
|
||||
self.default_address_values | {'use_delivery_as_billing': 'true'}
|
||||
)
|
||||
self.WebsiteSaleController.shop_address_submit(**use_delivery_as_billing)
|
||||
new_delivery_use_same = self._get_last_address(self.demo_partner)
|
||||
msg = "New delivery use same should have its type set as 'other'"
|
||||
self.assertTrue(new_delivery_use_same.type == 'other', msg)
|
||||
self.assertNotEqual(new_billing, self.demo_partner)
|
||||
|
||||
# 5. Check that invoice/shipping address of so changed
|
||||
self.assertEqual(new_delivery_use_same, so.partner_invoice_id)
|
||||
self.assertEqual(new_delivery_use_same, so.partner_shipping_id)
|
||||
|
||||
# 6. forbid address page opening with wrong partners:
|
||||
with self.assertRaises(Forbidden):
|
||||
self.WebsiteSaleController.shop_address_submit(
|
||||
partner_id=self.env.user.partner_id.id, address_type='billing'
|
||||
)
|
||||
with self.assertRaises(Forbidden):
|
||||
self.WebsiteSaleController.shop_address_submit(
|
||||
partner_id=self.env.user.partner_id.id, address_type='delivery'
|
||||
)
|
||||
|
||||
def test_09_shop_update_address(self):
|
||||
self.env['ir.config_parameter'].sudo().set_param('auth_password_policy.minlength', 4)
|
||||
user = self.env['res.users'].create({
|
||||
'name': 'test',
|
||||
'login': 'test',
|
||||
'password': 'test',
|
||||
})
|
||||
user_partner = user.partner_id
|
||||
partner_company = self.env['res.partner'].create({
|
||||
'name': 'My company',
|
||||
'is_company': True,
|
||||
'child_ids': [Command.link(user_partner.id)],
|
||||
})
|
||||
colleague = self.env['res.partner'].create({
|
||||
'parent_id': partner_company.id,
|
||||
'name': 'whatever',
|
||||
})
|
||||
colleague_shipping = self.env['res.partner'].create({
|
||||
'parent_id': colleague.id,
|
||||
'name': 'whatever',
|
||||
'type': 'delivery',
|
||||
})
|
||||
|
||||
self.assertEqual(partner_company.commercial_partner_id, partner_company)
|
||||
self.assertEqual(user_partner.commercial_partner_id, partner_company)
|
||||
|
||||
invoicing, shipping, bad_invoicing, bad_shipping = self.env['res.partner'].create([
|
||||
{
|
||||
'name': 'Invoicing',
|
||||
'street': '215 Vine St',
|
||||
'city': 'Scranton',
|
||||
'zip': '18503',
|
||||
'country_id': self.country_us.id,
|
||||
'state_id': self.country_us_state_id,
|
||||
'phone': '+1 555-555-5555',
|
||||
'email': 'admin@yourcompany.example.com',
|
||||
'type': 'invoice',
|
||||
'parent_id': user_partner.id,
|
||||
},
|
||||
{
|
||||
'name': 'Shipping',
|
||||
'street': '215 Vine St',
|
||||
'city': 'Scranton',
|
||||
'zip': '18503',
|
||||
'country_id': self.country_us.id,
|
||||
'state_id': self.country_us_state_id,
|
||||
'phone': '+1 555-555-5555',
|
||||
'email': 'admin@yourcompany.example.com',
|
||||
'type': 'delivery',
|
||||
'parent_id': user_partner.id,
|
||||
},
|
||||
{
|
||||
'name': 'Invalid billing', # missing email
|
||||
'street': '215 Vine St',
|
||||
'city': 'Scranton',
|
||||
'zip': '18503',
|
||||
'country_id': self.country_us.id,
|
||||
'state_id': self.country_us_state_id,
|
||||
'phone': '+1 555-555-5555',
|
||||
'type': 'invoice',
|
||||
'parent_id': user_partner.id,
|
||||
},
|
||||
{
|
||||
'name': 'Invalid Shipping',
|
||||
'type': 'delivery',
|
||||
'parent_id': user_partner.id,
|
||||
},
|
||||
])
|
||||
|
||||
so = self._create_so(partner_id=user_partner.id)
|
||||
self.assertNotEqual(so.partner_shipping_id, shipping)
|
||||
self.assertNotEqual(so.partner_invoice_id, invoicing)
|
||||
|
||||
website = self.website.with_user(user).with_context({})
|
||||
with MockRequest(website.env, website=website, sale_order_id=so.id):
|
||||
|
||||
self.assertFalse(colleague._can_be_edited_by_current_customer(order_sudo=so))
|
||||
# Invalid addresses unaccessible to current customer
|
||||
with self.assertRaises(Forbidden):
|
||||
# cannot use contact type addresses
|
||||
self.WebsiteSaleController.shop_update_address(partner_id=colleague.id)
|
||||
with self.assertRaises(Forbidden):
|
||||
# unrelated partner
|
||||
self.WebsiteSaleController.shop_update_address(partner_id=self.env.user.partner_id.id)
|
||||
|
||||
# Good addresses
|
||||
self.WebsiteSaleController.shop_update_address(
|
||||
partner_id=colleague_shipping.id, address_type='delivery')
|
||||
self.assertEqual(so.partner_shipping_id, colleague_shipping)
|
||||
self.WebsiteSaleController.shop_update_address(
|
||||
partner_id=shipping.id, address_type='delivery',
|
||||
)
|
||||
self.assertEqual(so.partner_shipping_id, shipping)
|
||||
|
||||
self.WebsiteSaleController.shop_update_address(
|
||||
partner_id=invoicing.id, address_type='billing',
|
||||
)
|
||||
self.assertEqual(so.partner_shipping_id, shipping)
|
||||
self.assertEqual(so.partner_invoice_id, invoicing)
|
||||
|
||||
# Using invalid addresses --> change and the customer is forced to update the address
|
||||
self.WebsiteSaleController.shop_update_address(
|
||||
partner_id=bad_invoicing.id, address_type='billing')
|
||||
self.assertEqual(so.partner_invoice_id, bad_invoicing)
|
||||
redirection = self.WebsiteSaleController._check_addresses(so)
|
||||
self.assertTrue(redirection is not None)
|
||||
self.assertEqual(redirection.location, f'/shop/address?partner_id={bad_invoicing.id}&address_type=billing')
|
||||
|
||||
# reset to valid one
|
||||
self.WebsiteSaleController.shop_update_address(
|
||||
partner_id=invoicing.id, address_type='billing',
|
||||
)
|
||||
|
||||
self.WebsiteSaleController.shop_update_address(
|
||||
partner_id=bad_shipping.id, address_type='delivery')
|
||||
self.assertEqual(so.partner_shipping_id, bad_shipping)
|
||||
redirection = self.WebsiteSaleController._check_addresses(so)
|
||||
self.assertTrue(redirection is not None)
|
||||
self.assertEqual(redirection.location, f'/shop/address?partner_id={bad_shipping.id}&address_type=delivery')
|
||||
|
||||
# reset to valid one
|
||||
self.WebsiteSaleController.shop_update_address(
|
||||
partner_id=shipping.id, address_type='delivery',
|
||||
)
|
||||
|
||||
# Using commercial partner address
|
||||
self.WebsiteSaleController.shop_update_address(
|
||||
partner_id=partner_company.id, address_type='billing',
|
||||
)
|
||||
self.assertEqual(so.partner_invoice_id, partner_company)
|
||||
self.WebsiteSaleController.shop_update_address(
|
||||
partner_id=partner_company.id, address_type='delivery',
|
||||
)
|
||||
self.assertEqual(so.partner_shipping_id, partner_company)
|
||||
|
||||
def test_10_addresses_updates(self):
|
||||
# TODO dispatch test to sale & account
|
||||
partner_company = self.env['res.partner'].create({
|
||||
'name': 'My company',
|
||||
'is_company': True,
|
||||
'child_ids': [
|
||||
Command.create({
|
||||
'name': 'partner_1',
|
||||
}),
|
||||
Command.create({
|
||||
'name': 'partner_2',
|
||||
}),
|
||||
Command.create({
|
||||
'name': 'partner_3',
|
||||
}),
|
||||
],
|
||||
})
|
||||
partner_1, _partner_2, _partner_3 = partner_company.child_ids
|
||||
self.assertTrue(partner_company.can_edit_vat())
|
||||
self.assertTrue(partner_company._can_edit_country())
|
||||
self.assertTrue(all(not p.can_edit_vat() for p in partner_company.child_ids))
|
||||
self.assertTrue(all(p._can_edit_country() for p in partner_company.child_ids))
|
||||
|
||||
dumb_product = self.env['product.product'].create({'name': 'test'})
|
||||
invoice = self.env['account.move'].create({
|
||||
'move_type': 'out_invoice',
|
||||
'partner_id': partner_company.id,
|
||||
'invoice_line_ids': [
|
||||
Command.create({
|
||||
'product_id': dumb_product.id,
|
||||
})
|
||||
],
|
||||
})
|
||||
invoice.action_post()
|
||||
|
||||
self.assertEqual(invoice.state, 'posted')
|
||||
self.assertFalse(partner_company.can_edit_vat())
|
||||
self.assertFalse(partner_company._can_edit_country())
|
||||
self.assertTrue(all(p._can_edit_country() for p in partner_company.child_ids))
|
||||
invoice = self.env['account.move'].create({
|
||||
'move_type': 'out_invoice',
|
||||
'partner_id': partner_1.id,
|
||||
'invoice_line_ids': [
|
||||
Command.create({
|
||||
'product_id': dumb_product.id,
|
||||
})
|
||||
],
|
||||
})
|
||||
invoice.action_post()
|
||||
self.assertFalse(partner_1._can_edit_country())
|
||||
|
||||
def test_11_payment_term_when_address_change(self):
|
||||
"""Make sure the expected payment terms are set on ecommerce orders"""
|
||||
self.portal_user = self.user_portal
|
||||
self.portal_partner = self.portal_user.partner_id
|
||||
self.assertFalse(self.portal_partner.property_payment_term_id)
|
||||
so = self._create_so(partner_id=self.portal_partner.id)
|
||||
self.assertTrue(so.payment_term_id, "A payment term should be set by default on the sale order")
|
||||
|
||||
website = self.website.with_user(self.portal_user).with_context({})
|
||||
with MockRequest(website.env, website=website) as req:
|
||||
req.httprequest.method = "POST"
|
||||
|
||||
self.default_address_values['partner_id'] = self.portal_partner.id
|
||||
self.WebsiteSaleController.shop_address_submit(**self.default_billing_address_values)
|
||||
self.assertTrue(so.payment_term_id, "A payment term should still be set on the sale order")
|
||||
|
||||
so.website_id = False
|
||||
so._compute_payment_term_id()
|
||||
self.assertFalse(so.payment_term_id, "The website default payment term should not be set on a sale order not coming from the website")
|
||||
|
||||
def test_12_recompute_taxes_on_address_change(self):
|
||||
self.env.company.country_id = self.env.ref('base.us')
|
||||
fpos_be = self.env['account.fiscal.position'].create({
|
||||
'name': "Fiscal Position BE",
|
||||
'auto_apply': True,
|
||||
'country_id': self.country_id,
|
||||
})
|
||||
tax_15_incl, tax_0 = self.env['account.tax'].create([
|
||||
{
|
||||
'name': "15% excl",
|
||||
'amount': 15,
|
||||
'price_include_override': 'tax_included',
|
||||
'fiscal_position_ids': fpos_be.ids,
|
||||
},
|
||||
{
|
||||
'name': "0%",
|
||||
'amount': 0,
|
||||
'fiscal_position_ids': fpos_be.ids,
|
||||
},
|
||||
])
|
||||
tax_0.original_tax_ids = tax_15_incl
|
||||
self.product.taxes_id = [Command.set(tax_15_incl.ids)]
|
||||
self.partner.country_id = self.country_id
|
||||
|
||||
cart = self.empty_cart
|
||||
cart.order_line = [Command.create({'product_id': self.product.id})]
|
||||
amount_untaxed = cart.amount_untaxed
|
||||
|
||||
self.assertEqual(cart.fiscal_position_id, fpos_be)
|
||||
self.assertEqual(cart.order_line.tax_ids, tax_0)
|
||||
|
||||
self.partner.country_id = self.env.company.country_id
|
||||
self.assertNotEqual(cart.fiscal_position_id, fpos_be)
|
||||
self.assertEqual(cart.order_line.tax_ids, tax_15_incl)
|
||||
self.assertEqual(cart.amount_untaxed, amount_untaxed, "Untaxed amount should not change")
|
||||
|
||||
cart.action_confirm()
|
||||
self.partner.country_id = self.country_id
|
||||
self.assertEqual(
|
||||
cart.order_line.tax_ids, tax_15_incl,
|
||||
"Tax should no longer change after order confirmation",
|
||||
)
|
||||
|
||||
def test_imported_user_with_trailing_name_can_checkout(self):
|
||||
"""Ensure that an imported user with trailing spaces in their name can complete checkout without error."""
|
||||
|
||||
imported_user = self.env['res.users'].create({
|
||||
'name': 'Imported User ', # trailing space
|
||||
'login': 'imported_user',
|
||||
'email': 'imported@example.com',
|
||||
})
|
||||
imported_partner = imported_user.partner_id
|
||||
so = self._create_so(partner_id=imported_partner.id)
|
||||
|
||||
website = self.website.with_user(imported_user).with_context({})
|
||||
with MockRequest(website.env, website=website, sale_order_id=so.id) as req:
|
||||
req.httprequest.method = "POST"
|
||||
|
||||
values = {
|
||||
'name': 'Imported User', # trimmed input
|
||||
'email': 'imported@example.com',
|
||||
'street': '123 Some Street',
|
||||
'city': 'Cityville',
|
||||
'zip': '12345',
|
||||
'country_id': self.country_id,
|
||||
'phone': '+33123456789',
|
||||
'partner_id': imported_partner.id,
|
||||
'address_type': 'delivery',
|
||||
'use_delivery_as_billing': 'true',
|
||||
}
|
||||
res = self.WebsiteSaleController.shop_address_submit(**values).data
|
||||
self.assertIsNotNone(json.loads(res).get('redirectUrl'), "We should get a 'redirectUrl' in the response")
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.tests import tagged
|
||||
|
||||
from odoo.addons.website_sale.tests.common import WebsiteSaleCommon
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestWSaleCommon(WebsiteSaleCommon):
|
||||
|
||||
def test_common(self):
|
||||
self.assertEqual(self.env.company, self.website.company_id)
|
||||
self.assertEqual(self.pricelist.currency_id, self.env.company.currency_id)
|
||||
|
|
@ -1,53 +1,56 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import base64
|
||||
|
||||
from odoo.addons.base.tests.common import HttpCaseWithUserDemo, HttpCaseWithUserPortal
|
||||
from odoo.modules.module import get_module_resource
|
||||
from odoo.fields import Command
|
||||
from odoo.tests import tagged
|
||||
|
||||
from odoo.addons.base.tests.common import HttpCaseWithUserDemo, HttpCaseWithUserPortal
|
||||
from odoo.addons.sale.tests.product_configurator_common import TestProductConfiguratorCommon
|
||||
from odoo.addons.website.tests.common import HttpCaseWithWebsiteUser
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestUi(HttpCaseWithUserDemo, HttpCaseWithUserPortal):
|
||||
class TestCustomize(HttpCaseWithUserDemo, HttpCaseWithUserPortal, TestProductConfiguratorCommon, HttpCaseWithWebsiteUser):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.env.company.country_id = self.env.ref('base.us')
|
||||
# create a template
|
||||
product_template = self.env['product.template'].create({
|
||||
'name': 'Test Product',
|
||||
'is_published': True,
|
||||
'list_price': 750,
|
||||
})
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.env.company.country_id = cls.env.ref('base.us')
|
||||
|
||||
tax = self.env['account.tax'].create({'name': "Test tax", 'amount': 10})
|
||||
product_template.taxes_id = tax
|
||||
|
||||
product_attribute = self.env['product.attribute'].create({
|
||||
product_attribute = cls.env['product.attribute'].create({
|
||||
'name': 'Legs',
|
||||
'visibility': 'visible',
|
||||
'sequence': 10,
|
||||
'value_ids': [
|
||||
Command.create({
|
||||
'name': 'Steel - Test',
|
||||
'sequence': 1,
|
||||
}),
|
||||
Command.create({
|
||||
'name': 'Aluminium',
|
||||
'sequence': 2,
|
||||
}),
|
||||
]
|
||||
})
|
||||
product_attribute_value_1 = self.env['product.attribute.value'].create({
|
||||
'name': 'Steel - Test',
|
||||
'attribute_id': product_attribute.id,
|
||||
'sequence': 1,
|
||||
})
|
||||
product_attribute_value_2 = self.env['product.attribute.value'].create({
|
||||
'name': 'Aluminium',
|
||||
'attribute_id': product_attribute.id,
|
||||
'sequence': 2,
|
||||
# create a template
|
||||
product_template = cls.env['product.template'].create({
|
||||
'name': 'Test Product',
|
||||
'is_published': True,
|
||||
'list_price': 750,
|
||||
'attribute_line_ids': [
|
||||
Command.create({
|
||||
'attribute_id': product_attribute.id,
|
||||
'value_ids': [Command.set(product_attribute.value_ids.ids)]
|
||||
})
|
||||
],
|
||||
})
|
||||
|
||||
# set attribute and attribute values on the template
|
||||
self.env['product.template.attribute.line'].create([{
|
||||
'attribute_id': product_attribute.id,
|
||||
'product_tmpl_id': product_template.id,
|
||||
'value_ids': [(6, 0, [product_attribute_value_1.id, product_attribute_value_2.id])]
|
||||
}])
|
||||
tax = cls.env['account.tax'].create({'name': "Test tax", 'amount': 10})
|
||||
product_template.taxes_id = tax
|
||||
|
||||
# set a different price on the variants to differentiate them
|
||||
product_template_attribute_values = self.env['product.template.attribute.value'] \
|
||||
.search([('product_tmpl_id', '=', product_template.id)])
|
||||
product_template_attribute_values = cls.env['product.template.attribute.value'].search([
|
||||
('product_tmpl_id', '=', product_template.id),
|
||||
])
|
||||
|
||||
for ptav in product_template_attribute_values:
|
||||
if ptav.name == "Steel - Test":
|
||||
|
|
@ -55,114 +58,42 @@ class TestUi(HttpCaseWithUserDemo, HttpCaseWithUserPortal):
|
|||
else:
|
||||
ptav.price_extra = 50.4
|
||||
|
||||
# Update the pricelist currency regarding env.company_id currency_id in case company has changed currency with COA installation.
|
||||
website = self.env['website'].get_current_website()
|
||||
pricelist = website.get_current_pricelist()
|
||||
pricelist.write({'currency_id': self.env.company.currency_id.id})
|
||||
# Ensure that no pricelist is available during the test.
|
||||
# This ensures that tours which triggers on the amounts will run properly, and that the
|
||||
# currency will be the company currency.
|
||||
cls.env['product.pricelist'].action_archive()
|
||||
|
||||
def test_01_admin_shop_customize_tour(self):
|
||||
# Enable Variant Group
|
||||
self.env.ref('product.group_product_variant').write({'users': [(4, self.env.ref('base.user_admin').id)]})
|
||||
self.start_tour(self.env['website'].get_client_action_url('/shop?search=Test Product'), 'shop_customize', login="admin", timeout=120)
|
||||
def test_01_admin_shop_custom_attribute_value_tour(self):
|
||||
self.env.user.write(
|
||||
{'group_ids': [Command.link(self.env.ref('product.group_product_pricelist').id)]}
|
||||
)
|
||||
self.env['product.pricelist'].create({
|
||||
'name': 'Custom pricelist (TEST)',
|
||||
'sequence': 4,
|
||||
'item_ids': [(0, 0, {
|
||||
'base': 'list_price',
|
||||
'applied_on': '1_product',
|
||||
'product_tmpl_id': self.product_product_custo_desk.id,
|
||||
'price_discount': 20,
|
||||
'min_quantity': 2,
|
||||
'compute_price': 'formula'
|
||||
})]
|
||||
})
|
||||
self.start_tour("/", 'a_shop_custom_attribute_value', login="admin")
|
||||
|
||||
def test_02_admin_shop_custom_attribute_value_tour(self):
|
||||
# Make sure pricelist rule exist
|
||||
self.product_attribute_1 = self.env['product.attribute'].create({
|
||||
'name': 'Legs',
|
||||
'sequence': 10,
|
||||
self.env['product.pricelist'].create({
|
||||
'name': 'Base Pricelist',
|
||||
'item_ids': [Command.create({
|
||||
'base': 'list_price',
|
||||
'applied_on': '1_product',
|
||||
'product_tmpl_id': self.product_product_custo_desk.id,
|
||||
'price_discount': 20,
|
||||
'min_quantity': 2,
|
||||
'compute_price': 'formula',
|
||||
})],
|
||||
})
|
||||
product_attribute_value_1 = self.env['product.attribute.value'].create({
|
||||
'name': 'Steel',
|
||||
'attribute_id': self.product_attribute_1.id,
|
||||
'sequence': 1,
|
||||
})
|
||||
product_attribute_value_2 = self.env['product.attribute.value'].create({
|
||||
'name': 'Aluminium',
|
||||
'attribute_id': self.product_attribute_1.id,
|
||||
'sequence': 2,
|
||||
})
|
||||
product_attribute_2 = self.env['product.attribute'].create({
|
||||
'name': 'Color',
|
||||
'sequence': 20,
|
||||
})
|
||||
product_attribute_value_3 = self.env['product.attribute.value'].create({
|
||||
'name': 'White',
|
||||
'attribute_id': product_attribute_2.id,
|
||||
'sequence': 1,
|
||||
})
|
||||
product_attribute_value_4 = self.env['product.attribute.value'].create({
|
||||
'name': 'Black',
|
||||
'attribute_id': product_attribute_2.id,
|
||||
'sequence': 2,
|
||||
})
|
||||
|
||||
# Create product template
|
||||
self.product_product_4_product_template = self.env['product.template'].create({
|
||||
'name': 'Customizable Desk (TEST)',
|
||||
'standard_price': 500.0,
|
||||
'list_price': 750.0,
|
||||
})
|
||||
|
||||
# Generate variants
|
||||
self.env['product.template.attribute.line'].create([{
|
||||
'product_tmpl_id': self.product_product_4_product_template.id,
|
||||
'attribute_id': self.product_attribute_1.id,
|
||||
'value_ids': [(4, product_attribute_value_1.id), (4, product_attribute_value_2.id)],
|
||||
}, {
|
||||
'product_tmpl_id': self.product_product_4_product_template.id,
|
||||
'attribute_id': product_attribute_2.id,
|
||||
'value_ids': [(4, product_attribute_value_3.id), (4, product_attribute_value_4.id)],
|
||||
|
||||
}])
|
||||
product_template = self.product_product_4_product_template
|
||||
|
||||
# Add Custom Attribute
|
||||
product_attribute_value_7 = self.env['product.attribute.value'].create({
|
||||
'name': 'Custom TEST',
|
||||
'attribute_id': self.product_attribute_1.id,
|
||||
'sequence': 3,
|
||||
'is_custom': True
|
||||
})
|
||||
self.product_product_4_product_template.attribute_line_ids[0].write({'value_ids': [(4, product_attribute_value_7.id)]})
|
||||
|
||||
img_path = get_module_resource('product', 'static', 'img', 'product_product_11-image.png')
|
||||
img_content = base64.b64encode(open(img_path, "rb").read())
|
||||
self.product_product_11_product_template = self.env['product.template'].create({
|
||||
'name': 'Conference Chair (TEST)',
|
||||
'website_sequence': 9999, # laule
|
||||
'image_1920': img_content,
|
||||
'list_price': 16.50,
|
||||
})
|
||||
|
||||
self.env['product.template.attribute.line'].create({
|
||||
'product_tmpl_id': self.product_product_11_product_template.id,
|
||||
'attribute_id': self.product_attribute_1.id,
|
||||
'value_ids': [(4, product_attribute_value_1.id), (4, product_attribute_value_2.id)],
|
||||
})
|
||||
self.product_product_11_product_template.attribute_line_ids[0].product_template_value_ids[1].price_extra = 6.40
|
||||
|
||||
# Setup a second optional product
|
||||
self.product_product_1_product_template = self.env['product.template'].create({
|
||||
'name': 'Chair floor protection',
|
||||
'list_price': 12.0,
|
||||
})
|
||||
|
||||
# fix runbot, sometimes one pricelist is chosen, sometimes the other...
|
||||
pricelists = self.env['website'].get_current_website().get_current_pricelist() | self.env.ref('product.list0')
|
||||
|
||||
for pricelist in pricelists:
|
||||
if not pricelist.item_ids.filtered(lambda i: i.product_tmpl_id == product_template and i.price_discount == 20):
|
||||
self.env['product.pricelist.item'].create({
|
||||
'base': 'list_price',
|
||||
'applied_on': '1_product',
|
||||
'pricelist_id': pricelist.id,
|
||||
'product_tmpl_id': product_template.id,
|
||||
'price_discount': 20,
|
||||
'min_quantity': 2,
|
||||
'compute_price': 'formula',
|
||||
})
|
||||
|
||||
pricelist.discount_policy = 'without_discount'
|
||||
|
||||
self.start_tour("/", 'shop_custom_attribute_value', login="admin")
|
||||
|
||||
|
|
@ -170,41 +101,32 @@ class TestUi(HttpCaseWithUserDemo, HttpCaseWithUserPortal):
|
|||
""" The goal of this test is to make sure product variants with dynamic
|
||||
attributes can be created by the public user (when being added to cart).
|
||||
"""
|
||||
|
||||
# create the attribute
|
||||
product_attribute = self.env['product.attribute'].create({
|
||||
'name': "Dynamic Attribute",
|
||||
'create_variant': 'dynamic',
|
||||
'value_ids': [
|
||||
Command.create({'name': "Dynamic Value 1"}),
|
||||
Command.create({'name': "Dynamic Value 2"}),
|
||||
]
|
||||
})
|
||||
|
||||
# create the attribute values
|
||||
product_attribute_values = self.env['product.attribute.value'].create([{
|
||||
'name': "Dynamic Value 1",
|
||||
'attribute_id': product_attribute.id,
|
||||
'sequence': 1,
|
||||
}, {
|
||||
'name': "Dynamic Value 2",
|
||||
'attribute_id': product_attribute.id,
|
||||
'sequence': 2,
|
||||
}])
|
||||
|
||||
# create the template
|
||||
product_template = self.env['product.template'].create({
|
||||
'name': 'Dynamic Product',
|
||||
'website_published': True,
|
||||
'list_price': 10,
|
||||
'attribute_line_ids': [
|
||||
Command.create({
|
||||
'attribute_id': product_attribute.id,
|
||||
'value_ids': [Command.set(product_attribute.value_ids.ids)]
|
||||
})
|
||||
]
|
||||
})
|
||||
|
||||
# set attribute and attribute values on the template
|
||||
self.env['product.template.attribute.line'].create([{
|
||||
'attribute_id': product_attribute.id,
|
||||
'product_tmpl_id': product_template.id,
|
||||
'value_ids': [(6, 0, product_attribute_values.ids)]
|
||||
}])
|
||||
|
||||
# set a different price on the variants to differentiate them
|
||||
product_template_attribute_values = self.env['product.template.attribute.value'] \
|
||||
.search([('product_tmpl_id', '=', product_template.id)])
|
||||
product_template_attribute_values = self.env['product.template.attribute.value'].search([
|
||||
('product_tmpl_id', '=', product_template.id),
|
||||
])
|
||||
|
||||
for ptav in product_template_attribute_values:
|
||||
if ptav.name == "Dynamic Value 1":
|
||||
|
|
@ -226,39 +148,29 @@ class TestUi(HttpCaseWithUserDemo, HttpCaseWithUserPortal):
|
|||
product_attribute = self.env['product.attribute'].create({
|
||||
'name': "My Attribute",
|
||||
'create_variant': 'always',
|
||||
'value_ids': [
|
||||
Command.create({'name': "My Value 1"}),
|
||||
Command.create({'name': "My Value 2"}),
|
||||
Command.create({'name': "My Value 3"}),
|
||||
],
|
||||
})
|
||||
|
||||
# create the attribute values
|
||||
product_attribute_values = self.env['product.attribute.value'].create([{
|
||||
'name': "My Value 1",
|
||||
'attribute_id': product_attribute.id,
|
||||
'sequence': 1,
|
||||
}, {
|
||||
'name': "My Value 2",
|
||||
'attribute_id': product_attribute.id,
|
||||
'sequence': 2,
|
||||
}, {
|
||||
'name': "My Value 3",
|
||||
'attribute_id': product_attribute.id,
|
||||
'sequence': 3,
|
||||
}])
|
||||
|
||||
# create the template
|
||||
product_template = self.env['product.template'].create({
|
||||
'name': 'Test Product 2',
|
||||
'is_published': True,
|
||||
'attribute_line_ids': [
|
||||
Command.create({
|
||||
'attribute_id': product_attribute.id,
|
||||
'value_ids': [Command.set(product_attribute.value_ids.ids)]
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
# set attribute and attribute values on the template
|
||||
self.env['product.template.attribute.line'].create([{
|
||||
'attribute_id': product_attribute.id,
|
||||
'product_tmpl_id': product_template.id,
|
||||
'value_ids': [(6, 0, product_attribute_values.ids)]
|
||||
}])
|
||||
|
||||
# set a different price on the variants to differentiate them
|
||||
product_template_attribute_values = self.env['product.template.attribute.value'] \
|
||||
.search([('product_tmpl_id', '=', product_template.id)])
|
||||
product_template_attribute_values = self.env['product.template.attribute.value'].search([
|
||||
('product_tmpl_id', '=', product_template.id),
|
||||
])
|
||||
|
||||
product_template_attribute_values[0].price_extra = 10
|
||||
product_template_attribute_values[1].price_extra = 20
|
||||
|
|
@ -278,57 +190,47 @@ class TestUi(HttpCaseWithUserDemo, HttpCaseWithUserPortal):
|
|||
Using "demo" to have various users in the tests.
|
||||
"""
|
||||
|
||||
# create the attribute
|
||||
product_attribute_no_variant = self.env['product.attribute'].create({
|
||||
'name': "No Variant Attribute",
|
||||
'create_variant': 'no_variant',
|
||||
})
|
||||
|
||||
# create the attribute value
|
||||
product_attribute_value_no_variant = self.env['product.attribute.value'].create({
|
||||
'name': "No Variant Value",
|
||||
'attribute_id': product_attribute_no_variant.id,
|
||||
'value_ids': [Command.create({'name': "No Variant Value"})],
|
||||
})
|
||||
|
||||
# create the template
|
||||
product_template = self.env['product.template'].create({
|
||||
'name': 'Test Product 3',
|
||||
'website_published': True,
|
||||
'attribute_line_ids': [
|
||||
Command.create({
|
||||
'attribute_id': product_attribute_no_variant.id,
|
||||
'value_ids': [Command.set(product_attribute_no_variant.value_ids.ids)]
|
||||
})
|
||||
]
|
||||
})
|
||||
|
||||
# set attribute and attribute value on the template
|
||||
ptal = self.env['product.template.attribute.line'].create([{
|
||||
'attribute_id': product_attribute_no_variant.id,
|
||||
'product_tmpl_id': product_template.id,
|
||||
'value_ids': [(6, 0, product_attribute_value_no_variant.ids)]
|
||||
}])
|
||||
|
||||
# set a price on the value
|
||||
ptal.product_template_value_ids.price_extra = 10
|
||||
product_template.attribute_line_ids.product_template_value_ids.price_extra = 10
|
||||
|
||||
self.start_tour("/", 'tour_shop_no_variant_attribute', login="demo")
|
||||
|
||||
def test_06_admin_list_view_b2c(self):
|
||||
self.env.ref('product.group_product_variant').write({'users': [(4, self.env.ref('base.user_admin').id)]})
|
||||
|
||||
# activate b2c
|
||||
config = self.env['res.config.settings'].create({})
|
||||
config.show_line_subtotals_tax_selection = "tax_included"
|
||||
config.execute()
|
||||
|
||||
self.start_tour(self.env['website'].get_client_action_url('/shop?search=Test Product'), 'shop_list_view_b2c', login="admin")
|
||||
sol = self.env['sale.order.line'].search([
|
||||
('product_id', '=', product_template.product_variant_id.id)
|
||||
])
|
||||
self.assertTrue(sol)
|
||||
self.assertEqual(
|
||||
sol.product_no_variant_attribute_value_ids,
|
||||
product_template.attribute_line_ids.product_template_value_ids
|
||||
)
|
||||
|
||||
def test_07_editor_shop(self):
|
||||
self.env["product.pricelist"].create({
|
||||
"name": "EUR Pricelist",
|
||||
"selectable": True,
|
||||
"website_id": self.env.ref("website.default_website").id,
|
||||
"country_group_ids": [(4, self.env.ref('base.europe').id)],
|
||||
"sequence": 3,
|
||||
"currency_id": self.env.ref("base.EUR").id,
|
||||
})
|
||||
|
||||
self.start_tour("/", 'shop_editor', login="admin")
|
||||
self.env.user.write(
|
||||
{'group_ids': [Command.link(self.env.ref('product.group_product_pricelist').id)]}
|
||||
)
|
||||
self.env['product.pricelist'].create([
|
||||
{'name': 'Base Pricelist', 'selectable': True},
|
||||
{'name': 'Other Pricelist', 'selectable': True}
|
||||
])
|
||||
self.start_tour("/", 'shop_editor', login="website_user")
|
||||
|
||||
def test_08_portal_tour_archived_variant_multiple_attributes(self):
|
||||
"""The goal of this test is to make sure that an archived variant with multiple
|
||||
|
|
@ -337,78 +239,52 @@ class TestUi(HttpCaseWithUserDemo, HttpCaseWithUserPortal):
|
|||
Using "portal" to have various users in the tests.
|
||||
"""
|
||||
|
||||
attribute_1, attribute_2, attribute_3 = self.env['product.attribute'].create([
|
||||
attributes = self.env['product.attribute'].create([
|
||||
{
|
||||
'name': 'Size',
|
||||
'create_variant': 'always',
|
||||
'value_ids': [
|
||||
Command.create({'name': 'Large'}),
|
||||
Command.create({'name': 'Small'}),
|
||||
],
|
||||
},
|
||||
{
|
||||
'name': 'Color',
|
||||
'create_variant': 'always',
|
||||
'value_ids': [
|
||||
Command.create({'name': 'White'}),
|
||||
Command.create({'name': 'Black'}),
|
||||
],
|
||||
},
|
||||
{
|
||||
'name': 'Brand',
|
||||
'create_variant': 'always',
|
||||
'value_ids': [
|
||||
Command.create({'name': 'Brand A'}),
|
||||
Command.create({'name': 'Brand B'}),
|
||||
],
|
||||
},
|
||||
])
|
||||
|
||||
attribute_values = self.env['product.attribute.value'].create([
|
||||
{
|
||||
'name': 'Large',
|
||||
'attribute_id': attribute_1.id,
|
||||
'sequence': 1,
|
||||
},
|
||||
{
|
||||
'name': 'Small',
|
||||
'attribute_id': attribute_1.id,
|
||||
'sequence': 2,
|
||||
},
|
||||
{
|
||||
'name': 'White',
|
||||
'attribute_id': attribute_2.id,
|
||||
'sequence': 1,
|
||||
},
|
||||
{
|
||||
'name': 'Black',
|
||||
'attribute_id': attribute_2.id,
|
||||
'sequence': 2,
|
||||
},
|
||||
{
|
||||
'name': 'Brand A',
|
||||
'attribute_id': attribute_3.id,
|
||||
'sequence': 1,
|
||||
},
|
||||
{
|
||||
'name': 'Brand B',
|
||||
'attribute_id': attribute_3.id,
|
||||
'sequence': 2,
|
||||
},
|
||||
])
|
||||
|
||||
product_template = self.env['product.template'].create({
|
||||
'name': 'Test Product 2',
|
||||
'is_published': True,
|
||||
'attribute_line_ids': [
|
||||
Command.create({
|
||||
'attribute_id': attribute.id,
|
||||
'value_ids': [Command.set(attribute.value_ids.ids)],
|
||||
}) for attribute in attributes
|
||||
],
|
||||
})
|
||||
|
||||
self.env['product.template.attribute.line'].create([
|
||||
{
|
||||
'attribute_id': attribute_1.id,
|
||||
'product_tmpl_id': product_template.id,
|
||||
'value_ids': [(6, 0, attribute_values.filtered(lambda v: v.attribute_id == attribute_1).ids)],
|
||||
},
|
||||
{
|
||||
'attribute_id': attribute_2.id,
|
||||
'product_tmpl_id': product_template.id,
|
||||
'value_ids': [(6, 0, attribute_values.filtered(lambda v: v.attribute_id == attribute_2).ids)],
|
||||
},
|
||||
{
|
||||
'attribute_id': attribute_3.id,
|
||||
'product_tmpl_id': product_template.id,
|
||||
'value_ids': [(6, 0, attribute_values.filtered(lambda v: v.attribute_id == attribute_3).ids)],
|
||||
},
|
||||
])
|
||||
|
||||
product_template.product_variant_ids[-1].active = False
|
||||
# Archive (Small, Black, Brand B) variant
|
||||
combination_to_archive = product_template.attribute_line_ids.product_template_value_ids.filtered(
|
||||
lambda ptav: ptav.product_attribute_value_id.name in ('Small', 'Black', 'Brand B')
|
||||
)
|
||||
variant_to_archive = product_template._get_variant_for_combination(
|
||||
combination_to_archive
|
||||
)
|
||||
self.assertTrue(variant_to_archive)
|
||||
variant_to_archive.action_archive()
|
||||
self.assertFalse(variant_to_archive.active)
|
||||
|
||||
self.start_tour("/", 'tour_shop_archived_variant_multi', login="portal")
|
||||
|
||||
|
|
@ -419,41 +295,127 @@ class TestUi(HttpCaseWithUserDemo, HttpCaseWithUserPortal):
|
|||
Using "portal" to have various users in the tests.
|
||||
"""
|
||||
|
||||
attribute_1 = self.env['product.attribute'].create([
|
||||
{
|
||||
'name': 'Size',
|
||||
'create_variant': 'always',
|
||||
'display_type': 'pills',
|
||||
},
|
||||
])
|
||||
|
||||
attribute_values = self.env['product.attribute.value'].create([
|
||||
{
|
||||
'name': 'Large',
|
||||
'attribute_id': attribute_1.id,
|
||||
'sequence': 1,
|
||||
},
|
||||
{
|
||||
'name': 'Small',
|
||||
'attribute_id': attribute_1.id,
|
||||
'sequence': 2,
|
||||
},
|
||||
])
|
||||
|
||||
product_template = self.env['product.template'].create({
|
||||
'name': 'Test Product 2',
|
||||
'is_published': True,
|
||||
attribute_size = self.env['product.attribute'].create({
|
||||
'name': 'Size',
|
||||
'create_variant': 'always',
|
||||
'display_type': 'pills',
|
||||
'value_ids': [
|
||||
Command.create({'name': 'Large'}),
|
||||
Command.create({'name': 'Small'}),
|
||||
],
|
||||
})
|
||||
|
||||
self.env['product.template.attribute.line'].create([
|
||||
{
|
||||
'attribute_id': attribute_1.id,
|
||||
'product_tmpl_id': product_template.id,
|
||||
'value_ids': [(6, 0, attribute_values.ids)],
|
||||
},
|
||||
])
|
||||
self.env['product.template'].create({
|
||||
'name': 'Test Product 2',
|
||||
'is_published': True,
|
||||
'attribute_line_ids': [
|
||||
Command.create({
|
||||
'attribute_id': attribute_size.id,
|
||||
'value_ids': [Command.set(attribute_size.value_ids.ids)],
|
||||
})
|
||||
],
|
||||
})
|
||||
|
||||
self.start_tour("/", 'test_09_pills_variant', login="portal")
|
||||
|
||||
def test_10_shop_editor_set_product_ribbon(self):
|
||||
def test_10_multi_checkbox_attribute(self):
|
||||
attribute = self.env['product.attribute'].create([
|
||||
{
|
||||
'name': 'Options',
|
||||
'create_variant': 'no_variant',
|
||||
'display_type': 'multi',
|
||||
'value_ids': [
|
||||
Command.create({
|
||||
'name': 'Option 1',
|
||||
'default_extra_price': 1,
|
||||
'sequence': 1,
|
||||
}),
|
||||
Command.create({
|
||||
'name': 'Option 2',
|
||||
'sequence': 2,
|
||||
}),
|
||||
Command.create({
|
||||
'name': 'Option 3',
|
||||
'default_extra_price': 3,
|
||||
'sequence': 3,
|
||||
}),
|
||||
Command.create({
|
||||
'name': 'Option 4',
|
||||
'sequence': 4,
|
||||
}),
|
||||
],
|
||||
},
|
||||
])
|
||||
product_template = self.env['product.template'].create({
|
||||
'name': 'Product Multi',
|
||||
'is_published': True,
|
||||
'list_price': 750,
|
||||
'attribute_line_ids': [
|
||||
Command.create({
|
||||
'attribute_id': attribute.id,
|
||||
'value_ids': [Command.set(attribute.value_ids.ids)],
|
||||
}),
|
||||
],
|
||||
})
|
||||
# set an extra price for free attribute values on the product (nothing is free)
|
||||
self.env['product.template.attribute.value'].search([
|
||||
('product_tmpl_id', '=', product_template.id),
|
||||
('price_extra', '=', 0),
|
||||
]).price_extra = 2
|
||||
# set an exclusion between option 1 and option 3
|
||||
self.env['product.template.attribute.value'].search([
|
||||
('product_tmpl_id', '=', product_template.id),
|
||||
('price_extra', '=', 1),
|
||||
]).exclude_for = [
|
||||
Command.create({
|
||||
'product_tmpl_id': product_template.id,
|
||||
'value_ids': [Command.set(
|
||||
self.env['product.template.attribute.value'].search([
|
||||
('product_tmpl_id', '=', product_template.id),
|
||||
('price_extra', '=', 3)
|
||||
]).ids
|
||||
)],
|
||||
}),
|
||||
]
|
||||
|
||||
self.start_tour("/", 'tour_shop_multi_checkbox', login="portal")
|
||||
|
||||
def test_11_shop_editor_set_product_ribbon(self):
|
||||
self.start_tour("/", 'shop_editor_set_product_ribbon', login="admin")
|
||||
|
||||
def test_12_multi_checkbox_attribute_single_value(self):
|
||||
attribute = self.env['product.attribute'].create([
|
||||
{
|
||||
'name': 'Toppings',
|
||||
'create_variant': 'no_variant',
|
||||
'display_type': 'multi',
|
||||
'value_ids': [(0, 0, {'name': 'cheese'})],
|
||||
},
|
||||
])
|
||||
self.env['product.template'].create({
|
||||
'name': 'Burger',
|
||||
'is_published': True,
|
||||
'list_price': 750,
|
||||
'attribute_line_ids': [
|
||||
Command.create({
|
||||
'attribute_id': attribute.id,
|
||||
'value_ids': [(6, 0, attribute.value_ids.ids)],
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
self.start_tour("/", 'tour_shop_multi_checkbox_single_value', login="website_user")
|
||||
|
||||
def test_shop_editor_no_alternative_products_visibility(self):
|
||||
product_no_alternative = self.env['product.template'].create({
|
||||
'name': 'product_without_alternative',
|
||||
'is_published': True,
|
||||
})
|
||||
product_with_alternative = self.env['product.template'].create({
|
||||
'name': 'product_with_alternative',
|
||||
'is_published': True,
|
||||
'alternative_product_ids': product_no_alternative.ids,
|
||||
})
|
||||
product_no_alternative.set_sequence_top()
|
||||
product_with_alternative.set_sequence_top()
|
||||
self.start_tour('/', 'shop_editor_no_alternative_products_visibility_tour', login="admin")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,82 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.fields import Command
|
||||
from odoo.tests import tagged
|
||||
|
||||
from odoo.addons.payment.tests.common import PaymentCommon
|
||||
from odoo.addons.website_sale.controllers.delivery import Delivery
|
||||
from odoo.addons.website_sale.tests.common import MockRequest, WebsiteSaleCommon
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestWebsiteSaleDeliveryController(PaymentCommon, WebsiteSaleCommon):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.Controller = Delivery()
|
||||
|
||||
# test that changing the carrier while there is a pending transaction raises an error
|
||||
def test_controller_change_carrier_when_transaction(self):
|
||||
website = self.website.with_env(self.env)
|
||||
self.empty_cart.transaction_ids = self._create_transaction(flow='redirect', state='pending')
|
||||
with MockRequest(website.env, website=website, sale_order_id=self.empty_cart.id) as request, self.assertRaises(UserError):
|
||||
request.cart = self.empty_cart
|
||||
self.Controller.shop_set_delivery_method(dm_id=self.free_delivery.id)
|
||||
|
||||
# test that changing the carrier while there is a draft transaction doesn't raise an error
|
||||
def test_controller_change_carrier_when_draft_transaction(self):
|
||||
website = self.website.with_env(self.env)
|
||||
self.empty_cart.transaction_ids = self._create_transaction(flow='redirect', state='draft')
|
||||
with MockRequest(website.env, website=website, sale_order_id=self.empty_cart.id):
|
||||
self.Controller.shop_set_delivery_method(dm_id=self.free_delivery.id)
|
||||
|
||||
def test_available_methods(self):
|
||||
self.env['delivery.carrier'].search([]).action_archive()
|
||||
self.product_delivery_poste = self.env['product.product'].create({
|
||||
'name': 'The Poste',
|
||||
'type': 'service',
|
||||
'categ_id': self.env.ref('delivery.product_category_deliveries').id,
|
||||
'sale_ok': False,
|
||||
'purchase_ok': False,
|
||||
'list_price': 20.0,
|
||||
})
|
||||
self.env['delivery.carrier'].create([
|
||||
{
|
||||
'name': 'Over 300',
|
||||
'delivery_type': 'base_on_rule',
|
||||
'product_id': self.product_delivery_poste.id,
|
||||
'website_published': True,
|
||||
'price_rule_ids': [
|
||||
Command.create({
|
||||
'operator': '>=',
|
||||
'max_value': 300,
|
||||
'variable': 'price',
|
||||
}),
|
||||
],
|
||||
}, {
|
||||
'name': 'Under 300',
|
||||
'delivery_type': 'base_on_rule',
|
||||
'product_id': self.product_delivery_poste.id,
|
||||
'website_published': True,
|
||||
'price_rule_ids': [
|
||||
Command.create({
|
||||
'operator': '<',
|
||||
'max_value': 300,
|
||||
'variable': 'price',
|
||||
}),
|
||||
],
|
||||
}, {
|
||||
'name': 'No rules',
|
||||
'delivery_type': 'base_on_rule',
|
||||
'product_id': self.product_delivery_poste.id,
|
||||
'website_published': True,
|
||||
}, {
|
||||
'name': 'Fixed',
|
||||
'product_id': self.product_delivery_poste.id,
|
||||
'website_published': True,
|
||||
},
|
||||
])
|
||||
|
||||
self.assertEqual(
|
||||
self.empty_cart._get_delivery_methods().mapped('name'), ['Under 300', 'Fixed']
|
||||
)
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import odoo.tests
|
||||
from odoo.fields import Command
|
||||
|
||||
|
||||
@odoo.tests.tagged('post_install', '-at_install')
|
||||
class TestUi(odoo.tests.HttpCase):
|
||||
def test_01_free_delivery_when_exceed_threshold(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.write({
|
||||
'state': 'enabled',
|
||||
'is_published': True,
|
||||
})
|
||||
transfer_provider._transfer_ensure_pending_msg_is_set()
|
||||
|
||||
# Avoid Shipping/Billing address page
|
||||
self.env.ref('base.partner_admin').write({
|
||||
'street': '215 Vine St',
|
||||
'city': 'Scranton',
|
||||
'zip': '18503',
|
||||
'country_id': self.env.ref('base.us').id,
|
||||
'state_id': self.env.ref('base.state_us_39').id,
|
||||
'phone': '+1 555-555-5555',
|
||||
'email': 'admin@yourcompany.example.com',
|
||||
})
|
||||
|
||||
self.env['product.product'].create({
|
||||
'name': 'Office Chair Black TEST',
|
||||
'list_price': 12.50,
|
||||
})
|
||||
self.env.ref("delivery.free_delivery_carrier").write({
|
||||
'name': 'Delivery Now Free Over 10',
|
||||
'fixed_price': 2,
|
||||
'free_over': True,
|
||||
'amount': 10,
|
||||
})
|
||||
self.product_delivery_poste = self.env['product.product'].create({
|
||||
'name': 'The Poste',
|
||||
'type': 'service',
|
||||
'categ_id': self.env.ref('delivery.product_category_deliveries').id,
|
||||
'sale_ok': False,
|
||||
'purchase_ok': False,
|
||||
'list_price': 20.0,
|
||||
})
|
||||
self.carrier = self.env['delivery.carrier'].create({
|
||||
'name': 'The Poste',
|
||||
'sequence': 9999, # ensure last to load price async
|
||||
'fixed_price': 20.0,
|
||||
'delivery_type': 'base_on_rule',
|
||||
'product_id': self.product_delivery_poste.id,
|
||||
'website_published': True,
|
||||
'price_rule_ids': [
|
||||
Command.create({
|
||||
'max_value': 5,
|
||||
'list_base_price': 20,
|
||||
}),
|
||||
Command.create({
|
||||
'operator': '>=',
|
||||
'max_value': 5,
|
||||
'list_base_price': 50,
|
||||
}),
|
||||
Command.create({
|
||||
'operator': '>=',
|
||||
'max_value': 300,
|
||||
'variable': 'price',
|
||||
'list_base_price': 0,
|
||||
}),
|
||||
]
|
||||
})
|
||||
|
||||
self.start_tour("/", 'check_free_delivery', login="admin")
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo 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 TestDynamicSnippetCategory(WebsiteSaleCommon):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
Category = self.env['product.public.category']
|
||||
self.category1, self.category2, self.category3 = Category.create([
|
||||
{'name': "Published Category"},
|
||||
{'name': "Published Category 2"},
|
||||
{'name': "Unpublished Category"},
|
||||
])
|
||||
self.child_category = Category.create({
|
||||
'name': "Child category",
|
||||
'parent_id': self.category1.id,
|
||||
})
|
||||
self.env['product.template'].create({
|
||||
'name': "Test Product",
|
||||
'public_categ_ids': [
|
||||
Command.link(self.category1.id),
|
||||
Command.link(self.category2.id),
|
||||
Command.link(self.child_category.id),
|
||||
],
|
||||
'website_published': True,
|
||||
})
|
||||
self.website_sale = WebsiteSale()
|
||||
self.website = self.website.with_user(self.env.ref('base.user_admin'))
|
||||
|
||||
def test_snippet_categories_returns_only_published_and_with_children(self):
|
||||
categories = self.env['product.public.category'].get_available_snippet_categories(
|
||||
self.website.id,
|
||||
)
|
||||
category_ids = [c['id'] for c in categories]
|
||||
self.assertIn(self.category1.id, category_ids)
|
||||
|
||||
def test_set_category_image(self):
|
||||
"""Test setting a cover image via JSON-RPC route"""
|
||||
attachment = self.env['ir.attachment'].create({
|
||||
'name': "test.png",
|
||||
'datas': 'iVBORw0KGgoAAAANSUhEUgAAAAYAAAAGCAYAAADgzO9IAAAAJElEQVQI'
|
||||
'mWP4/b/qPzbM8Pt/1X8GBgaEAJTNgFcHXqOQMV4dAMmObXXo1/BqAAAA'
|
||||
'AElFTkSuQmCC',
|
||||
'public': True,
|
||||
})
|
||||
with MockRequest(self.website.env, website=self.website):
|
||||
self.website_sale.set_category_image(self.category1.id, attachment.id)
|
||||
self.assertEqual(
|
||||
self.category1.cover_image,
|
||||
attachment.datas,
|
||||
"Cover image should match the uploaded attachment",
|
||||
)
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.tests import tagged
|
||||
|
||||
from odoo.addons.base.tests.common import HttpCaseWithUserDemo
|
||||
from odoo.addons.website_sale.tests.common import WebsiteSaleCommon
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestEcommerceAccess(HttpCaseWithUserDemo, WebsiteSaleCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.filled_category, cls.empty_category = cls.env['product.public.category'].create([
|
||||
{'name': 'Category with Product', 'website_id': cls.website.id},
|
||||
{'name': 'Empty Category', 'website_id': cls.website.id},
|
||||
])
|
||||
|
||||
cls.env['product.public.category'].create([
|
||||
{
|
||||
'name': 'Has Products',
|
||||
'parent_id': cls.filled_category.id,
|
||||
'website_id': cls.website.id,
|
||||
},
|
||||
{
|
||||
'name': 'Empty Subcategory',
|
||||
'parent_id': cls.filled_category.id,
|
||||
'website_id': cls.website.id,
|
||||
},
|
||||
{
|
||||
'name': 'Empty Subcategory of Empty Category',
|
||||
'parent_id': cls.empty_category.id,
|
||||
'website_id': cls.website.id,
|
||||
},
|
||||
])
|
||||
|
||||
# Add one dummy product in one of the subcategories
|
||||
cls._create_product(public_categ_ids=[cls.filled_category.child_id[0].id])
|
||||
|
||||
def test_ecommerce_access_public_user(self):
|
||||
# By default, everyone has access to ecommerce
|
||||
self.assertTrue(self.website.with_user(self.public_user).has_ecommerce_access())
|
||||
self.website.ecommerce_access = 'logged_in'
|
||||
self.assertFalse(self.website.with_user(self.public_user).has_ecommerce_access())
|
||||
|
||||
def test_frontend_ecommerce_access_public_user(self):
|
||||
"""
|
||||
Ensures that the '/shop' URL returns a 200 OK status code even if categories are empty when
|
||||
not logged.
|
||||
"""
|
||||
self.quick_ref('website_sale.products_categories').active = True
|
||||
self.quick_ref('website_sale.option_collapse_products_categories').active = False
|
||||
|
||||
response = self.url_open('/shop')
|
||||
self.assertEqual(response.status_code, 200) # Check that customers can access
|
||||
|
||||
def test_ecommerce_access_logged_user(self):
|
||||
# By default, everyone has access to the ecommerce
|
||||
self.assertTrue(self.website.has_ecommerce_access())
|
||||
self.website.ecommerce_access = 'logged_in'
|
||||
# Check if logged-in users still have access to ecommerce after restricting it
|
||||
self.assertTrue(self.website.has_ecommerce_access())
|
||||
|
||||
def test_frontend_ecommerce_access_portal_user(self):
|
||||
"""
|
||||
Ensures that the '/shop' URL returns a 200 OK status code even if categories are empty when
|
||||
logged as portal user.
|
||||
"""
|
||||
self.quick_ref('website_sale.products_categories').active = True
|
||||
self.quick_ref('website_sale.option_collapse_products_categories').active = False
|
||||
portal_user = self._create_new_portal_user(website_id=self.website.id)
|
||||
|
||||
self.authenticate(portal_user.login, portal_user.login)
|
||||
response = self.url_open('/shop')
|
||||
self.assertEqual(response.status_code, 200) # Check that customers can access
|
||||
|
||||
def test_ecommerce_menu_visibility_public_user(self):
|
||||
self.menu = self.env['website.menu'].create({
|
||||
'name': 'Shop',
|
||||
'url': '/shop',
|
||||
'parent_id': self.website.menu_id.id,
|
||||
'sequence': 0,
|
||||
'website_id': self.website.id,
|
||||
})
|
||||
|
||||
# Check if by default public user can see shop menu
|
||||
self.menu.with_user(self.public_user).sudo()._compute_visible() # Needs to be sudoed as
|
||||
# public user can't access _compute_visible
|
||||
self.assertTrue(self.menu.is_visible)
|
||||
|
||||
self.website.ecommerce_access = 'logged_in'
|
||||
self.menu.with_user(self.public_user).sudo()._compute_visible()
|
||||
# Check if menu is hidden for public user when ecommerce is restricted
|
||||
self.assertFalse(self.menu.is_visible)
|
||||
|
||||
def test_ecommerce_access_shop_redirection(self):
|
||||
self.website.ecommerce_access = 'logged_in'
|
||||
self.authenticate(None, None)
|
||||
public_category = self.env['product.public.category'].create({
|
||||
'name': 'Test Category',
|
||||
})
|
||||
public_product_template = self.env['product.template'].create({
|
||||
'name': 'Test Template',
|
||||
'public_categ_ids': [public_category.id],
|
||||
'website_published': True,
|
||||
})
|
||||
category_slug = self.env["ir.http"]._slug(public_category)
|
||||
response = self.url_open(f'/shop/category/{category_slug}/page/1', allow_redirects=True)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertURLEqual(response.url, f'/web/login?redirect=/shop/category/{category_slug}/page/1')
|
||||
|
||||
product_slug = self.env["ir.http"]._slug(public_product_template)
|
||||
response = self.url_open(f'/shop/{product_slug}', allow_redirects=True)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertURLEqual(response.url, f'/web/login?redirect=/shop/{product_slug}')
|
||||
|
|
@ -1,19 +1,20 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import json
|
||||
from uuid import uuid4
|
||||
from werkzeug import urls
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
from odoo import Command
|
||||
from odoo.http import root
|
||||
from odoo.tests import tagged
|
||||
from odoo.tests import HttpCase, tagged
|
||||
from odoo.tools import urls
|
||||
|
||||
from odoo.addons.base.tests.common import HttpCaseWithUserDemo
|
||||
from odoo.addons.website_sale.controllers.main import WebsiteSale as WebsiteSaleController
|
||||
from odoo.addons.payment import utils as payment_utils
|
||||
from odoo.addons.website_sale.controllers.delivery import Delivery as WebsiteSaleDeliveryController
|
||||
from odoo.addons.website_sale.controllers.main import WebsiteSale
|
||||
from odoo.addons.website_sale.controllers.cart import Cart
|
||||
from odoo.addons.website_sale.tests.common import MockRequest, WebsiteSaleCommon
|
||||
|
||||
|
||||
@tagged('at_install')
|
||||
class TestWebsiteSaleExpressCheckoutFlows(HttpCaseWithUserDemo):
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestWebsiteSaleExpressCheckoutFlows(WebsiteSaleCommon, HttpCase):
|
||||
""" The goal of this method class is to test the address management on
|
||||
express checkout.
|
||||
"""
|
||||
|
|
@ -21,20 +22,9 @@ class TestWebsiteSaleExpressCheckoutFlows(HttpCaseWithUserDemo):
|
|||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.website = cls.env.ref('website.default_website')
|
||||
cls.country_id = cls.env.ref('base.be').id
|
||||
cls.sale_order = cls.env['sale.order'].create({
|
||||
'partner_id': cls.website.user_id.partner_id.id,
|
||||
'website_id': cls.website.id,
|
||||
'order_line': [Command.create({
|
||||
'product_id': cls.env['product.product'].create({
|
||||
'name': 'Product A',
|
||||
'list_price': 100,
|
||||
'website_published': True,
|
||||
'sale_ok': True}).id,
|
||||
'name': 'Product A',
|
||||
})]
|
||||
})
|
||||
cls.country_id = cls.country_be.id
|
||||
cls.sale_order = cls.cart
|
||||
cls.sale_order.partner_id = cls.public_partner.id
|
||||
cls.express_checkout_billing_values = {
|
||||
'name': 'Express Checkout Partner',
|
||||
'email': 'express@check.out',
|
||||
|
|
@ -43,17 +33,68 @@ class TestWebsiteSaleExpressCheckoutFlows(HttpCaseWithUserDemo):
|
|||
'street2': 'ppp',
|
||||
'city': 'ooo',
|
||||
'zip': '1200',
|
||||
'country': 'US',
|
||||
'state': 'WA',
|
||||
'country': "US", # United States
|
||||
'state': "CA", # California
|
||||
}
|
||||
cls.express_checkout_shipping_values = {
|
||||
'name': "Express Checkout Shipping Partner",
|
||||
'email': "express_shipping@check.out",
|
||||
'phone': "1111111111",
|
||||
'street': "ooo shipping",
|
||||
'street2': "ppp shipping",
|
||||
'city': "ooo shipping",
|
||||
'zip': "25781",
|
||||
'country': "US", # United States
|
||||
'state': "WA", # Washington
|
||||
}
|
||||
cls.express_checkout_anonymized_shipping_values = {
|
||||
'city': "ooo shipping",
|
||||
'zip': "6155",
|
||||
'country': "AU", # Australia
|
||||
'state': "WA", # Western Australia
|
||||
}
|
||||
cls.express_checkout_anonymized_shipping_values_2 = {
|
||||
'city': "ooo shipping 2",
|
||||
'zip': "11519",
|
||||
'country': "ES", # Spain
|
||||
'state': "CA", # Cádiz
|
||||
}
|
||||
|
||||
cls.user_demo = cls._create_new_internal_user(**cls.dummy_partner_address_values)
|
||||
|
||||
cls.express_checkout_demo_shipping_values = {
|
||||
'name': cls.user_demo.partner_id.name,
|
||||
'email': cls.user_demo.partner_id.email,
|
||||
'phone': cls.user_demo.partner_id.phone,
|
||||
'street': cls.user_demo.partner_id.street,
|
||||
'street2': cls.user_demo.partner_id.street2,
|
||||
'city': cls.user_demo.partner_id.city,
|
||||
'zip': cls.user_demo.partner_id.zip,
|
||||
'country': cls.user_demo.partner_id.country_id.code,
|
||||
'state': cls.user_demo.partner_id.state_id.code,
|
||||
}
|
||||
cls.express_checkout_anonymized_demo_shipping_values = {
|
||||
'city': cls.user_demo.partner_id.city,
|
||||
'zip': cls.user_demo.partner_id.zip,
|
||||
'country': cls.user_demo.partner_id.country_id.code,
|
||||
'state': cls.user_demo.partner_id.state_id.code,
|
||||
}
|
||||
cls.express_checkout_demo_shipping_values_2 = {
|
||||
'name': "Express Checkout Shipping Partner",
|
||||
'email': "express_shipping@check.out",
|
||||
'phone': "1111111111",
|
||||
'street': "ooo shipping",
|
||||
'street2': "ppp shipping",
|
||||
'city': cls.user_demo.partner_id.city,
|
||||
'zip': cls.user_demo.partner_id.zip,
|
||||
'country': cls.user_demo.partner_id.country_id.code,
|
||||
'state': cls.user_demo.partner_id.state_id.code,
|
||||
}
|
||||
cls.rate_shipment_result = {
|
||||
'success': True,
|
||||
'price': 5.0,
|
||||
'warning_message': "",
|
||||
}
|
||||
# Ensure demo user address exists and is valid
|
||||
cls.user_demo.write({
|
||||
'street': "215 Vine St",
|
||||
'city': "Scranton",
|
||||
'zip': "18503",
|
||||
'country_id': cls.env.ref('base.us').id,
|
||||
'state_id': cls.env.ref('base.state_us_39').id,
|
||||
})
|
||||
|
||||
def assertPartnerShippingValues(self, partner, shipping_values):
|
||||
for key, expected in shipping_values.items():
|
||||
|
|
@ -69,31 +110,17 @@ class TestWebsiteSaleExpressCheckoutFlows(HttpCaseWithUserDemo):
|
|||
"Partner's state should be within partner's country",
|
||||
)
|
||||
|
||||
def _make_json_rpc_request(self, url, data=None):
|
||||
""" Make a JSON-RPC request to the provided URL.
|
||||
|
||||
:param str url: The URL to make the request to
|
||||
:param dict data: The data to be send in the request body in JSON-RPC 2.0 format
|
||||
:return dict: The result of the JSON-RPC request
|
||||
"""
|
||||
rpc_request = {
|
||||
"jsonrpc": "2.0",
|
||||
"method": "call",
|
||||
"id": str(uuid4()),
|
||||
"params": data,
|
||||
}
|
||||
|
||||
result = self.url_open(
|
||||
url,
|
||||
data=json.dumps(rpc_request).encode(),
|
||||
headers={"Content-Type": "application/json"},
|
||||
timeout=None,
|
||||
def test_express_checkout_takes_order_amount_without_delivery(self):
|
||||
"""Test that the amount to pay does not include the delivery costs in express checkout."""
|
||||
amount_without_delivery = payment_utils.to_minor_currency_units(
|
||||
self.cart.amount_total, self.cart.currency_id
|
||||
)
|
||||
self.carrier.fixed_price = 20
|
||||
self.cart.set_delivery_line(self.carrier, self.carrier.fixed_price)
|
||||
with MockRequest(self.env, sale_order_id=self.cart.id, website=self.website):
|
||||
payment_values = Cart()._get_express_shop_payment_values(self.cart)
|
||||
|
||||
if not result.ok:
|
||||
return {}
|
||||
|
||||
return result.json().get("result", {})
|
||||
self.assertEqual(payment_values['minor_amount'], amount_without_delivery)
|
||||
|
||||
def test_express_checkout_public_user(self):
|
||||
""" Test that when using express checkout as a public user, a new partner is created. """
|
||||
|
|
@ -101,10 +128,10 @@ class TestWebsiteSaleExpressCheckoutFlows(HttpCaseWithUserDemo):
|
|||
session['sale_order_id'] = self.sale_order.id
|
||||
root.session_store.save(session)
|
||||
|
||||
self._make_json_rpc_request(
|
||||
urls.url_join(
|
||||
self.base_url(), WebsiteSaleController._express_checkout_route
|
||||
), data={
|
||||
self.make_jsonrpc_request(
|
||||
urls.urljoin(
|
||||
self.base_url(), WebsiteSale._express_checkout_route
|
||||
), params={
|
||||
'billing_address': dict(self.express_checkout_billing_values)
|
||||
}
|
||||
)
|
||||
|
|
@ -126,10 +153,10 @@ class TestWebsiteSaleExpressCheckoutFlows(HttpCaseWithUserDemo):
|
|||
session['sale_order_id'] = self.sale_order.id
|
||||
root.session_store.save(session)
|
||||
|
||||
self._make_json_rpc_request(
|
||||
urls.url_join(
|
||||
self.base_url(), WebsiteSaleController._express_checkout_route
|
||||
), data={
|
||||
self.make_jsonrpc_request(
|
||||
urls.urljoin(
|
||||
self.base_url(), WebsiteSale._express_checkout_route
|
||||
), params={
|
||||
'billing_address': {
|
||||
'name': self.user_demo.partner_id.name,
|
||||
'email': self.user_demo.partner_id.email,
|
||||
|
|
@ -174,10 +201,10 @@ class TestWebsiteSaleExpressCheckoutFlows(HttpCaseWithUserDemo):
|
|||
session['sale_order_id'] = self.sale_order.id
|
||||
root.session_store.save(session)
|
||||
|
||||
self._make_json_rpc_request(
|
||||
urls.url_join(
|
||||
self.base_url(), WebsiteSaleController._express_checkout_route
|
||||
), data={
|
||||
self.make_jsonrpc_request(
|
||||
urls.urljoin(
|
||||
self.base_url(), WebsiteSale._express_checkout_route
|
||||
), params={
|
||||
'billing_address': dict(self.express_checkout_billing_values)
|
||||
}
|
||||
)
|
||||
|
|
@ -195,10 +222,10 @@ class TestWebsiteSaleExpressCheckoutFlows(HttpCaseWithUserDemo):
|
|||
session['sale_order_id'] = self.sale_order.id
|
||||
root.session_store.save(session)
|
||||
|
||||
self._make_json_rpc_request(
|
||||
urls.url_join(
|
||||
self.base_url(), WebsiteSaleController._express_checkout_route
|
||||
), data={
|
||||
self.make_jsonrpc_request(
|
||||
urls.urljoin(
|
||||
self.base_url(), WebsiteSale._express_checkout_route
|
||||
), params={
|
||||
'billing_address': dict(self.express_checkout_billing_values)
|
||||
}
|
||||
)
|
||||
|
|
@ -210,3 +237,299 @@ class TestWebsiteSaleExpressCheckoutFlows(HttpCaseWithUserDemo):
|
|||
new_partner,
|
||||
self.express_checkout_billing_values,
|
||||
)
|
||||
|
||||
def test_express_checkout_public_user_shipping_address_change(self):
|
||||
""" Test that when using express checkout as a public user and selecting a shipping address,
|
||||
a new partner is created if the partner of the SO is the public partner.
|
||||
"""
|
||||
session = self.authenticate(None, None)
|
||||
session['sale_order_id'] = self.sale_order.id
|
||||
root.session_store.save(session)
|
||||
with patch(
|
||||
'odoo.addons.delivery.models.delivery_carrier.DeliveryCarrier.rate_shipment',
|
||||
return_value=self.rate_shipment_result
|
||||
):
|
||||
self.make_jsonrpc_request(
|
||||
urls.urljoin(
|
||||
self.base_url(),
|
||||
WebsiteSaleDeliveryController._express_checkout_delivery_route,
|
||||
),
|
||||
params={
|
||||
'partial_delivery_address': dict(
|
||||
self.express_checkout_anonymized_shipping_values,
|
||||
),
|
||||
},
|
||||
)
|
||||
new_partner = self.sale_order.partner_shipping_id
|
||||
self.assertNotEqual(new_partner, self.website.user_id.partner_id)
|
||||
self.assertTrue(new_partner.name.endswith(self.sale_order.name))
|
||||
self.assertPartnerShippingValues(
|
||||
new_partner,
|
||||
self.express_checkout_anonymized_shipping_values,
|
||||
)
|
||||
|
||||
def test_express_checkout_public_user_shipping_address_change_twice(self):
|
||||
""" Test that when using express checkout as a public user and selecting a shipping address
|
||||
more than once, a new partner is created if the partner of the SO is the public partner
|
||||
(only creates one new partner that is updated).
|
||||
"""
|
||||
session = self.authenticate(None, None)
|
||||
session['sale_order_id'] = self.sale_order.id
|
||||
root.session_store.save(session)
|
||||
with patch(
|
||||
'odoo.addons.delivery.models.delivery_carrier.DeliveryCarrier.rate_shipment',
|
||||
return_value=self.rate_shipment_result
|
||||
):
|
||||
self.make_jsonrpc_request(
|
||||
urls.urljoin(
|
||||
self.base_url(),
|
||||
WebsiteSaleDeliveryController._express_checkout_delivery_route,
|
||||
),
|
||||
params={
|
||||
'partial_delivery_address': dict(
|
||||
self.express_checkout_anonymized_shipping_values,
|
||||
),
|
||||
},
|
||||
)
|
||||
new_partner = self.sale_order.partner_shipping_id
|
||||
self.make_jsonrpc_request(
|
||||
urls.urljoin(
|
||||
self.base_url(),
|
||||
WebsiteSaleDeliveryController._express_checkout_delivery_route,
|
||||
),
|
||||
params={
|
||||
'partial_delivery_address': dict(
|
||||
self.express_checkout_anonymized_shipping_values_2,
|
||||
),
|
||||
},
|
||||
)
|
||||
self.assertEqual(new_partner.id, self.sale_order.partner_shipping_id.id)
|
||||
self.assertPartnerShippingValues(
|
||||
new_partner,
|
||||
self.express_checkout_anonymized_shipping_values_2,
|
||||
)
|
||||
|
||||
def test_express_checkout_registered_user_exisiting_shipping_address_change(self):
|
||||
""" Test that when using express checkout as a registered user and selecting an exisiting
|
||||
shipping address, the existing partner (the one of the SO) is reused.
|
||||
"""
|
||||
self.sale_order.partner_id = self.user_demo.partner_id.id
|
||||
session = self.authenticate(self.user_demo.login, self.user_demo.login)
|
||||
session['sale_order_id'] = self.sale_order.id
|
||||
root.session_store.save(session)
|
||||
with patch(
|
||||
'odoo.addons.delivery.models.delivery_carrier.DeliveryCarrier.rate_shipment',
|
||||
return_value=self.rate_shipment_result
|
||||
):
|
||||
self.make_jsonrpc_request(
|
||||
urls.urljoin(
|
||||
self.base_url(),
|
||||
WebsiteSaleDeliveryController._express_checkout_delivery_route,
|
||||
),
|
||||
params={
|
||||
'partial_delivery_address': dict(
|
||||
self.express_checkout_anonymized_shipping_values,
|
||||
),
|
||||
},
|
||||
)
|
||||
self.assertEqual(self.sale_order.partner_id.id, self.user_demo.partner_id.id)
|
||||
|
||||
def test_express_checkout_registered_user_new_shipping_address_change(self):
|
||||
""" Test that when using express checkout as a registered user and selecting a new shipping
|
||||
address, a new partner is created if the partner of the SO or his children are different
|
||||
from the delivery information received.
|
||||
"""
|
||||
self.sale_order.partner_id = self.user_demo.partner_id.id
|
||||
session = self.authenticate(self.user_demo.login, self.user_demo.login)
|
||||
session['sale_order_id'] = self.sale_order.id
|
||||
root.session_store.save(session)
|
||||
with patch(
|
||||
'odoo.addons.delivery.models.delivery_carrier.DeliveryCarrier.rate_shipment',
|
||||
return_value=self.rate_shipment_result
|
||||
):
|
||||
self.make_jsonrpc_request(
|
||||
urls.urljoin(
|
||||
self.base_url(),
|
||||
WebsiteSaleDeliveryController._express_checkout_delivery_route,
|
||||
),
|
||||
params={
|
||||
'partial_delivery_address': dict(
|
||||
self.express_checkout_anonymized_shipping_values,
|
||||
),
|
||||
},
|
||||
)
|
||||
new_partner = self.sale_order.partner_shipping_id
|
||||
self.assertEqual(self.sale_order.partner_id.id, self.user_demo.partner_id.id)
|
||||
self.assertNotEqual(new_partner.id, self.user_demo.partner_id.id)
|
||||
self.assertTrue(new_partner.name.endswith(self.sale_order.name))
|
||||
self.assertPartnerShippingValues(
|
||||
new_partner,
|
||||
self.express_checkout_anonymized_shipping_values,
|
||||
)
|
||||
|
||||
def test_express_checkout_registered_user_new_shipping_address_change_twice(self):
|
||||
""" Test that when using express checkout as a registered user and selecting a new
|
||||
shipping address more than once, a new partner is created if the partner of the SO is
|
||||
the public partner (only creates one new partner that is updated).
|
||||
"""
|
||||
self.sale_order.partner_id = self.user_demo.partner_id.id
|
||||
session = self.authenticate(self.user_demo.login, self.user_demo.login)
|
||||
session['sale_order_id'] = self.sale_order.id
|
||||
root.session_store.save(session)
|
||||
with patch(
|
||||
'odoo.addons.delivery.models.delivery_carrier.DeliveryCarrier.rate_shipment',
|
||||
return_value=self.rate_shipment_result
|
||||
):
|
||||
self.make_jsonrpc_request(
|
||||
urls.urljoin(
|
||||
self.base_url(),
|
||||
WebsiteSaleDeliveryController._express_checkout_delivery_route,
|
||||
),
|
||||
params={
|
||||
'partial_delivery_address': dict(
|
||||
self.express_checkout_anonymized_shipping_values,
|
||||
),
|
||||
},
|
||||
)
|
||||
new_partner = self.sale_order.partner_shipping_id
|
||||
self.make_jsonrpc_request(
|
||||
urls.urljoin(
|
||||
self.base_url(),
|
||||
WebsiteSaleDeliveryController._express_checkout_delivery_route,
|
||||
),
|
||||
params={
|
||||
'partial_delivery_address': dict(
|
||||
self.express_checkout_anonymized_shipping_values_2,
|
||||
),
|
||||
},
|
||||
)
|
||||
self.assertEqual(new_partner.id, self.sale_order.partner_shipping_id.id)
|
||||
self.assertPartnerShippingValues(
|
||||
new_partner,
|
||||
self.express_checkout_anonymized_shipping_values_2
|
||||
)
|
||||
|
||||
def test_express_checkout_partial_delivery_address_context_key(self):
|
||||
""" Test that when using express checkout with only partial delivery information,
|
||||
`express_checkout_partial_delivery_address` context key is in the context.
|
||||
"""
|
||||
delivery_carrier_mock = Mock()
|
||||
delivery_carrier_mock.rate_shipment = Mock(
|
||||
# Since we didn't mock the product ids for the mocked carrier, return an unsuccessful
|
||||
# response to skip the part where the product ids are checked on the carrier
|
||||
return_value=dict(self.rate_shipment_result, success=False)
|
||||
)
|
||||
|
||||
WebsiteSaleDeliveryController._get_rate(
|
||||
delivery_carrier_mock, self.sale_order, is_express_checkout_flow=True
|
||||
)
|
||||
sale_order = delivery_carrier_mock.rate_shipment.call_args[0][0]
|
||||
self.assertTrue(sale_order.env.context.get('express_checkout_partial_delivery_address'))
|
||||
|
||||
def test_express_checkout_registered_user_with_shipping_option(self):
|
||||
""" Test that when you use the express checkout as a registered user and the shipping
|
||||
address sent by the express checkout form exactly matches one of the addresses linked
|
||||
to this user in Odoo, we do not create a new partner and reuse the existing one.
|
||||
"""
|
||||
self.sale_order.partner_id = self.user_demo.partner_id.id
|
||||
session = self.authenticate(self.user_demo.login, self.user_demo.login)
|
||||
session['sale_order_id'] = self.sale_order.id
|
||||
root.session_store.save(session)
|
||||
with patch(
|
||||
'odoo.addons.delivery.models.delivery_carrier.DeliveryCarrier.rate_shipment',
|
||||
return_value=self.rate_shipment_result
|
||||
):
|
||||
shipping_options = self.make_jsonrpc_request(
|
||||
urls.urljoin(
|
||||
self.base_url(),
|
||||
WebsiteSaleDeliveryController._express_checkout_delivery_route,
|
||||
),
|
||||
params={
|
||||
'partial_delivery_address': dict(
|
||||
self.express_checkout_anonymized_demo_shipping_values,
|
||||
),
|
||||
},
|
||||
)
|
||||
self.assertEqual(self.sale_order.partner_id.id, self.user_demo.partner_id.id)
|
||||
|
||||
self.make_jsonrpc_request(
|
||||
urls.urljoin(
|
||||
self.base_url(),
|
||||
WebsiteSaleDeliveryController._express_checkout_route,
|
||||
),
|
||||
params={
|
||||
'billing_address': dict(self.express_checkout_billing_values),
|
||||
'shipping_address': dict(self.express_checkout_demo_shipping_values),
|
||||
'shipping_option': shipping_options['delivery_methods'][0],
|
||||
},
|
||||
)
|
||||
self.assertEqual(self.sale_order.partner_id.id, self.user_demo.partner_id.id)
|
||||
|
||||
def test_express_checkout_registered_user_with_shipping_option_new_address(self):
|
||||
""" Test that when you use the express checkout as a registered user and the shipping
|
||||
address sent by the express checkout form doesn't exist in Odoo, we create a new
|
||||
partner.
|
||||
"""
|
||||
self.sale_order.partner_id = self.user_demo.partner_id.id
|
||||
session = self.authenticate(self.user_demo.login, self.user_demo.login)
|
||||
session['sale_order_id'] = self.sale_order.id
|
||||
root.session_store.save(session)
|
||||
with patch(
|
||||
'odoo.addons.delivery.models.delivery_carrier.DeliveryCarrier.rate_shipment',
|
||||
return_value=self.rate_shipment_result
|
||||
):
|
||||
# Won't create a new partner because the partial information are the same as an
|
||||
# exisiting partner linked to the SO
|
||||
shipping_options = self.make_jsonrpc_request(
|
||||
urls.urljoin(
|
||||
self.base_url(),
|
||||
WebsiteSaleDeliveryController._express_checkout_delivery_route,
|
||||
),
|
||||
params={
|
||||
'partial_delivery_address': dict(
|
||||
self.express_checkout_anonymized_demo_shipping_values,
|
||||
),
|
||||
},
|
||||
)
|
||||
self.assertEqual(self.sale_order.partner_shipping_id, self.user_demo.partner_id)
|
||||
|
||||
# Will create a new partner because the complete shipping information differs from
|
||||
# the partner actually selected
|
||||
self.make_jsonrpc_request(
|
||||
urls.urljoin(
|
||||
self.base_url(),
|
||||
WebsiteSaleDeliveryController._express_checkout_route
|
||||
),
|
||||
params={
|
||||
'billing_address': dict(self.express_checkout_billing_values),
|
||||
'shipping_address': dict(self.express_checkout_demo_shipping_values_2),
|
||||
'shipping_option': shipping_options['delivery_methods'][0],
|
||||
},
|
||||
)
|
||||
self.assertNotEqual(
|
||||
self.sale_order.partner_shipping_id.id, self.user_demo.partner_id.id
|
||||
)
|
||||
self.assertFalse(
|
||||
self.sale_order.partner_shipping_id.name.endswith(self.sale_order.name)
|
||||
)
|
||||
|
||||
def test_express_checkout_compute_taxes_returns_amount_without_delivery(self):
|
||||
"""
|
||||
Test that the /express_checkout/compute_taxes route returns the minor amount
|
||||
without delivery costs.
|
||||
"""
|
||||
websiteSaleDeliveryController = WebsiteSaleDeliveryController()
|
||||
|
||||
delivery_product = self.env['product.product'].create({'name': 'Delivery'})
|
||||
delivery = self.env['delivery.carrier'].create({
|
||||
'name': 'Normal Delivery Charges',
|
||||
'fixed_price': 10,
|
||||
'delivery_type': 'fixed',
|
||||
'product_id': delivery_product.id,
|
||||
})
|
||||
self.sale_order._create_delivery_line(delivery, 10)
|
||||
expected_amount = self.sale_order._compute_amount_total_without_delivery()
|
||||
|
||||
with MockRequest(self.env, website=self.website, sale_order_id=self.sale_order.id):
|
||||
minor_amount = websiteSaleDeliveryController.express_checkout_shipping_address_compute_taxes()
|
||||
self.assertEqual(minor_amount, expected_amount * 100)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.tests import tagged
|
||||
|
||||
from odoo.addons.product.tests.common import ProductVariantsCommon
|
||||
|
||||
|
||||
@tagged('-at_install', 'post_install')
|
||||
class TestFuzzy(ProductVariantsCommon):
|
||||
def test_variant_default_code(self):
|
||||
website = self.env.ref('website.default_website')
|
||||
|
||||
line = self.product_template_sofa.attribute_line_ids
|
||||
value_red = line.product_template_value_ids[0]
|
||||
value_blue = line.product_template_value_ids[1]
|
||||
value_green = line.product_template_value_ids[2]
|
||||
product_red = self.product_template_sofa._get_variant_for_combination(value_red)
|
||||
product_blue = self.product_template_sofa._get_variant_for_combination(value_blue)
|
||||
product_green = self.product_template_sofa._get_variant_for_combination(value_green)
|
||||
product_red.default_code = 'RED_12345'
|
||||
product_blue.default_code = 'BLUE_ABCDE'
|
||||
product_green.default_code = 'GREEN_98765'
|
||||
self.cr.flush()
|
||||
|
||||
options = {
|
||||
'displayDescription': True, 'displayDetail': True, 'display_currency': True,
|
||||
'displayExtraDetail': True, 'displayExtraLink': True,
|
||||
'displayImage': True, 'allowFuzzy': True
|
||||
}
|
||||
results_count, _, fuzzy_term = website._search_with_fuzzy('products_only', 'RED234', 5, 'name asc', options)
|
||||
self.assertEqual(1, results_count, "Should have found red")
|
||||
self.assertEqual('red_12345', fuzzy_term, "Should suggest red")
|
||||
results_count, _, fuzzy_term = website._search_with_fuzzy('products_only', 'GROEN98765', 5, 'name asc', options)
|
||||
self.assertEqual(1, results_count, "Should have found green")
|
||||
self.assertEqual('green_98765', fuzzy_term, "Should suggest green")
|
||||
results_count, _, fuzzy_term = website._search_with_fuzzy('products_only', 'BLUABCE', 5, 'name asc', options)
|
||||
self.assertEqual(1, results_count, "Should have found blue")
|
||||
self.assertEqual('blue_abcde', fuzzy_term, "Should suggest blue")
|
||||
results_count, _, fuzzy_term = website._search_with_fuzzy('products_only', 'SQWBRNZ', 5, 'name asc', options)
|
||||
self.assertEqual(0, results_count, "Should have found none")
|
||||
self.assertIsNone(fuzzy_term, "Should have no suggestion")
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
from odoo.tests import tagged
|
||||
|
||||
from odoo.addons.payment.models.payment_provider import PaymentProvider
|
||||
from odoo.addons.payment.tests.http_common import PaymentHttpCommon
|
||||
from odoo.addons.sale.tests.common import SaleCommon
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestPaymentProviderVisibility(PaymentHttpCommon, SaleCommon):
|
||||
|
||||
def test_payment_provider_visibility_with_portal(self):
|
||||
"""Check providers availability on the sales portal.
|
||||
|
||||
The current website must be considered to filter the providers.
|
||||
"""
|
||||
website_portal = self.env['website'].get_current_website()
|
||||
website_shop = self.env['website'].create({'name': "Shop Website"})
|
||||
|
||||
base_url = self.env['ir.config_parameter'].sudo().get_base_url()
|
||||
website_portal.write({'domain': base_url})
|
||||
|
||||
self.provider.write({'website_id': website_portal.id})
|
||||
restricted_provider = self.env['payment.provider'].sudo().search([('name', '=', 'Demo'), ('company_id', '=', website_shop.company_id.id)])
|
||||
restricted_provider.write({'state': 'test', 'website_id': website_shop.id})
|
||||
|
||||
url_so = self.sale_order.get_portal_url()
|
||||
self.sale_order.require_payment = True
|
||||
portal_url = f"{website_portal.domain}{url_so}"
|
||||
|
||||
with patch(
|
||||
'odoo.addons.website_payment.models.payment_provider.PaymentProvider._get_compatible_providers',
|
||||
side_effect=lambda *args, **kwargs: PaymentProvider._get_compatible_providers(
|
||||
self.env['payment.provider'], *args, **kwargs
|
||||
),
|
||||
) as mock_method:
|
||||
self.url_open(portal_url, allow_redirects=True)
|
||||
|
||||
mock_method.assert_called_once()
|
||||
self.assertEqual(mock_method.call_args.kwargs['website_id'], website_portal.id)
|
||||
|
||||
mock_method.call_args.kwargs.pop('show_non_tokenize_provider', None)
|
||||
providers = self.env['payment.provider']._get_compatible_providers(
|
||||
*mock_method.call_args.args, **mock_method.call_args.kwargs
|
||||
)
|
||||
|
||||
self.assertIn(self.provider.id, providers.ids, "The visible provider should be visible.")
|
||||
|
||||
self.assertNotIn(
|
||||
restricted_provider.id, providers.ids, "The restricted provider shouldn't be visible."
|
||||
)
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.fields import Command
|
||||
from odoo.tests import TransactionCase, tagged
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestProductPublicCategory(TransactionCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
def create_multi(vals_list):
|
||||
return list(map(Command.create, vals_list))
|
||||
|
||||
cls.published_product, cls.unpublished_product = cls.env['product.template'].create([
|
||||
{'name': 'Published Product', 'is_published': True},
|
||||
{'name': 'Unpublished Product', 'is_published': False},
|
||||
])
|
||||
|
||||
cls.env['product.public.category'].search([]).unlink()
|
||||
|
||||
cls.env['product.public.category'].create([
|
||||
{
|
||||
'name': '1',
|
||||
'child_id': create_multi([
|
||||
{
|
||||
'name': '1.1',
|
||||
'child_id': create_multi([{'name': '1.1.1'}]),
|
||||
},
|
||||
{'name': '1.2', 'product_tmpl_ids': [Command.link(cls.published_product.id)]},
|
||||
]),
|
||||
},
|
||||
{
|
||||
'name': '2',
|
||||
'child_id': create_multi([
|
||||
{
|
||||
'name': '2.1',
|
||||
'child_id': create_multi([{
|
||||
'name': '2.1.1',
|
||||
'product_tmpl_ids': [
|
||||
Command.link(cls.published_product.id),
|
||||
Command.link(cls.unpublished_product.id)
|
||||
],
|
||||
}]),
|
||||
},
|
||||
{'name': '2.2'},
|
||||
]),
|
||||
},
|
||||
{'name': '3', 'product_tmpl_ids': [Command.link(cls.unpublished_product.id)]},
|
||||
])
|
||||
|
||||
def test_search_has_published_products(self):
|
||||
published_categs = set(self.env['product.public.category'].search(
|
||||
[('has_published_products', 'not in', (False,))]
|
||||
).mapped('name'))
|
||||
|
||||
self.assertSetEqual(published_categs, {'1', '1.2', '2', '2.1', '2.1.1'})
|
||||
|
||||
def test_search_does_not_have_published_products(self):
|
||||
unpublished_categs = set(self.env['product.public.category'].search(
|
||||
[('has_published_products', '!=', True)]
|
||||
).mapped('name'))
|
||||
|
||||
self.assertSetEqual(unpublished_categs, {'1.1', '1.1.1', '2.2', '3'})
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import Command
|
||||
from odoo.tests import tagged
|
||||
|
||||
from odoo.addons.sale.tests.common import SaleCommon
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestSaleOrder(SaleCommon):
|
||||
|
||||
def test_delivery_methods_match_order_company(self):
|
||||
company_1 = self.env['res.company'].create({'name': 'Test Company 1'})
|
||||
company_2 = self.env['res.company'].create({'name': 'Test Company 2'})
|
||||
product_delivery_1 = self.env['product.product'].create(
|
||||
{
|
||||
'name': 'Delivery Product 1',
|
||||
'type': 'service',
|
||||
'company_id': company_1.id,
|
||||
}
|
||||
)
|
||||
product_delivery_2 = self.env['product.product'].create(
|
||||
{
|
||||
'name': 'Delivery Product 2',
|
||||
'type': 'service',
|
||||
'company_id': company_2.id,
|
||||
}
|
||||
)
|
||||
delivery_1 = self.env['delivery.carrier'].create(
|
||||
{
|
||||
'name': 'Delivery 1',
|
||||
'delivery_type': 'fixed',
|
||||
'product_id': product_delivery_1.id,
|
||||
'is_published': True,
|
||||
}
|
||||
)
|
||||
delivery_2 = self.env['delivery.carrier'].create(
|
||||
{
|
||||
'name': 'Delivery 2',
|
||||
'delivery_type': 'fixed',
|
||||
'product_id': product_delivery_2.id,
|
||||
'is_published': True,
|
||||
}
|
||||
)
|
||||
sale_order = self.env['sale.order'].create(
|
||||
{
|
||||
'partner_id': self.partner.id,
|
||||
'company_id': company_1.id,
|
||||
'order_line': [
|
||||
Command.create(
|
||||
{
|
||||
'product_id': self.product.id,
|
||||
}
|
||||
)],
|
||||
}
|
||||
)
|
||||
available_dms = sale_order._get_delivery_methods()
|
||||
self.assertIn(delivery_1, available_dms)
|
||||
self.assertNotIn(delivery_2, available_dms)
|
||||
|
|
@ -1,113 +1,107 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import logging
|
||||
import lxml.html
|
||||
|
||||
import odoo.tests
|
||||
from odoo.fields import Command
|
||||
from odoo.tests import tagged
|
||||
|
||||
from odoo import api, Command
|
||||
from odoo.addons.base.tests.common import HttpCaseWithUserDemo, TransactionCaseWithUserDemo, HttpCaseWithUserPortal
|
||||
from odoo.addons.website_sale.controllers.main import WebsiteSale
|
||||
from odoo.addons.website.tools import MockRequest
|
||||
from odoo.addons.base.tests.common import HttpCaseWithUserDemo
|
||||
from odoo.addons.website_sale.tests.common import WebsiteSaleCommon
|
||||
from odoo.addons.website.tests.common import HttpCaseWithWebsiteUser
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@odoo.tests.tagged('post_install', '-at_install')
|
||||
class TestUi(HttpCaseWithUserDemo):
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestSaleProcess(HttpCaseWithUserDemo, WebsiteSaleCommon, HttpCaseWithWebsiteUser):
|
||||
|
||||
def setUp(self):
|
||||
super(TestUi, self).setUp()
|
||||
product_product_7 = self.env['product.product'].create({
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.storage_box = cls.env['product.product'].create({
|
||||
'name': 'Storage Box',
|
||||
'standard_price': 70.0,
|
||||
'list_price': 79.0,
|
||||
'website_published': True,
|
||||
})
|
||||
self.product_attribute_1 = self.env['product.attribute'].create({
|
||||
cls.product_attribute_legs = cls.env['product.attribute'].create({
|
||||
'name': 'Legs',
|
||||
'sequence': 10,
|
||||
'value_ids': [
|
||||
Command.create({
|
||||
'name': 'Steel',
|
||||
'sequence': 1,
|
||||
}),
|
||||
Command.create({
|
||||
'name': 'Aluminium',
|
||||
'sequence': 2,
|
||||
}),
|
||||
],
|
||||
})
|
||||
product_attribute_value_1 = self.env['product.attribute.value'].create({
|
||||
'name': 'Steel',
|
||||
'attribute_id': self.product_attribute_1.id,
|
||||
'sequence': 1,
|
||||
})
|
||||
product_attribute_value_2 = self.env['product.attribute.value'].create({
|
||||
'name': 'Aluminium',
|
||||
'attribute_id': self.product_attribute_1.id,
|
||||
'sequence': 2,
|
||||
})
|
||||
self.product_product_11_product_template = self.env['product.template'].create({
|
||||
cls.conference_chair = cls.env['product.template'].create({
|
||||
'name': 'Conference Chair',
|
||||
'list_price': 16.50,
|
||||
'website_published': True,
|
||||
'sale_ok': True,
|
||||
'accessory_product_ids': [(4, product_product_7.id)],
|
||||
})
|
||||
self.env['product.template.attribute.line'].create({
|
||||
'product_tmpl_id': self.product_product_11_product_template.id,
|
||||
'attribute_id': self.product_attribute_1.id,
|
||||
'value_ids': [(4, product_attribute_value_1.id), (4, product_attribute_value_2.id)],
|
||||
'accessory_product_ids': [Command.link(cls.storage_box.id)],
|
||||
'attribute_line_ids': [
|
||||
Command.create({
|
||||
'attribute_id': cls.product_attribute_legs.id,
|
||||
'value_ids': [Command.set(cls.product_attribute_legs.value_ids.ids)],
|
||||
})
|
||||
],
|
||||
})
|
||||
|
||||
self.product_product_1_product_template = self.env['product.template'].create({
|
||||
cls.chair_floor_protection = cls.env['product.template'].create({
|
||||
'name': 'Chair floor protection',
|
||||
'list_price': 12.0,
|
||||
})
|
||||
# Crappy hack: But otherwise the "Proceed To Checkout" modal button won't be displayed
|
||||
if 'optional_product_ids' in self.env['product.template']:
|
||||
self.product_product_11_product_template.optional_product_ids = [(6, 0, self.product_product_1_product_template.ids)]
|
||||
if 'optional_product_ids' in cls.env['product.template']:
|
||||
cls.conference_chair.optional_product_ids = [Command.set(cls.chair_floor_protection.ids)]
|
||||
|
||||
self.env['account.journal'].create({'name': 'Cash - Test', 'type': 'cash', 'code': 'CASH - Test'})
|
||||
|
||||
# Avoid Shipping/Billing address page
|
||||
(self.env.ref('base.partner_admin') + self.partner_demo).write({
|
||||
'street': '215 Vine St',
|
||||
'city': 'Scranton',
|
||||
'zip': '18503',
|
||||
'country_id': self.env.ref('base.us').id,
|
||||
'state_id': self.env.ref('base.state_us_39').id,
|
||||
'phone': '+1 555-555-5555',
|
||||
'email': 'admin@yourcompany.example.com',
|
||||
cls.env['account.journal'].create({
|
||||
'name': 'Cash - Test',
|
||||
'type': 'cash',
|
||||
'code': 'CASH - Test',
|
||||
})
|
||||
|
||||
# Avoid Shipping/Billing address page
|
||||
cls.env.ref('base.partner_admin').write(cls.dummy_partner_address_values)
|
||||
cls.partner_website_user.write(cls.dummy_partner_address_values)
|
||||
|
||||
if cls.env['ir.module.module']._get('payment_custom').state == 'installed':
|
||||
transfer_provider = cls.env.ref('payment.payment_provider_transfer')
|
||||
transfer_provider.write({
|
||||
'state': 'enabled',
|
||||
'is_published': True,
|
||||
})
|
||||
transfer_provider._transfer_ensure_pending_msg_is_set()
|
||||
|
||||
def test_01_admin_shop_tour(self):
|
||||
self.start_tour(self.env['website'].get_client_action_url('/shop'), 'shop', login='admin')
|
||||
self.start_tour(self.env['website'].get_client_action_url('/shop'), 'test_01_admin_shop_tour', login='admin')
|
||||
|
||||
def test_01_cart_update_check(self):
|
||||
self.start_tour('/', 'shop_update_cart', login='admin')
|
||||
|
||||
def test_02_admin_checkout(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.write({
|
||||
'state': 'enabled',
|
||||
'is_published': True,
|
||||
})
|
||||
transfer_provider._transfer_ensure_pending_msg_is_set()
|
||||
self.start_tour("/", 'shop_buy_product', login="admin")
|
||||
|
||||
def test_03_demo_checkout(self):
|
||||
self.partner_demo.write(self.dummy_partner_address_values)
|
||||
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.write({
|
||||
'state': 'enabled',
|
||||
'is_published': True,
|
||||
})
|
||||
transfer_provider._transfer_ensure_pending_msg_is_set()
|
||||
self.start_tour("/", 'shop_buy_product', login="demo")
|
||||
|
||||
def test_04_admin_website_sale_tour(self):
|
||||
if self.env['ir.module.module']._get('payment_custom').state != 'installed':
|
||||
self.skipTest("Transfer provider is not installed")
|
||||
|
||||
self.env.ref('payment.payment_provider_transfer').write({
|
||||
'state': 'enabled',
|
||||
'is_published': True,
|
||||
})
|
||||
self.env.company.country_id = self.env.ref('base.us')
|
||||
self.env.company.country_id = self.country_us
|
||||
tax_group = self.env['account.tax.group'].create({'name': 'Tax 15%'})
|
||||
tax = self.env['account.tax'].create({
|
||||
'name': 'Tax 15%',
|
||||
|
|
@ -120,7 +114,6 @@ class TestUi(HttpCaseWithUserDemo):
|
|||
'name': 'Storage Box Test',
|
||||
'standard_price': 70.0,
|
||||
'list_price': 79.0,
|
||||
'categ_id': self.env.ref('product.product_category_all').id,
|
||||
'website_published': True,
|
||||
'invoice_policy': 'delivery',
|
||||
})
|
||||
|
|
@ -128,12 +121,14 @@ class TestUi(HttpCaseWithUserDemo):
|
|||
self.env['res.config.settings'].create({
|
||||
'auth_signup_uninvited': 'b2c',
|
||||
'show_line_subtotals_tax_selection': 'tax_excluded',
|
||||
'group_show_line_subtotals_tax_excluded': True,
|
||||
'group_show_line_subtotals_tax_included': False,
|
||||
}).execute()
|
||||
|
||||
self.start_tour("/", 'website_sale_tour_1')
|
||||
self.start_tour(self.env['website'].get_client_action_url('/shop/cart'), 'website_sale_tour_backend', login='admin')
|
||||
self.start_tour(
|
||||
self.env['website'].get_client_action_url('/shop/cart'),
|
||||
'website_sale_tour_backend',
|
||||
login='admin'
|
||||
)
|
||||
self.start_tour("/", 'website_sale_tour_2', login="admin")
|
||||
|
||||
def test_05_google_analytics_tracking(self):
|
||||
|
|
@ -155,7 +150,7 @@ class TestUi(HttpCaseWithUserDemo):
|
|||
'name': 'Colored T-Shirt',
|
||||
'standard_price': 500,
|
||||
'list_price': 750,
|
||||
'detailed_type': 'consu',
|
||||
'type': 'consu',
|
||||
'website_published': True,
|
||||
'attribute_line_ids': [
|
||||
Command.create({
|
||||
|
|
@ -170,339 +165,59 @@ class TestUi(HttpCaseWithUserDemo):
|
|||
self.env['product.template'].create({
|
||||
'name': 'Basic Shirt',
|
||||
'standard_price': 500,
|
||||
'detailed_type': 'consu',
|
||||
'type': 'consu',
|
||||
'website_published': True
|
||||
})
|
||||
self.start_tour("/shop", 'google_analytics_add_to_cart')
|
||||
|
||||
def test_06_public_user_shop_repair(self):
|
||||
""" Public user purchasing repair service products in website shop. """
|
||||
if self.env['ir.module.module']._get('repair').state != 'installed':
|
||||
self.skipTest("Repair is not installed")
|
||||
|
||||
@odoo.tests.tagged('post_install', '-at_install')
|
||||
class TestWebsiteSaleCheckoutAddress(TransactionCaseWithUserDemo, HttpCaseWithUserPortal):
|
||||
''' The goal of this method class is to test the address management on
|
||||
the checkout (new/edit billing/shipping, company_id, website_id..).
|
||||
'''
|
||||
|
||||
def setUp(self):
|
||||
super(TestWebsiteSaleCheckoutAddress, self).setUp()
|
||||
self.partner_demo.company_id = self.env.ref('base.main_company')
|
||||
self.website = self.env.ref('website.default_website')
|
||||
self.country_id = self.env.ref('base.be').id
|
||||
self.WebsiteSaleController = WebsiteSale()
|
||||
self.default_address_values = {
|
||||
'name': 'a res.partner address', 'email': 'email@email.email', 'street': 'ooo',
|
||||
'city': 'ooo', 'zip': '1200', 'country_id': self.country_id, 'submitted': 1,
|
||||
}
|
||||
|
||||
def _create_so(self, partner_id=None, company_id=None):
|
||||
values = {
|
||||
'partner_id': partner_id,
|
||||
'website_id': self.website.id,
|
||||
'order_line': [(0, 0, {
|
||||
'product_id': self.env['product.product'].create({
|
||||
'name': 'Product A',
|
||||
'list_price': 100,
|
||||
'website_published': True,
|
||||
'sale_ok': True}).id,
|
||||
'name': 'Product A',
|
||||
})]
|
||||
}
|
||||
if company_id:
|
||||
values['company_id'] = company_id
|
||||
return self.env['sale.order'].create(values)
|
||||
|
||||
def _get_last_address(self, partner):
|
||||
''' Useful to retrieve the last created shipping address '''
|
||||
return partner.child_ids.sorted('id', reverse=True)[0]
|
||||
|
||||
# TEST WEBSITE
|
||||
def test_01_create_shipping_address_specific_user_account(self):
|
||||
''' Ensure `website_id` is correctly set (specific_user_account) '''
|
||||
p = self.env.user.partner_id
|
||||
so = self._create_so(p.id)
|
||||
|
||||
with MockRequest(self.env, website=self.website, sale_order_id=so.id) as req:
|
||||
req.httprequest.method = "POST"
|
||||
self.WebsiteSaleController.address(**self.default_address_values)
|
||||
self.assertFalse(self._get_last_address(p).website_id, "New shipping address should not have a website set on it (no specific_user_account).")
|
||||
|
||||
self.website.specific_user_account = True
|
||||
|
||||
self.WebsiteSaleController.address(**self.default_address_values)
|
||||
self.assertEqual(self._get_last_address(p).website_id, self.website, "New shipping address should have a website set on it (specific_user_account).")
|
||||
|
||||
# TEST COMPANY
|
||||
def _setUp_multicompany_env(self):
|
||||
''' Have 2 companies A & B.
|
||||
Have 1 website 1 which company is B
|
||||
Have admin on company A
|
||||
'''
|
||||
self.company_a = self.env['res.company'].create({
|
||||
'name': 'Company A',
|
||||
})
|
||||
self.company_b = self.env['res.company'].create({
|
||||
'name': 'Company B',
|
||||
})
|
||||
self.company_c = self.env['res.company'].create({
|
||||
'name': 'Company C',
|
||||
})
|
||||
self.website.company_id = self.company_b
|
||||
self.env.user.company_id = self.company_a
|
||||
|
||||
self.demo_user = self.user_demo
|
||||
self.demo_user.company_ids += self.company_c
|
||||
self.demo_user.company_id = self.company_c
|
||||
self.demo_partner = self.demo_user.partner_id
|
||||
|
||||
self.portal_user = self.user_portal
|
||||
self.portal_partner = self.portal_user.partner_id
|
||||
|
||||
def test_02_demo_address_and_company(self):
|
||||
''' This test ensure that the company_id of the address (partner) is
|
||||
correctly set and also, is not wrongly changed.
|
||||
eg: new shipping should use the company of the website and not the
|
||||
one from the admin, and editing a billing should not change its
|
||||
company.
|
||||
'''
|
||||
self._setUp_multicompany_env()
|
||||
so = self._create_so(self.demo_partner.id)
|
||||
|
||||
env = api.Environment(self.env.cr, self.demo_user.id, {})
|
||||
# change also website env for `sale_get_order` to not change order partner_id
|
||||
with MockRequest(env, website=self.website.with_env(env), sale_order_id=so.id) as req:
|
||||
req.httprequest.method = "POST"
|
||||
|
||||
# 1. Logged in user, new shipping
|
||||
self.WebsiteSaleController.address(**self.default_address_values)
|
||||
new_shipping = self._get_last_address(self.demo_partner)
|
||||
self.assertTrue(new_shipping.company_id != self.env.user.company_id, "Logged in user new shipping should not get the company of the sudo() neither the one from it's partner..")
|
||||
self.assertEqual(new_shipping.company_id, self.website.company_id, ".. but the one from the website.")
|
||||
|
||||
# 2. Logged in user/internal user, should not edit name or email address of billing
|
||||
self.default_address_values['partner_id'] = self.demo_partner.id
|
||||
self.WebsiteSaleController.address(**self.default_address_values)
|
||||
self.assertEqual(self.demo_partner.company_id, self.company_c, "Logged in user edited billing (the partner itself) should not get its company modified.")
|
||||
self.assertNotEqual(self.demo_partner.name, self.default_address_values['name'], "Employee cannot change their name during the checkout process.")
|
||||
self.assertNotEqual(self.demo_partner.email, self.default_address_values['email'], "Employee cannot change their email during the checkout process.")
|
||||
|
||||
def test_03_public_user_address_and_company(self):
|
||||
''' Same as test_02 but with public user '''
|
||||
self._setUp_multicompany_env()
|
||||
so = self._create_so(self.website.user_id.partner_id.id)
|
||||
|
||||
env = api.Environment(self.env.cr, self.website.user_id.id, {})
|
||||
# change also website env for `sale_get_order` to not change order partner_id
|
||||
with MockRequest(env, website=self.website.with_env(env), sale_order_id=so.id) as req:
|
||||
req.httprequest.method = "POST"
|
||||
|
||||
# 1. Public user, new billing
|
||||
self.default_address_values['partner_id'] = -1
|
||||
self.WebsiteSaleController.address(**self.default_address_values)
|
||||
new_partner = so.partner_id
|
||||
self.assertNotEqual(new_partner, self.website.user_id.partner_id, "New billing should have created a new partner and assign it on the SO")
|
||||
self.assertEqual(new_partner.company_id, self.website.company_id, "The new partner should get the company of the website")
|
||||
|
||||
# 2. Public user, edit billing
|
||||
self.default_address_values['partner_id'] = new_partner.id
|
||||
self.WebsiteSaleController.address(**self.default_address_values)
|
||||
self.assertEqual(new_partner.company_id, self.website.company_id, "Public user edited billing (the partner itself) should not get its company modified.")
|
||||
|
||||
def test_04_apply_empty_pl(self):
|
||||
''' Ensure empty pl code reset the applied pl '''
|
||||
so = self._create_so(self.env.user.partner_id.id)
|
||||
eur_pl = self.env['product.pricelist'].create({
|
||||
'name': 'EUR_test',
|
||||
'website_id': self.website.id,
|
||||
'code': 'EUR_test',
|
||||
})
|
||||
|
||||
with MockRequest(self.env, website=self.website, sale_order_id=so.id):
|
||||
self.WebsiteSaleController.pricelist('EUR_test')
|
||||
self.assertEqual(so.pricelist_id, eur_pl, "Ensure EUR_test is applied")
|
||||
|
||||
self.WebsiteSaleController.pricelist('')
|
||||
self.assertNotEqual(so.pricelist_id, eur_pl, "Pricelist should be removed when sending an empty pl code")
|
||||
|
||||
def test_04_pl_reset_on_login(self):
|
||||
"""Check that after login, the SO pricelist is correctly recomputed."""
|
||||
test_user = self.env['res.users'].create({
|
||||
'name': 'Toto',
|
||||
'login': 'long_enough_password',
|
||||
'password': 'long_enough_password',
|
||||
})
|
||||
eur_pl = self.env['product.pricelist'].create({
|
||||
'name': 'EUR_test',
|
||||
'website_id': self.website.id,
|
||||
'code': 'EUR_test',
|
||||
})
|
||||
test_user.partner_id.property_product_pricelist = eur_pl
|
||||
|
||||
public_user_env = self.env(user=self.website.user_id)
|
||||
so = self._create_so(public_user_env.user.partner_id.id)
|
||||
|
||||
with MockRequest(self.env, website=self.website, sale_order_id=so.id, website_sale_current_pl=so.pricelist_id.id):
|
||||
order = self.website.sale_get_order()
|
||||
pl = order.pricelist_id
|
||||
self.assertNotEqual(pl, eur_pl)
|
||||
order_b = self.website.with_user(test_user).sale_get_order()
|
||||
self.assertEqual(order, order_b)
|
||||
self.assertEqual(order_b.pricelist_id, eur_pl)
|
||||
|
||||
# TEST WEBSITE & MULTI COMPANY
|
||||
|
||||
def test_05_create_so_with_website_and_multi_company(self):
|
||||
''' This test ensure that the company_id of the website set on the order
|
||||
is the same as the env company or the one set on the order.
|
||||
'''
|
||||
self._setUp_multicompany_env()
|
||||
# No company on the SO
|
||||
so = self._create_so(self.demo_partner.id)
|
||||
self.assertEqual(so.company_id, self.website.company_id)
|
||||
|
||||
# Same company on the SO and the env user company but no website
|
||||
with self.assertRaises(ValueError, msg="Should not be able to create SO with company different than the website company"):
|
||||
self._create_so(self.demo_partner.id, self.company_a.id)
|
||||
|
||||
# Same company on the SO and the website company
|
||||
so = self._create_so(self.demo_partner.id, self.company_b.id)
|
||||
self.assertEqual(so.company_id, self.website.company_id)
|
||||
|
||||
# Different company on the SO and the env user company
|
||||
with self.assertRaises(ValueError, msg="Should not be able to create SO with company different than the website company"):
|
||||
self._create_so(self.demo_partner.id, self.company_c.id)
|
||||
|
||||
def test_06_portal_user_address_and_company(self):
|
||||
''' Same as test_03 but with portal user '''
|
||||
self._setUp_multicompany_env()
|
||||
so = self._create_so(self.portal_partner.id)
|
||||
|
||||
self.env['sale.order'].create({
|
||||
'partner_id': self.partner_portal.id,
|
||||
'state': 'sent',
|
||||
})
|
||||
|
||||
env = api.Environment(self.env.cr, self.portal_user.id, {})
|
||||
# change also website env for `sale_get_order` to not change order partner_id
|
||||
with MockRequest(env, website=self.website.with_env(env), sale_order_id=so.id) as req:
|
||||
req.httprequest.method = "POST"
|
||||
|
||||
# 1. Portal user, new shipping, same with the log in user
|
||||
self.WebsiteSaleController.address(**self.default_address_values)
|
||||
new_shipping = self._get_last_address(self.portal_partner)
|
||||
self.assertTrue(new_shipping.company_id != self.env.user.company_id, "Portal user new shipping should not get the company of the sudo() neither the one from it's partner..")
|
||||
self.assertEqual(new_shipping.company_id, self.website.company_id, ".. but the one from the website.")
|
||||
|
||||
# 2. Portal user, edit billing
|
||||
self.default_address_values['partner_id'] = self.portal_partner.id
|
||||
self.WebsiteSaleController.address(**self.default_address_values)
|
||||
# Name cannot be changed if there are issued invoices
|
||||
self.assertNotEqual(self.portal_partner.name, self.default_address_values['name'], "Portal User should not be able to change the name if they have invoices under their name.")
|
||||
|
||||
def test_07_change_fiscal_position(self):
|
||||
"""
|
||||
Check that the sale order is updated when you change fiscal position.
|
||||
Change fiscal position by modifying address during checkout process.
|
||||
"""
|
||||
self.env.company.country_id = self.env.ref('base.us')
|
||||
partner = self.env['res.partner'].create({'name': 'test'})
|
||||
be_address_POST, nl_address_POST = [
|
||||
{
|
||||
'name': 'Test name', 'email': 'test@email.com', 'street': 'test',
|
||||
'city': 'test', 'zip': '3000', 'country_id': self.env.ref('base.be').id, 'submitted': 1,
|
||||
'partner_id': partner.id,
|
||||
'callback': '/shop/checkout',
|
||||
},
|
||||
{
|
||||
'name': 'Test name', 'email': 'test@email.com', 'street': 'test',
|
||||
'city': 'test', 'zip': '3000', 'country_id': self.env.ref('base.nl').id, 'submitted': 1,
|
||||
'partner_id': partner.id,
|
||||
'callback': '/shop/checkout',
|
||||
},
|
||||
]
|
||||
|
||||
tax_10_incl, tax_20_excl, tax_15_incl = self.env['account.tax'].create([
|
||||
{'name': 'Tax 10% incl', 'amount': 10, 'price_include': True},
|
||||
{'name': 'Tax 20% excl', 'amount': 20, 'price_include': False},
|
||||
{'name': 'Tax 15% incl', 'amount': 15, 'price_include': True},
|
||||
])
|
||||
self.env['account.fiscal.position'].create([
|
||||
{
|
||||
'sequence': 1,
|
||||
'name': 'BE',
|
||||
'auto_apply': True,
|
||||
'country_id': self.env.ref('base.be').id,
|
||||
'tax_ids': [Command.create({'tax_src_id': tax_10_incl.id, 'tax_dest_id': tax_20_excl.id})],
|
||||
},
|
||||
{
|
||||
'sequence': 2,
|
||||
'name': 'NL',
|
||||
'auto_apply': True,
|
||||
'country_id': self.env.ref('base.nl').id,
|
||||
'tax_ids': [Command.create({'tax_src_id': tax_10_incl.id, 'tax_dest_id': tax_15_incl.id})],
|
||||
},
|
||||
])
|
||||
|
||||
product = self.env['product.product'].create({
|
||||
'name': 'Product test',
|
||||
'list_price': 100,
|
||||
'website_published': True,
|
||||
self.env['product.template'].create({
|
||||
'name': 'Test Repair Service',
|
||||
'type': 'service',
|
||||
'service_tracking': 'repair',
|
||||
'sale_ok': True,
|
||||
'taxes_id': [tax_10_incl.id]
|
||||
'is_published': True,
|
||||
})
|
||||
so = self.env['sale.order'].create({
|
||||
'partner_id': partner.id,
|
||||
'website_id': self.website.id,
|
||||
'order_line': [Command.create({
|
||||
'product_id': product.id,
|
||||
'name': 'Product test',
|
||||
})]
|
||||
self.start_tour("/", 'shop_repair_product', login=None)
|
||||
|
||||
def test_checkout_with_rewrite(self):
|
||||
# check that checkout page can be open with step rewritten
|
||||
self.env['website.rewrite'].create({
|
||||
'name': 'Test Address Rename',
|
||||
'redirect_type': '308',
|
||||
'url_from': '/shop/address',
|
||||
'url_to': '/test/address',
|
||||
})
|
||||
self.env['website.rewrite'].create({
|
||||
'name': 'Test Checkout Rename',
|
||||
'redirect_type': '308',
|
||||
'url_from': '/shop/checkout',
|
||||
'url_to': '/test/checkout',
|
||||
})
|
||||
self._create_so(partner_id=self.user_demo.partner_id.id)
|
||||
self.authenticate('demo', 'demo')
|
||||
response = self.url_open('/shop/address')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.url[-13:], '/test/address')
|
||||
|
||||
self.assertEqual(
|
||||
[so.amount_untaxed, so.amount_tax, so.amount_total],
|
||||
[90.91, 9.09, 100.0]
|
||||
)
|
||||
# check that navigation (next and previous checkout steps) are correct
|
||||
allowed_steps_domain = self.website._get_allowed_steps_domain()
|
||||
checkout_step = self.env.ref('website_sale.checkout_step_delivery')
|
||||
previous_step = checkout_step._get_previous_checkout_step(allowed_steps_domain)
|
||||
next_step = checkout_step._get_next_checkout_step(allowed_steps_domain)
|
||||
root = lxml.html.fromstring(response.content)
|
||||
self.assertEqual(len(root.xpath(f'//a[@href="{previous_step.step_href}"]//span[text()="{previous_step.back_button_label}"]')), 2)
|
||||
self.assertEqual(len(root.xpath(f'//a[@name="website_sale_main_button"][not(@href)]//span[text()="{next_step.main_button_label}"]')), 2)
|
||||
|
||||
env = api.Environment(self.env.cr, self.website.user_id.id, {})
|
||||
with MockRequest(self.env, website=self.website.with_env(env), sale_order_id=so.id) as req:
|
||||
req.httprequest.method = "POST"
|
||||
|
||||
self.WebsiteSaleController.address(**be_address_POST)
|
||||
self.assertEqual(
|
||||
[so.amount_untaxed, so.amount_tax, so.amount_total],
|
||||
[90.91, 18.18, 109.09] # (100 : (1 + 10%)) * (1 + 20%) = 109.09
|
||||
)
|
||||
|
||||
self.WebsiteSaleController.address(**nl_address_POST)
|
||||
self.assertEqual(
|
||||
[so.amount_untaxed, so.amount_tax, so.amount_total],
|
||||
[90.91, 13.64, 104.55] # (100 : (1 + 10%)) * (1 + 15%) = 104.55
|
||||
)
|
||||
|
||||
def test_08_payment_term_when_address_change(self):
|
||||
''' This test ensures that the payment term set when triggering
|
||||
`onchange_partner_id` by changing the address of a website sale
|
||||
order is computed by `sale_get_payment_term`.
|
||||
'''
|
||||
self._setUp_multicompany_env()
|
||||
product_id = self.env['product.product'].create({
|
||||
'name': 'Product A',
|
||||
'list_price': 100,
|
||||
'website_published': True,
|
||||
'sale_ok': True}).id
|
||||
|
||||
env = api.Environment(self.env.cr, self.portal_user.id, {})
|
||||
with MockRequest(env, website=self.website.with_env(env).with_context(website_id=self.website.id)) as req:
|
||||
req.httprequest.method = "POST"
|
||||
|
||||
self.WebsiteSaleController.cart_update(product_id)
|
||||
so = self.portal_user.sale_order_ids[0]
|
||||
self.assertTrue(so.payment_term_id, "A payment term should be set by default on the sale order")
|
||||
|
||||
self.default_address_values['partner_id'] = self.portal_partner.id
|
||||
self.default_address_values['name'] = self.portal_partner.name
|
||||
self.WebsiteSaleController.address(**self.default_address_values)
|
||||
self.assertTrue(so.payment_term_id, "A payment term should still be set on the sale order")
|
||||
|
||||
so.website_id = False
|
||||
self.WebsiteSaleController.address(**self.default_address_values)
|
||||
self.assertFalse(so.payment_term_id, "The website default payment term should not be set on a sale order not coming from the website")
|
||||
def test_update_same_address_billing_shipping_edit(self):
|
||||
''' Phone field should be required when updating an adress for billing and shipping '''
|
||||
self.env['product.product'].create({
|
||||
'name': 'Office Chair Black TEST',
|
||||
'list_price': 12.50,
|
||||
'is_published': True,
|
||||
})
|
||||
self.start_tour("/shop", 'update_billing_shipping_address', login="website_user")
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.fields import Command
|
||||
from odoo.tests import HttpCase, tagged
|
||||
|
||||
|
||||
|
|
@ -15,11 +16,31 @@ class TestSitemap(HttpCase):
|
|||
'name': 'Level 1',
|
||||
}, {
|
||||
'name': 'Level 2',
|
||||
}, {
|
||||
'name': 'Level 2A',
|
||||
}])
|
||||
self.cats[3].parent_id = self.cats[1].id
|
||||
self.cats[2].parent_id = self.cats[1].id
|
||||
self.cats[1].parent_id = self.cats[0].id
|
||||
# 'Level 2' cetegory must have at least one published product to be visible by public users
|
||||
self.env['product.product'].create({
|
||||
'name': 'Dummy product',
|
||||
'list_price': 100.0,
|
||||
'public_categ_ids': [Command.link(self.cats[2].id)],
|
||||
'is_published': True,
|
||||
})
|
||||
# 'Level 2A' category will contains only archived products, so should be hidden to public users
|
||||
prodA = self.env['product.product'].create({
|
||||
'name': 'Dummy product A',
|
||||
'list_price': 100.0,
|
||||
'public_categ_ids': [Command.link(self.cats[3].id)],
|
||||
'is_published': True,
|
||||
})
|
||||
prodA.product_tmpl_id.active = False
|
||||
|
||||
def test_01_shop_route_sitemap(self):
|
||||
resp = self.url_open('/sitemap.xml')
|
||||
level2_url = '/shop/category/level-0-level-1-level-2-%s' % self.cats[2].id
|
||||
self.assertTrue(level2_url in resp.text, "Category entry in sitemap should be prefixed by its parent hierarchy.")
|
||||
self.assertIn(level2_url, resp.text, "Category entry in sitemap should be prefixed by its parent hierarchy.")
|
||||
level2A_url = '/shop/category/level-0-level-1-level-2a-%s' % self.cats[3].id
|
||||
self.assertNotIn(level2A_url, resp.text, "Category entry with no active products should not be listed in sitemap.")
|
||||
|
|
|
|||
|
|
@ -1,14 +1,16 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import logging
|
||||
|
||||
from odoo import Command
|
||||
from odoo.addons.website_sale.controllers.main import WebsiteSale
|
||||
from odoo.addons.website.tools import MockRequest
|
||||
from odoo.addons.website.tests.common import HttpCaseWithWebsiteUser
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.fields import Command
|
||||
from odoo.tests import HttpCase, tagged
|
||||
|
||||
from odoo.addons.http_routing.tests.common import MockRequest
|
||||
from odoo.addons.website_sale.controllers.main import WebsiteSale
|
||||
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
ATTACHMENT_DATA = [
|
||||
|
|
@ -45,8 +47,9 @@ class TestProductPictureController(HttpCase):
|
|||
|
||||
def _create_product_images(self):
|
||||
with MockRequest(self.product.env, website=self.website):
|
||||
self.WebsiteSaleController.add_product_images(
|
||||
self.WebsiteSaleController.add_product_media(
|
||||
[{'id': attachment.id} for attachment in self.attachments],
|
||||
'image',
|
||||
self.product.id,
|
||||
self.product.product_tmpl_id.id,
|
||||
)
|
||||
|
|
@ -114,8 +117,9 @@ class TestProductPictureController(HttpCase):
|
|||
})
|
||||
self.assertEqual(0, len(product_template.product_variant_ids))
|
||||
with MockRequest(product_template.env, website=self.website):
|
||||
self.WebsiteSaleController.add_product_images(
|
||||
self.WebsiteSaleController.add_product_media(
|
||||
[{'id': self.attachments[0].id}],
|
||||
'image',
|
||||
False,
|
||||
product_template.id,
|
||||
[product_template_attribute_line.product_template_value_ids[0].id],
|
||||
|
|
@ -223,26 +227,21 @@ class TestProductPictureController(HttpCase):
|
|||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestWebsiteSaleEditor(HttpCase):
|
||||
class TestWebsiteSaleEditor(HttpCaseWithWebsiteUser):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
cls.env['res.users'].create({
|
||||
'name': 'Restricted Editor',
|
||||
'login': 'restricted',
|
||||
'password': 'restricted',
|
||||
'groups_id': [Command.set([
|
||||
cls.env.ref('base.group_user').id,
|
||||
cls.env.ref('sales_team.group_sale_manager').id,
|
||||
cls.env.ref('website.group_website_restricted_editor').id
|
||||
])]
|
||||
})
|
||||
cls.user_website_user.group_ids += cls.env.ref('sales_team.group_sale_manager')
|
||||
cls.user_website_user.group_ids += cls.env.ref('product.group_product_manager')
|
||||
|
||||
def test_category_page_and_products_snippet(self):
|
||||
category = self.env['product.public.category'].create({
|
||||
'name': 'Test Category',
|
||||
})
|
||||
self.env['product.public.category'].create({
|
||||
'parent_id': category.id,
|
||||
'name': 'Test Category - Child',
|
||||
})
|
||||
self.env['product.template'].create({
|
||||
'name': 'Test Product',
|
||||
'website_published': True,
|
||||
|
|
@ -254,7 +253,7 @@ class TestWebsiteSaleEditor(HttpCase):
|
|||
'name': 'Test Product Outside Category',
|
||||
'website_published': True,
|
||||
})
|
||||
self.start_tour(self.env['website'].get_client_action_url('/shop'), 'category_page_and_products_snippet_edition', login='restricted')
|
||||
self.start_tour(self.env['website'].get_client_action_url('/shop'), 'category_page_and_products_snippet_edition', login="admin")
|
||||
self.start_tour('/shop', 'category_page_and_products_snippet_use', login=None)
|
||||
|
||||
def test_website_sale_restricted_editor_ui(self):
|
||||
|
|
@ -263,4 +262,57 @@ class TestWebsiteSaleEditor(HttpCase):
|
|||
'website_sequence': 0,
|
||||
'website_published': True,
|
||||
})
|
||||
self.start_tour(self.env['website'].get_client_action_url('/shop'), 'website_sale_restricted_editor_ui', login='restricted')
|
||||
self.start_tour(self.env['website'].get_client_action_url('/shop'), 'website_sale_restricted_editor_ui', login="website_user")
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestProductVideoUpload(HttpCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.website = cls.env['website'].browse(1)
|
||||
cls.WebsiteSaleController = WebsiteSale()
|
||||
cls.product = cls.env['product.product'].create({
|
||||
'name': 'Test Video Product',
|
||||
'standard_price': 100.0,
|
||||
'list_price': 120.0,
|
||||
'website_published': True,
|
||||
})
|
||||
cls.video_data = {
|
||||
'src': 'https://www.youtube.com/watch?v=dQw4w9WgXcQ', # A placeholder video URL
|
||||
'name': 'Test Video',
|
||||
}
|
||||
|
||||
def _upload_video(self):
|
||||
with MockRequest(self.product.env, website=self.website):
|
||||
self.WebsiteSaleController.add_product_media(
|
||||
[{'src': self.video_data['src'], 'name': self.video_data['name']}],
|
||||
'video',
|
||||
self.product.id,
|
||||
self.product.product_tmpl_id.id,
|
||||
)
|
||||
|
||||
def test_video_upload(self):
|
||||
# Upload a video to the product
|
||||
self._upload_video()
|
||||
|
||||
# Retrieve the product's media data
|
||||
video_url = self.product.product_template_image_ids[0].video_url
|
||||
image_1920 = self.product.product_template_image_ids[0].image_1920
|
||||
|
||||
# Check that the video URL and thumbnail are correctly saved
|
||||
self.assertEqual(video_url, self.video_data['src'])
|
||||
self.assertIsNotNone(image_1920) # Ensure a thumbnail was generated
|
||||
|
||||
# Verify that the video was added as part of the media
|
||||
self.assertEqual(len(self.product.product_template_image_ids), 1)
|
||||
|
||||
def test_video_upload_invalid(self):
|
||||
# Try to upload invalid video data (e.g., empty src)
|
||||
with MockRequest(self.product.env, website=self.website):
|
||||
with self.assertRaises(ValidationError):
|
||||
self.WebsiteSaleController.add_product_media(
|
||||
[{'src': '', 'name': 'Invalid Video'}],
|
||||
'video',
|
||||
self.product.id,
|
||||
self.product.product_tmpl_id.id,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -77,7 +77,8 @@ class TestAddToCartSnippet(HttpCase):
|
|||
'street2': "",
|
||||
'city': "Ramillies",
|
||||
'zip': 1367,
|
||||
'country_id': self.env.ref('base.be').id
|
||||
'country_id': self.env.ref('base.be').id,
|
||||
'phone': "+32 123456789"
|
||||
})
|
||||
self.env.ref('base.user_admin').country_id = self.env.ref('base.be')
|
||||
self.start_tour("/", 'add_to_cart_snippet_tour', login="admin")
|
||||
|
|
|
|||
|
|
@ -1,70 +1,91 @@
|
|||
# coding: utf-8
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from datetime import datetime
|
||||
from functools import partial
|
||||
from unittest.mock import patch
|
||||
|
||||
from odoo.addons.base.tests.common import TransactionCaseWithUserPortal
|
||||
from odoo.addons.website_sale.controllers.main import WebsiteSale, PaymentPortal
|
||||
from odoo.addons.website.tools import MockRequest
|
||||
from odoo.addons.website_sale.models.product_template import ProductTemplate
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tests.common import tagged
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
from odoo.fields import Command
|
||||
from odoo.tests import tagged
|
||||
|
||||
from odoo.addons.product.tests.common import ProductVariantsCommon
|
||||
from odoo.addons.website_sale.controllers.cart import Cart
|
||||
from odoo.addons.website_sale.controllers.combo_configurator import (
|
||||
WebsiteSaleComboConfiguratorController,
|
||||
)
|
||||
from odoo.addons.website_sale.controllers.main import WebsiteSale
|
||||
from odoo.addons.website_sale.controllers.payment import PaymentPortal
|
||||
from odoo.addons.website_sale.models.product_template import ProductTemplate
|
||||
from odoo.addons.website_sale.tests.common import MockRequest, WebsiteSaleCommon
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class WebsiteSaleCart(TransactionCaseWithUserPortal):
|
||||
class TestWebsiteSaleCart(ProductVariantsCommon, WebsiteSaleCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(WebsiteSaleCart, cls).setUpClass()
|
||||
cls.website = cls.env['website'].browse(1)
|
||||
super().setUpClass()
|
||||
cls.user_portal = cls._create_new_portal_user()
|
||||
cls.WebsiteSaleController = WebsiteSale()
|
||||
cls.WebsiteSaleCartController = Cart()
|
||||
cls.public_user = cls.env.ref('base.public_user')
|
||||
|
||||
def test_add_cart_deleted_product(self):
|
||||
# Create a published product then unlink it
|
||||
product = self.env['product.product'].create({
|
||||
cls.product = cls.env['product.product'].create({
|
||||
'name': 'Test Product',
|
||||
'sale_ok': True,
|
||||
'website_published': True,
|
||||
'lst_price': 1000.0,
|
||||
'standard_price': 800.0,
|
||||
})
|
||||
product_id = product.id
|
||||
product.unlink()
|
||||
|
||||
with self.assertRaises(UserError):
|
||||
with MockRequest(product.with_user(self.public_user).env, website=self.website.with_user(self.public_user)):
|
||||
self.WebsiteSaleController.cart_update_json(product_id=product_id, add_qty=1)
|
||||
def test_add_cart_deleted_product(self):
|
||||
# Unlink published product.
|
||||
product_template_id = self.product.product_tmpl_id
|
||||
product_id = self.product.id
|
||||
self.product.unlink()
|
||||
website = self.website.with_user(self.public_user)
|
||||
|
||||
with self.assertRaises(UserError), MockRequest(website.env, website=website):
|
||||
self.WebsiteSaleCartController.add_to_cart(
|
||||
product_template_id=product_template_id,
|
||||
product_id=product_id,
|
||||
quantity=1,
|
||||
)
|
||||
|
||||
def test_add_cart_unpublished_product(self):
|
||||
# Try to add an unpublished product
|
||||
product = self.env['product.product'].create({
|
||||
'name': 'Test Product',
|
||||
'sale_ok': True,
|
||||
})
|
||||
self.product.website_published = False
|
||||
# Environment must be public user as admin can add unpublished products to cart
|
||||
website = self.website.with_user(self.public_user)
|
||||
|
||||
with self.assertRaises(UserError):
|
||||
with MockRequest(product.with_user(self.public_user).env, website=self.website.with_user(self.public_user)):
|
||||
self.WebsiteSaleController.cart_update_json(product_id=product.id, add_qty=1)
|
||||
with self.assertRaises(UserError), MockRequest(website.env, website=website):
|
||||
self.WebsiteSaleCartController.add_to_cart(
|
||||
product_template_id=self.product.product_tmpl_id,
|
||||
product_id=self.product.id,
|
||||
quantity=1,
|
||||
)
|
||||
|
||||
# public but remove sale_ok
|
||||
product.sale_ok = False
|
||||
product.website_published = True
|
||||
self.product.sale_ok = False
|
||||
self.product.website_published = True
|
||||
|
||||
with self.assertRaises(UserError):
|
||||
with MockRequest(product.with_user(self.public_user).env, website=self.website.with_user(self.public_user)):
|
||||
self.WebsiteSaleController.cart_update_json(product_id=product.id, add_qty=1)
|
||||
with self.assertRaises(UserError), MockRequest(website.env, website=website):
|
||||
self.WebsiteSaleCartController.add_to_cart(
|
||||
product_template_id=self.product.product_tmpl_id,
|
||||
product_id=self.product.id,
|
||||
quantity=1,
|
||||
)
|
||||
|
||||
def test_add_cart_archived_product(self):
|
||||
# Try to add an archived product
|
||||
product = self.env['product.product'].create({
|
||||
'name': 'Test Product',
|
||||
'sale_ok': True,
|
||||
})
|
||||
product.active = False
|
||||
self.product.active = False
|
||||
website = self.website.with_user(self.public_user)
|
||||
|
||||
with self.assertRaises(UserError):
|
||||
with MockRequest(product.with_user(self.public_user).env, website=self.website.with_user(self.public_user)):
|
||||
self.WebsiteSaleController.cart_update_json(product_id=product.id, add_qty=1)
|
||||
with self.assertRaises(UserError), MockRequest(website.env, website=website):
|
||||
self.WebsiteSaleCartController.add_to_cart(
|
||||
product_template_id=self.product.product_tmpl_id,
|
||||
product_id=self.product.id,
|
||||
quantity=1,
|
||||
)
|
||||
|
||||
def test_zero_price_product_rule(self):
|
||||
"""
|
||||
|
|
@ -80,75 +101,106 @@ class WebsiteSaleCart(TransactionCaseWithUserPortal):
|
|||
})
|
||||
product_consu = self.env['product.product'].create({
|
||||
'name': 'Cannot be zero price',
|
||||
'detailed_type': 'consu',
|
||||
'type': 'consu',
|
||||
'list_price': 0,
|
||||
'website_published': True,
|
||||
})
|
||||
product_service = self.env['product.product'].create({
|
||||
'name': 'Can be zero price',
|
||||
'detailed_type': 'service',
|
||||
'type': 'service',
|
||||
'list_price': 0,
|
||||
'website_published': True,
|
||||
})
|
||||
|
||||
with patch.object(ProductTemplate, '_get_product_types_allow_zero_price', lambda pt: ['service']):
|
||||
with self.assertRaises(UserError, msg="'consu' product type is not allowed to have a 0 price sale"), \
|
||||
MockRequest(self.env, website=website_prevent_zero_price):
|
||||
self.WebsiteSaleController.cart_update_json(product_id=product_consu.id, add_qty=1)
|
||||
with (
|
||||
self.assertRaises(UserError, msg="'consu' product type is not allowed to have a 0 price sale"),
|
||||
MockRequest(self.env, website=website_prevent_zero_price)
|
||||
):
|
||||
self.WebsiteSaleCartController.add_to_cart(
|
||||
product_template_id=product_consu.product_tmpl_id,
|
||||
product_id=product_consu.id,
|
||||
quantity=1,
|
||||
)
|
||||
|
||||
# service types should not raise a UserError
|
||||
with (
|
||||
patch.object(ProductTemplate, '_get_product_types_allow_zero_price', lambda pt: ['no']),
|
||||
MockRequest(self.env, website=website_prevent_zero_price),
|
||||
):
|
||||
# service_tracking 'no' should not raise error
|
||||
with MockRequest(self.env, website=website_prevent_zero_price):
|
||||
self.WebsiteSaleController.cart_update_json(product_id=product_service.id, add_qty=1)
|
||||
self.WebsiteSaleCartController.add_to_cart(
|
||||
product_template_id=product_service.product_tmpl_id,
|
||||
product_id=product_service.id,
|
||||
quantity=1,
|
||||
)
|
||||
|
||||
def test_update_cart_before_payment(self):
|
||||
product = self.env['product.product'].create({
|
||||
'name': 'Test Product',
|
||||
'sale_ok': True,
|
||||
'website_published': True,
|
||||
'lst_price': 1000.0,
|
||||
'standard_price': 800.0,
|
||||
})
|
||||
website = self.website.with_user(self.public_user)
|
||||
with MockRequest(product.with_user(self.public_user).env, website=website):
|
||||
self.WebsiteSaleController.cart_update_json(product_id=product.id, add_qty=1)
|
||||
sale_order = website.sale_get_order()
|
||||
with MockRequest(website.env, website=website) as request:
|
||||
self.WebsiteSaleCartController.add_to_cart(
|
||||
product_template_id=self.product.product_tmpl_id,
|
||||
product_id=self.product.id,
|
||||
quantity=1,
|
||||
)
|
||||
sale_order = request.cart
|
||||
sale_order.access_token = 'test_token'
|
||||
old_amount = sale_order.amount_total
|
||||
self.WebsiteSaleController.cart_update_json(product_id=product.id, add_qty=1)
|
||||
self.WebsiteSaleCartController.add_to_cart(
|
||||
product_template_id=self.product.product_tmpl_id,
|
||||
product_id=self.product.id,
|
||||
quantity=1,
|
||||
)
|
||||
# Try processing payment with the old amount
|
||||
with self.assertRaises(UserError):
|
||||
PaymentPortal().shop_payment_transaction(sale_order.id, sale_order.access_token, amount=old_amount)
|
||||
PaymentPortal().shop_payment_transaction(
|
||||
sale_order.id,
|
||||
sale_order.access_token,
|
||||
amount=old_amount
|
||||
)
|
||||
|
||||
def test_check_order_delivery_before_payment(self):
|
||||
website = self.website.with_user(self.public_user)
|
||||
with MockRequest(website.env, website=website):
|
||||
sale_order = self.env['sale.order'].create({
|
||||
'partner_id': self.public_user.id,
|
||||
'order_line': [Command.create({'product_id': self.product.id})],
|
||||
'access_token': 'test_token',
|
||||
})
|
||||
# Try processing payment with a storable product and no carrier_id
|
||||
with self.assertRaises(ValidationError):
|
||||
PaymentPortal().shop_payment_transaction(sale_order.id, sale_order.access_token)
|
||||
|
||||
def test_update_cart_zero_qty(self):
|
||||
# Try to remove a product that has already been removed
|
||||
product = self.env['product.product'].create({
|
||||
'name': 'Test Product',
|
||||
'sale_ok': True,
|
||||
'website_published': True,
|
||||
'lst_price': 1000.0,
|
||||
'standard_price': 800.0,
|
||||
})
|
||||
portal_user = self.user_portal
|
||||
website = self.website.with_user(portal_user)
|
||||
|
||||
SaleOrderLine = self.env['sale.order.line']
|
||||
|
||||
with MockRequest(product.with_user(portal_user).env, website=website):
|
||||
with MockRequest(website.env, website=website) as request:
|
||||
# add the product to the cart
|
||||
self.WebsiteSaleController.cart_update_json(product_id=product.id, add_qty=1)
|
||||
sale_order = website.sale_get_order()
|
||||
self.WebsiteSaleCartController.add_to_cart(
|
||||
product_template_id=self.product.product_tmpl_id,
|
||||
product_id=self.product.id,
|
||||
quantity=1,
|
||||
)
|
||||
sale_order = request.cart
|
||||
self.assertEqual(sale_order.amount_untaxed, 1000.0)
|
||||
|
||||
# remove the product from the cart
|
||||
self.WebsiteSaleController.cart_update_json(product_id=product.id, line_id=sale_order.order_line.id, set_qty=0)
|
||||
self.WebsiteSaleCartController.update_cart(
|
||||
line_id=sale_order.order_line.id,
|
||||
quantity=0,
|
||||
)
|
||||
self.assertEqual(sale_order.amount_total, 0.0)
|
||||
self.assertEqual(sale_order.order_line, SaleOrderLine)
|
||||
|
||||
# removing the product again doesn't add a line with zero quantity
|
||||
self.WebsiteSaleController.cart_update_json(product_id=product.id, set_qty=0)
|
||||
self.assertEqual(sale_order.order_line, SaleOrderLine)
|
||||
|
||||
self.WebsiteSaleController.cart_update_json(product_id=product.id, add_qty=0)
|
||||
self.WebsiteSaleCartController.update_cart(
|
||||
line_id=sale_order.order_line.id,
|
||||
quantity=0,
|
||||
)
|
||||
self.assertEqual(sale_order.cart_quantity, 0.0)
|
||||
self.assertEqual(sale_order.order_line, SaleOrderLine)
|
||||
|
||||
def test_unpublished_accessory_product_visibility(self):
|
||||
|
|
@ -158,18 +210,244 @@ class WebsiteSaleCart(TransactionCaseWithUserPortal):
|
|||
'is_published': False,
|
||||
})
|
||||
|
||||
product = self.env['product.product'].create({
|
||||
'name': 'Test Product',
|
||||
'sale_ok': True,
|
||||
'website_published': True,
|
||||
'accessory_product_ids': [Command.link(accessory_product.id)]
|
||||
self.product.accessory_product_ids = [Command.link(accessory_product.id)]
|
||||
self.empty_cart._cart_add(product_id=self.product.id)
|
||||
self.assertEqual(len(self.empty_cart.with_user(self.public_user)._cart_accessories()), 0)
|
||||
|
||||
def test_cart_new_fpos_from_geoip(self):
|
||||
fpos_be = self.env["account.fiscal.position"].create({
|
||||
'name': 'Fiscal Position BE',
|
||||
'country_id': self.country_be.id,
|
||||
'company_id': self.company.id,
|
||||
'auto_apply': True,
|
||||
})
|
||||
|
||||
website = self.website.with_user(self.public_user)
|
||||
with MockRequest(product.with_user(self.public_user).env, website=self.website.with_user(self.public_user)):
|
||||
self.WebsiteSaleController.cart_update_json(product_id=product.id, add_qty=1)
|
||||
sale_order = website.sale_get_order()
|
||||
self.assertEqual(len(sale_order._cart_accessories()), 0)
|
||||
with MockRequest(website.env, website=website, country_code='BE') as request:
|
||||
self.assertEqual(request.fiscal_position, fpos_be)
|
||||
self.WebsiteSaleCartController.add_to_cart(
|
||||
product_template_id=self.product.product_tmpl_id,
|
||||
product_id=self.product.id,
|
||||
quantity=1,
|
||||
)
|
||||
self.assertEqual(
|
||||
request.cart.fiscal_position_id, fpos_be,
|
||||
"Fiscal position should be determined from GEOIP country for public users."
|
||||
)
|
||||
|
||||
def test_cart_update_with_fpos(self):
|
||||
# We will test that the mapping of an 10% included tax by a 6% by a fiscal position is taken
|
||||
# into account when updating the cart
|
||||
self._enable_pricelists()
|
||||
pricelist = self.pricelist
|
||||
# Create fiscal position mapping taxes 10% -> 6%
|
||||
fpos = self.env['account.fiscal.position'].create({
|
||||
'name': 'test',
|
||||
})
|
||||
# Add 10% tax on product
|
||||
tax10, tax6 = self.env['account.tax'].create([
|
||||
{
|
||||
'name': "Test tax 10",
|
||||
'amount': 10,
|
||||
'price_include_override': 'tax_included',
|
||||
'amount_type': 'percent'
|
||||
}, {
|
||||
'name': "Test tax 6",
|
||||
'fiscal_position_ids': fpos,
|
||||
'amount': 6,
|
||||
'price_include_override': 'tax_included',
|
||||
'amount_type': 'percent'
|
||||
},
|
||||
])
|
||||
tax6.original_tax_ids = tax10
|
||||
|
||||
test_product = self.env['product.product'].create({
|
||||
'name': 'Test Product',
|
||||
'list_price': 110,
|
||||
'taxes_id': [Command.set([tax10.id])],
|
||||
})
|
||||
|
||||
# Add discount of 50% for pricelist
|
||||
pricelist.write({
|
||||
'item_ids': [
|
||||
Command.create({
|
||||
'base': "list_price",
|
||||
'compute_price': "percentage",
|
||||
'percent_price': 50,
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
so = self.env['sale.order'].create({
|
||||
'partner_id': self.env.user.partner_id.id,
|
||||
'order_line': [
|
||||
Command.create({
|
||||
'product_id': test_product.id,
|
||||
})
|
||||
]
|
||||
})
|
||||
sol = so.order_line
|
||||
self.assertEqual(round(sol.price_total), 55.0, "110$ with 50% discount 10% included tax")
|
||||
self.assertEqual(round(sol.price_tax), 5.0, "110$ with 50% discount 10% included tax")
|
||||
|
||||
so.fiscal_position_id = fpos
|
||||
so._recompute_taxes()
|
||||
so._cart_update_line_quantity(line_id=sol.id, quantity=2)
|
||||
self.assertEqual(
|
||||
round(sol.price_total),
|
||||
106,
|
||||
"2 units @ 100$ with 50% discount + 6% tax (mapped from fp 10% -> 6%)"
|
||||
)
|
||||
|
||||
def test_cart_update_with_fpos_no_variant_product(self):
|
||||
# We will test that the mapping of an 10% included tax by a 0% by a fiscal position is taken
|
||||
# into account when updating the cart for no_variant product
|
||||
# Add 10% tax on product
|
||||
fpos = self.env['account.fiscal.position'].create({
|
||||
'name': 'test',
|
||||
})
|
||||
tax10, tax0 = self.env['account.tax'].create([
|
||||
{
|
||||
'name': "Test tax 10",
|
||||
'amount': 10,
|
||||
'price_include_override': 'tax_included',
|
||||
'amount_type': 'percent'
|
||||
}, {
|
||||
'name': "Test tax 0",
|
||||
'fiscal_position_ids': fpos,
|
||||
'amount': 0,
|
||||
'price_include_override': 'tax_included',
|
||||
'amount_type': 'percent'
|
||||
},
|
||||
])
|
||||
tax0.original_tax_ids = tax10
|
||||
|
||||
# create an attribute with one variant
|
||||
product_attribute = self.env['product.attribute'].create({
|
||||
'name': 'test_attr',
|
||||
'display_type': 'radio',
|
||||
'create_variant': 'no_variant',
|
||||
'value_ids': [
|
||||
Command.create({
|
||||
'name': 'pa_value',
|
||||
'sequence': 1,
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
product_template = self.env['product.template'].create({
|
||||
'name': 'prod_no_variant',
|
||||
'list_price': 110,
|
||||
'taxes_id': [Command.set([tax10.id])],
|
||||
'is_published': True,
|
||||
'attribute_line_ids': [
|
||||
Command.create({
|
||||
'attribute_id': product_attribute.id,
|
||||
'value_ids': [Command.set(product_attribute.value_ids.ids)],
|
||||
}),
|
||||
],
|
||||
})
|
||||
product = product_template.product_variant_id
|
||||
|
||||
# create a so for user using the fiscal position
|
||||
so = self.env['sale.order'].create({
|
||||
'partner_id': self.env.user.partner_id.id,
|
||||
'order_line': [
|
||||
Command.create({
|
||||
'product_id': product.id,
|
||||
})
|
||||
]
|
||||
})
|
||||
sol = so.order_line
|
||||
self.assertEqual(round(sol.price_total), 110.0, "110$ with 10% included tax")
|
||||
|
||||
so.fiscal_position_id = fpos
|
||||
so._recompute_taxes()
|
||||
so._cart_update_line_quantity(line_id=sol.id, quantity=2)
|
||||
self.assertEqual(
|
||||
round(sol.price_total),
|
||||
200,
|
||||
"200$ with public price+ 0% tax (mapped from fp 10% -> 0%)"
|
||||
)
|
||||
|
||||
def test_cart_lines_aggregation(self):
|
||||
# Adding a product with the same no_variant attributes combination twice should create only
|
||||
# one SOLine
|
||||
product_no_variants = self.env['product.template'].create({
|
||||
'name': 'No variants product (TEST)',
|
||||
'attribute_line_ids': [Command.create({
|
||||
'attribute_id': self.no_variant_attribute.id,
|
||||
'value_ids': [Command.set(self.no_variant_attribute.value_ids.ids)],
|
||||
})],
|
||||
})
|
||||
no_variant_ptavs = product_no_variants.attribute_line_ids.product_template_value_ids
|
||||
no_variant_ptav = no_variant_ptavs[0]
|
||||
add_one = partial(
|
||||
self.empty_cart._cart_add,
|
||||
product_id=product_no_variants.product_variant_id.id,
|
||||
quantity=1,
|
||||
)
|
||||
self.assertEqual(len(self.empty_cart.order_line), 0)
|
||||
|
||||
add_one(no_variant_attribute_value_ids=no_variant_ptav.ids)
|
||||
self.assertEqual(len(self.empty_cart.order_line), 1)
|
||||
|
||||
add_one(no_variant_attribute_value_ids=no_variant_ptav.ids)
|
||||
self.assertEqual(len(self.empty_cart.order_line), 1)
|
||||
self.assertEqual(self.empty_cart.order_line.product_uom_qty, 2)
|
||||
|
||||
# Providing `no_variant_attribute_value_ids` should be optional if there's only 1 value...
|
||||
product_no_variants.attribute_line_ids.value_ids = self.no_variant_attribute.value_ids[0]
|
||||
add_one(no_variant_attribute_value_ids=[])
|
||||
self.assertEqual(len(self.empty_cart.order_line), 1)
|
||||
self.assertEqual(self.empty_cart.order_line.product_uom_qty, 3)
|
||||
|
||||
# ...except if it's a multi-checkbox attribute, making the value optional
|
||||
self.no_variant_attribute.display_type = 'multi'
|
||||
add_one(no_variant_attribute_value_ids=[])
|
||||
self.assertEqual(len(self.empty_cart.order_line), 2)
|
||||
self.assertEqual(self.empty_cart.order_line.mapped('product_uom_qty'), [3, 1])
|
||||
|
||||
add_one(no_variant_attribute_value_ids=no_variant_ptav.ids)
|
||||
self.assertEqual(len(self.empty_cart.order_line), 2)
|
||||
self.assertEqual(self.empty_cart.order_line.mapped('product_uom_qty'), [4, 1])
|
||||
|
||||
def test_cart_new_pricelist_from_geoip(self):
|
||||
"""Check that, when adding a new partner to a website order, the partner's GeoIP
|
||||
is factored into the pricelist recomputation.
|
||||
"""
|
||||
self._enable_pricelists()
|
||||
eu_group = self.env.ref('base.europe')
|
||||
not_eu_group = self.env['res.country.group'].create({
|
||||
'name': "Not EU",
|
||||
'country_ids': self.env['res.country'].search([
|
||||
('id', 'not in', eu_group.country_ids.ids),
|
||||
]).ids,
|
||||
})
|
||||
|
||||
_pricelist_eu, pricelist_not_eu = self.env['product.pricelist'].create([{
|
||||
'name': "EU",
|
||||
'country_group_ids': eu_group.ids,
|
||||
'website_id': self.website.id,
|
||||
'sequence': 1,
|
||||
}, {
|
||||
'name': "Not EU",
|
||||
'country_group_ids': not_eu_group.ids,
|
||||
'website_id': self.website.id,
|
||||
'sequence': 2,
|
||||
}])
|
||||
|
||||
website = self.website.with_user(self.public_user)
|
||||
with MockRequest(website.env, website=website, country_code='US') as request:
|
||||
self.WebsiteSaleCartController.add_to_cart(
|
||||
product_template_id=self.product.product_tmpl_id,
|
||||
product_id=self.product.id,
|
||||
quantity=1,
|
||||
)
|
||||
cart = request.cart
|
||||
self.assertEqual(cart.pricelist_id, pricelist_not_eu)
|
||||
cart.partner_id = self.partner.create({'name': "New Partner"})
|
||||
self.assertEqual(cart.pricelist_id, pricelist_not_eu)
|
||||
|
||||
def test_remove_archived_product_line(self):
|
||||
"""If an order has a line containing an archived product,
|
||||
|
|
@ -182,9 +460,13 @@ class WebsiteSaleCart(TransactionCaseWithUserPortal):
|
|||
'sale_ok': True,
|
||||
'website_published': True,
|
||||
})
|
||||
with MockRequest(self.env(user=user), website=website):
|
||||
self.WebsiteSaleController.cart_update_json(product_id=product.id, add_qty=1)
|
||||
order = website.sale_get_order()
|
||||
with MockRequest(self.env(user=user), website=website) as request:
|
||||
self.WebsiteSaleCartController.add_to_cart(
|
||||
product_template_id=product.product_tmpl_id,
|
||||
product_id=product.id,
|
||||
quantity=1,
|
||||
)
|
||||
order = request.cart
|
||||
|
||||
# pre-condition: the order contains an active product
|
||||
self.assertRecordValues(order.order_line, [{
|
||||
|
|
@ -194,7 +476,7 @@ class WebsiteSaleCart(TransactionCaseWithUserPortal):
|
|||
|
||||
# Act: archive the product and open the cart
|
||||
product.active = False
|
||||
self.WebsiteSaleController.cart()
|
||||
self.WebsiteSaleCartController.cart()
|
||||
|
||||
# Assert: the line has been removed
|
||||
self.assertFalse(order.order_line)
|
||||
|
|
@ -205,8 +487,8 @@ class WebsiteSaleCart(TransactionCaseWithUserPortal):
|
|||
# Arrange
|
||||
user = self.public_user
|
||||
website = self.website.with_user(user)
|
||||
with MockRequest(self.env(user=user), website=website):
|
||||
order = website.sale_get_order(force_create=True)
|
||||
with MockRequest(self.env(user=user), website=website) as request:
|
||||
order = request.website._create_cart()
|
||||
order.order_line = [
|
||||
Command.create({
|
||||
"name": "Note",
|
||||
|
|
@ -220,9 +502,80 @@ class WebsiteSaleCart(TransactionCaseWithUserPortal):
|
|||
}])
|
||||
|
||||
# Act: open the cart
|
||||
self.WebsiteSaleController.cart()
|
||||
self.WebsiteSaleCartController.cart()
|
||||
|
||||
# Assert: the line is still there
|
||||
self.assertRecordValues(order.order_line, [{
|
||||
"display_type": "line_note",
|
||||
}])
|
||||
|
||||
def test_checkout_no_delivery_method_available(self):
|
||||
portal_user = self.user_portal
|
||||
website = self.website.with_user(portal_user)
|
||||
portal_user.write(self.dummy_partner_address_values)
|
||||
self.carrier.country_ids = [Command.set((2,))]
|
||||
self.product.type = 'consu'
|
||||
with (
|
||||
MockRequest(website.env, website=website) as request,
|
||||
patch(
|
||||
'odoo.addons.website_sale.models.sale_order.SaleOrder._get_preferred_delivery_method',
|
||||
return_value=self.env['delivery.carrier'],
|
||||
)
|
||||
):
|
||||
order = request.website._create_cart()
|
||||
order.order_line = [
|
||||
Command.create({
|
||||
'product_id': self.product.id,
|
||||
'product_uom_qty': 1.0,
|
||||
})
|
||||
]
|
||||
self.WebsiteSaleController.shop_checkout()
|
||||
|
||||
def test_add_to_cart_company_branch(self):
|
||||
"""Test that a product/website from a company branch
|
||||
can be added to the cart."""
|
||||
branch_a = self.env["res.company"].create(
|
||||
{
|
||||
"name": "Branch A",
|
||||
"parent_id": self.env.company.id,
|
||||
}
|
||||
)
|
||||
website = self.env["website"].create(
|
||||
{
|
||||
"name": "Branch A Website",
|
||||
"company_id": branch_a.id,
|
||||
}
|
||||
)
|
||||
self.product.company_id = branch_a
|
||||
with MockRequest(
|
||||
self.product.with_user(website.user_id).env,
|
||||
website=website.with_user(website.user_id),
|
||||
):
|
||||
branch_a.invalidate_recordset()
|
||||
data = WebsiteSaleComboConfiguratorController().website_sale_combo_configurator_get_data(
|
||||
date=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||
product_tmpl_id=self.product.product_tmpl_id.id,
|
||||
quantity=1,
|
||||
)
|
||||
self.assertEqual(data["quantity"], 1)
|
||||
|
||||
def test_get_cart_after_company_change(self):
|
||||
"""Finding the customer cart shouldn't crash even if their company changed."""
|
||||
internal_user = self.env.ref('base.user_admin')
|
||||
website = self.website.with_user(internal_user)
|
||||
with MockRequest(website.env, website=website):
|
||||
# Create a cart for the user
|
||||
self.WebsiteSaleCartController.add_to_cart(
|
||||
product_template_id=self.product.product_tmpl_id,
|
||||
product_id=self.product.id,
|
||||
quantity=1,
|
||||
)
|
||||
|
||||
# Change the user's company (will also update the user's partner)
|
||||
other_company = self.env['res.company'].create({'name': "Other Company"})
|
||||
internal_user.company_ids = [Command.link(other_company.id)]
|
||||
internal_user.company_id = other_company
|
||||
with MockRequest(website.env, website=website) as request:
|
||||
# We shouldn't find any abandonned cart if the customer isn't allowed to
|
||||
# buy from this website (because their contact belongs to another company)
|
||||
self.assertFalse(request.cart)
|
||||
|
|
|
|||
|
|
@ -1,15 +1,32 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from datetime import datetime
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from unittest.mock import patch
|
||||
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from odoo.tests import tagged
|
||||
from odoo.addons.base.tests.common import HttpCaseWithUserPortal
|
||||
|
||||
from odoo.addons.base.tests.common import TransactionCaseWithUserPortal
|
||||
from odoo.addons.mail.models.mail_template import MailTemplate
|
||||
|
||||
|
||||
class TestWebsiteSaleCartAbandonedCommon(HttpCaseWithUserPortal):
|
||||
class TestWebsiteSaleCartAbandonedCommon(TransactionCaseWithUserPortal):
|
||||
|
||||
def send_mail_patched(self, sale_order_id):
|
||||
email_got_sent = False
|
||||
|
||||
def check_send_mail_called(this, res_id, email_values, *args, **kwargs):
|
||||
nonlocal email_got_sent
|
||||
if res_id == sale_order_id:
|
||||
email_got_sent = True
|
||||
|
||||
with patch.object(MailTemplate, 'send_mail', check_send_mail_called):
|
||||
self.env['website']._send_abandoned_cart_email()
|
||||
return email_got_sent
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestWebsiteSaleCartAbandoned(TestWebsiteSaleCartAbandonedCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
|
|
@ -48,6 +65,7 @@ class TestWebsiteSaleCartAbandonedCommon(HttpCaseWithUserPortal):
|
|||
'product_id': product.id,
|
||||
'product_uom_qty': 1,
|
||||
}]]
|
||||
cls.payment_method_id = cls.env.ref('payment.payment_method_unknown').id
|
||||
cls.so0before = cls.env['sale.order'].create({
|
||||
'partner_id': cls.customer.id,
|
||||
'website_id': cls.website0.id,
|
||||
|
|
@ -107,20 +125,6 @@ class TestWebsiteSaleCartAbandonedCommon(HttpCaseWithUserPortal):
|
|||
'order_line': add_order_line,
|
||||
})
|
||||
|
||||
def send_mail_patched(self, sale_order_id):
|
||||
email_got_sent = False
|
||||
|
||||
def check_send_mail_called(this, res_id, email_values, *args, **kwargs):
|
||||
nonlocal email_got_sent
|
||||
if res_id == sale_order_id:
|
||||
email_got_sent = True
|
||||
|
||||
with patch.object(MailTemplate, 'send_mail', check_send_mail_called):
|
||||
self.env['website']._send_abandoned_cart_email()
|
||||
return email_got_sent
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestWebsiteSaleCartAbandoned(TestWebsiteSaleCartAbandonedCommon):
|
||||
def test_search_abandoned_cart(self):
|
||||
"""Make sure the search for abandoned carts uses the delay and public partner specified in each website."""
|
||||
SaleOrder = self.env['sale.order']
|
||||
|
|
@ -149,6 +153,15 @@ class TestWebsiteSaleCartAbandoned(TestWebsiteSaleCartAbandonedCommon):
|
|||
|
||||
website = self.env['website'].get_current_website()
|
||||
website.send_abandoned_cart_email = True
|
||||
website.write(
|
||||
{
|
||||
"send_abandoned_cart_email_activation_time": (
|
||||
datetime.utcnow()
|
||||
- relativedelta(hours=website.cart_abandoned_delay)
|
||||
)
|
||||
- relativedelta(minutes=10)
|
||||
}
|
||||
)
|
||||
|
||||
product = self.env['product.product'].create({
|
||||
'name': 'The Product'
|
||||
|
|
@ -225,6 +238,7 @@ class TestWebsiteSaleCartAbandoned(TestWebsiteSaleCartAbandonedCommon):
|
|||
})
|
||||
transaction = self.env['payment.transaction'].create({
|
||||
'provider_id': 15,
|
||||
'payment_method_id': self.payment_method_id,
|
||||
'partner_id': self.customer.id,
|
||||
'reference': abandoned_sale_order.name,
|
||||
'amount': abandoned_sale_order.amount_total,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
# 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.addons.product.tests.common import ProductVariantsCommon
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestWebsiteSaleCartNotification(HttpCase, ProductVariantsCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.website = cls.env.company.website_id
|
||||
cls.size_attribute.create_variant = 'no_variant'
|
||||
cls.product_tmpl_1 = cls.env['product.template'].create({
|
||||
'name': 'website_sale_cart_notification_product_1',
|
||||
'type': 'consu',
|
||||
'website_published': True,
|
||||
'list_price': 1000,
|
||||
})
|
||||
cls.product_tmpl_2 = cls.env['product.template'].create({
|
||||
'name': 'website_sale_cart_notification_product_2',
|
||||
'type': 'consu',
|
||||
'website_published': True,
|
||||
'list_price': 5000,
|
||||
'attribute_line_ids': [Command.create({
|
||||
'attribute_id': cls.size_attribute.id,
|
||||
'value_ids': [Command.set([
|
||||
cls.size_attribute_s.id,
|
||||
cls.size_attribute_m.id,
|
||||
cls.size_attribute_l.id,
|
||||
])],
|
||||
})],
|
||||
})
|
||||
|
||||
def test_website_sale_cart_notification_tax_included(self):
|
||||
self.env.ref('website_sale.product_search').active = True
|
||||
self.website.show_line_subtotals_tax_selection = 'tax_included'
|
||||
self.start_tour("/", 'website_sale_cart_notification_tax_included')
|
||||
|
||||
def test_website_sale_cart_notification_tax_excluded(self):
|
||||
self.env.ref('website_sale.product_search').active = True
|
||||
self.website.show_line_subtotals_tax_selection = 'tax_excluded'
|
||||
self.start_tour("/", 'website_sale_cart_notification_tax_excluded')
|
||||
|
||||
def test_website_sale_cart_notification_qty_and_total(self):
|
||||
""" Check that adding product into cart which is already in the cart only display newly
|
||||
added qty count and total."""
|
||||
|
||||
self.env.ref('website_sale.product_search').active = True
|
||||
self.start_tour("/", 'website_sale_cart_notification_qty_and_total')
|
||||
|
|
@ -1,23 +1,24 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.models import Command
|
||||
from odoo.tests.common import tagged
|
||||
from unittest.mock import patch
|
||||
|
||||
from odoo.addons.payment.tests.common import PaymentCommon
|
||||
from odoo.addons.website.tools import MockRequest
|
||||
from odoo.fields import Command
|
||||
from odoo.tests.common import JsonRpcException, tagged
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
from odoo.addons.payment.tests.http_common import PaymentHttpCommon
|
||||
from odoo.addons.website_sale.tests.common import MockRequest, WebsiteSaleCommon
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class WebsiteSaleCartPayment(PaymentCommon):
|
||||
class WebsiteSaleCartPayment(PaymentHttpCommon, WebsiteSaleCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
cls.website = cls.env['website'].get_current_website()
|
||||
with MockRequest(cls.env, website=cls.website):
|
||||
cls.order = cls.website.sale_get_order(force_create=True) # Create the cart to retrieve
|
||||
cls.tx = cls.env['payment.transaction'].create({
|
||||
'payment_method_id': cls.payment_method_id,
|
||||
'amount': cls.amount,
|
||||
'currency_id': cls.currency.id,
|
||||
'provider_id': cls.provider.id,
|
||||
|
|
@ -25,17 +26,17 @@ class WebsiteSaleCartPayment(PaymentCommon):
|
|||
'operation': 'online_redirect',
|
||||
'partner_id': cls.partner.id,
|
||||
})
|
||||
cls.order.write({'transaction_ids': [Command.set([cls.tx.id])]})
|
||||
cls.cart.write({'transaction_ids': [Command.set([cls.tx.id])]})
|
||||
|
||||
def test_unpaid_orders_can_be_retrieved(self):
|
||||
""" Test that fetching sales orders linked to a payment transaction in the states 'draft',
|
||||
'cancel', or 'error' returns the orders. """
|
||||
for unpaid_order_tx_state in ('draft', 'cancel', 'error'):
|
||||
self.tx.state = unpaid_order_tx_state
|
||||
with MockRequest(self.env, website=self.website, sale_order_id=self.order.id):
|
||||
with MockRequest(self.env, website=self.website, sale_order_id=self.cart.id) as request:
|
||||
self.assertEqual(
|
||||
self.website.sale_get_order(),
|
||||
self.order,
|
||||
request.cart,
|
||||
self.cart,
|
||||
msg=f"The transaction state '{unpaid_order_tx_state}' should not prevent "
|
||||
f"retrieving the linked order.",
|
||||
)
|
||||
|
|
@ -43,12 +44,37 @@ class WebsiteSaleCartPayment(PaymentCommon):
|
|||
def test_paid_orders_cannot_be_retrieved(self):
|
||||
""" Test that fetching sales orders linked to a payment transaction in the states 'pending',
|
||||
'authorized', or 'done' returns an empty recordset to prevent updating the paid orders. """
|
||||
self.tx.provider_id.support_manual_capture = True
|
||||
self.tx.provider_id.support_manual_capture = 'full_only'
|
||||
for paid_order_tx_state in ('pending', 'authorized', 'done'):
|
||||
self.tx.state = paid_order_tx_state
|
||||
with MockRequest(self.env, website=self.website, sale_order_id=self.order.id):
|
||||
with MockRequest(self.env, website=self.website, sale_order_id=self.cart.id) as request:
|
||||
self.assertFalse(
|
||||
self.website.sale_get_order(),
|
||||
request.cart,
|
||||
msg=f"The transaction state '{paid_order_tx_state}' should prevent retrieving "
|
||||
f"the linked order.",
|
||||
)
|
||||
|
||||
@mute_logger('odoo.http')
|
||||
def test_transaction_route_rejects_unexpected_kwarg(self):
|
||||
url = self._build_url(f'/shop/payment/transaction/{self.cart.id}')
|
||||
route_kwargs = {
|
||||
'access_token': self.cart._portal_ensure_token(),
|
||||
'partner_id': self.partner.id, # This should be rejected.
|
||||
}
|
||||
with self.assertRaises(JsonRpcException, msg='odoo.exceptions.ValidationError'):
|
||||
self.make_jsonrpc_request(url, route_kwargs)
|
||||
|
||||
def test_payment_confirmation_mail(self):
|
||||
"""Check that a salesperson gets assigned when sending payment confirmation mails."""
|
||||
salesperson = self.env.ref('base.user_admin')
|
||||
self.website.salesperson_id = salesperson
|
||||
self.cart.user_id = False
|
||||
self.tx._set_pending()
|
||||
with patch.object(self.env.registry['sale.order'], '_send_order_notification_mail') as mock:
|
||||
self.tx._post_process()
|
||||
self.assertEqual(mock.call_count, 1, "One payment confirmation mail should be sent")
|
||||
self.assertEqual(
|
||||
self.cart.user_id,
|
||||
salesperson,
|
||||
"Salesperson should get assigned when sending payment confirmation mail",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.tests import tagged
|
||||
from odoo.tests.common import HttpCase
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestWebsiteSaleCartPopover(HttpCase):
|
||||
def setUp(self):
|
||||
super(TestWebsiteSaleCartPopover, self).setUp()
|
||||
self.env['product.product'].create({
|
||||
'name': 'website_sale_cart_popover_tour_product',
|
||||
'type': 'consu',
|
||||
'website_published': True,
|
||||
'list_price': 1000,
|
||||
})
|
||||
|
||||
def test_website_sale_cart_popover(self):
|
||||
self.start_tour("/", 'website_sale_cart_popover_tour', login="admin")
|
||||
|
|
@ -1,15 +1,18 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.tests import tagged
|
||||
from odoo.tests.common import HttpCase, TransactionCase
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
from odoo.addons.base.tests.common import HttpCaseWithUserPortal
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestWebsiteSaleCartRecovery(HttpCaseWithUserPortal):
|
||||
|
||||
def test_01_shop_cart_recovery_tour(self):
|
||||
"""The goal of this test is to make sure cart recovery works."""
|
||||
self.env.ref('base.user_admin').write({
|
||||
'email': 'mitchell.admin@example.com',
|
||||
})
|
||||
self.env['product.product'].create({
|
||||
'name': 'Acoustic Bloc Screens',
|
||||
'list_price': 2950.0,
|
||||
|
|
@ -22,50 +25,49 @@ class TestWebsiteSaleCartRecovery(HttpCaseWithUserPortal):
|
|||
@tagged('post_install', '-at_install')
|
||||
class TestWebsiteSaleCartRecoveryServer(TransactionCase):
|
||||
|
||||
def setUp(self):
|
||||
res = super(TestWebsiteSaleCartRecoveryServer, self).setUp()
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
self.customer = self.env['res.partner'].create({
|
||||
cls.customer = cls.env['res.partner'].create({
|
||||
'name': 'a',
|
||||
'email': 'a@example.com',
|
||||
})
|
||||
self.recovery_template_default = self.env.ref('website_sale.mail_template_sale_cart_recovery')
|
||||
self.recovery_template_custom1 = self.recovery_template_default.copy()
|
||||
self.recovery_template_custom2 = self.recovery_template_default.copy()
|
||||
cls.recovery_template_default = cls.env.ref('website_sale.mail_template_sale_cart_recovery')
|
||||
cls.recovery_template_custom1 = cls.recovery_template_default.copy()
|
||||
cls.recovery_template_custom2 = cls.recovery_template_default.copy()
|
||||
|
||||
self.website0 = self.env['website'].create({
|
||||
cls.website0 = cls.env['website'].create({
|
||||
'name': 'web0',
|
||||
'cart_recovery_mail_template_id': self.recovery_template_default.id,
|
||||
'cart_recovery_mail_template_id': cls.recovery_template_default.id,
|
||||
})
|
||||
self.website1 = self.env['website'].create({
|
||||
cls.website1 = cls.env['website'].create({
|
||||
'name': 'web1',
|
||||
'cart_recovery_mail_template_id': self.recovery_template_custom1.id,
|
||||
'cart_recovery_mail_template_id': cls.recovery_template_custom1.id,
|
||||
})
|
||||
self.website2 = self.env['website'].create({
|
||||
cls.website2 = cls.env['website'].create({
|
||||
'name': 'web2',
|
||||
'cart_recovery_mail_template_id': self.recovery_template_custom2.id,
|
||||
'cart_recovery_mail_template_id': cls.recovery_template_custom2.id,
|
||||
})
|
||||
self.so0 = self.env['sale.order'].create({
|
||||
'partner_id': self.customer.id,
|
||||
'website_id': self.website0.id,
|
||||
cls.so0 = cls.env['sale.order'].create({
|
||||
'partner_id': cls.customer.id,
|
||||
'website_id': cls.website0.id,
|
||||
'is_abandoned_cart': True,
|
||||
'cart_recovery_email_sent': False,
|
||||
})
|
||||
self.so1 = self.env['sale.order'].create({
|
||||
'partner_id': self.customer.id,
|
||||
'website_id': self.website1.id,
|
||||
cls.so1 = cls.env['sale.order'].create({
|
||||
'partner_id': cls.customer.id,
|
||||
'website_id': cls.website1.id,
|
||||
'is_abandoned_cart': True,
|
||||
'cart_recovery_email_sent': False,
|
||||
})
|
||||
self.so2 = self.env['sale.order'].create({
|
||||
'partner_id': self.customer.id,
|
||||
'website_id': self.website2.id,
|
||||
cls.so2 = cls.env['sale.order'].create({
|
||||
'partner_id': cls.customer.id,
|
||||
'website_id': cls.website2.id,
|
||||
'is_abandoned_cart': True,
|
||||
'cart_recovery_email_sent': False,
|
||||
})
|
||||
|
||||
return res
|
||||
|
||||
def test_cart_recovery_mail_template(self):
|
||||
"""Make sure that we get the correct cart recovery templates to send."""
|
||||
self.assertEqual(
|
||||
|
|
@ -112,7 +114,7 @@ class TestWebsiteSaleCartRecoveryServer(TransactionCase):
|
|||
sent_mail = {}
|
||||
for order in orders:
|
||||
mail = self.env["mail.mail"].search([
|
||||
('record_name', '=', order['name'])
|
||||
('res_id', '=', order['id'])
|
||||
])
|
||||
sent_mail.update({order: mail})
|
||||
|
||||
|
|
@ -121,6 +123,6 @@ class TestWebsiteSaleCartRecoveryServer(TransactionCase):
|
|||
"Each cart recovery mail has been sent exactly once."
|
||||
)
|
||||
self.assertTrue(
|
||||
all(order.access_token in sent_mail[order].body for order in orders),
|
||||
all(order.access_token in sent_mail[order].body_html for order in orders),
|
||||
"Each mail should contain the access token of the corresponding SO."
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,67 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.tests import tagged
|
||||
|
||||
from odoo.addons.website_sale.tests.common import WebsiteSaleCommon
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestWebsiteSaleCheckoutSteps(WebsiteSaleCommon):
|
||||
|
||||
def test_get_existing_specific_extra_step(self):
|
||||
specific_extra_step = self.website._get_checkout_step('/shop/extra_info')
|
||||
generic_extra_step = self.env.ref('website_sale.checkout_step_extra')
|
||||
self.assertNotEqual(specific_extra_step, generic_extra_step)
|
||||
self.assertEqual(specific_extra_step.website_id, self.website)
|
||||
|
||||
def test_translate_checkout_steps(self):
|
||||
"""Verify that loading languages correctly translates website-specific steps."""
|
||||
CheckoutStep = self.env['website.checkout.step']
|
||||
IrModuleModule = self.env['ir.module.module']
|
||||
|
||||
if self.env['website'].search_count([]) == 1:
|
||||
self.env['website'].create({'name': "My Website 2"})
|
||||
|
||||
default_payment_step = self.env.ref('website_sale.checkout_step_payment')
|
||||
default_payment_step_FR = default_payment_step.with_context(lang='fr_FR')
|
||||
website_1_step_FR, website_2_step_FR = CheckoutStep.with_context(lang='fr_FR').search([
|
||||
('website_id', '!=', False),
|
||||
('step_href', '=', default_payment_step.step_href),
|
||||
], limit=2)
|
||||
|
||||
# Activate French
|
||||
if not (lang_fr := self.env.ref('base.lang_fr')).active:
|
||||
lang_fr.active = True
|
||||
for fname, field in CheckoutStep._fields.items():
|
||||
if field.translate and default_payment_step[fname]:
|
||||
default_payment_step_FR[fname] = f"{default_payment_step[fname]} (FR)"
|
||||
|
||||
# Have a different translation for a specific website
|
||||
website_1_step_FR.name = "Pay in French"
|
||||
|
||||
# Load translations without overwrite
|
||||
IrModuleModule._load_module_terms(['website_sale'], ['fr_FR'])
|
||||
CheckoutStep.invalidate_model(['name'])
|
||||
self.assertEqual(
|
||||
website_1_step_FR.name, "Pay in French",
|
||||
"Loading translations without overwrite should keep existing translation",
|
||||
)
|
||||
self.assertIn(
|
||||
website_2_step_FR.name,
|
||||
("Payment (FR)", "Paiement"), # "Paiement" in case translation was already loaded
|
||||
"Loading translations should add missing term from default step",
|
||||
)
|
||||
|
||||
# Load translations with overwrite
|
||||
IrModuleModule._load_module_terms(['website_sale'], ['fr_FR'], overwrite=True)
|
||||
CheckoutStep.invalidate_model(['name'])
|
||||
self.assertEqual(
|
||||
website_1_step_FR.name, "Payment (FR)",
|
||||
"Loading translations with overwrite should update existing term from template",
|
||||
)
|
||||
|
||||
# Ensure all translatable fields were updated
|
||||
for fname, field in CheckoutStep._fields.items():
|
||||
if field.translate:
|
||||
self.assertEqual(website_1_step_FR[fname], default_payment_step_FR[fname])
|
||||
self.assertEqual(website_2_step_FR[fname], default_payment_step_FR[fname])
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
# 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.addons.website_sale.tests.common import WebsiteSaleCommon
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestWebsiteSaleComboConfigurator(HttpCase, WebsiteSaleCommon):
|
||||
def test_website_sale_combo_configurator(self):
|
||||
no_variant_attribute = self.env['product.attribute'].create({
|
||||
'name': "No variant attribute",
|
||||
'create_variant': 'no_variant',
|
||||
'value_ids': [
|
||||
Command.create({'name': "A"}),
|
||||
Command.create({'name': "B", 'is_custom': True, 'default_extra_price': 1}),
|
||||
],
|
||||
})
|
||||
product_a1 = self.env['product.template'].create({
|
||||
'name': "Product A1",
|
||||
'website_published': True,
|
||||
'attribute_line_ids': [
|
||||
Command.create({
|
||||
'attribute_id': no_variant_attribute.id,
|
||||
'value_ids': [Command.set(no_variant_attribute.value_ids.ids)],
|
||||
}),
|
||||
],
|
||||
})
|
||||
combo_a = self.env['product.combo'].create({
|
||||
'name': "Combo A",
|
||||
'combo_item_ids': [
|
||||
Command.create({'product_id': product_a1.product_variant_id.id, 'extra_price': 5}),
|
||||
Command.create({'product_id': self._create_product(name="Product A2").id}),
|
||||
],
|
||||
})
|
||||
combo_b = self.env['product.combo'].create({
|
||||
'name': "Combo B",
|
||||
'combo_item_ids': [
|
||||
Command.create({'product_id': self._create_product(name="Product B1").id}),
|
||||
Command.create({'product_id': self._create_product(name="Product B2").id}),
|
||||
],
|
||||
})
|
||||
self._create_product(
|
||||
name="Combo product",
|
||||
list_price=25,
|
||||
type='combo',
|
||||
combo_ids=[
|
||||
Command.link(combo_a.id),
|
||||
Command.link(combo_b.id),
|
||||
],
|
||||
)
|
||||
self.website.show_line_subtotals_tax_selection = 'tax_included'
|
||||
self.start_tour('/', 'website_sale_combo_configurator')
|
||||
|
||||
def test_website_sale_combo_configurator_single_configuration(self):
|
||||
""" Test that the combo configurator isn't shown if there's a single configuration. """
|
||||
no_variant_attribute = self.env['product.attribute'].create({
|
||||
'name': "No variant attribute",
|
||||
'create_variant': 'no_variant',
|
||||
'value_ids': [Command.create({'name': "A"})],
|
||||
})
|
||||
product = self.env['product.template'].create({
|
||||
'name': "Test product",
|
||||
'attribute_line_ids': [
|
||||
Command.create({
|
||||
'attribute_id': no_variant_attribute.id,
|
||||
'value_ids': [Command.set(no_variant_attribute.value_ids.ids)],
|
||||
}),
|
||||
],
|
||||
'website_published': True,
|
||||
})
|
||||
combo = self.env['product.combo'].create({
|
||||
'name': "Test combo",
|
||||
'combo_item_ids': [Command.create({'product_id': product.product_variant_id.id})],
|
||||
})
|
||||
self._create_product(
|
||||
name="Combo product",
|
||||
type='combo',
|
||||
combo_ids=[Command.link(combo.id)],
|
||||
)
|
||||
self.start_tour('/', 'website_sale_combo_configurator_single_configuration')
|
||||
|
||||
def test_website_sale_combo_configurator_single_configurable_item(self):
|
||||
""" Test that the combo configurator is shown if there's a single combo item, but that combo
|
||||
item is configurable.
|
||||
"""
|
||||
no_variant_attribute = self.env['product.attribute'].create({
|
||||
'name': "No variant attribute",
|
||||
'create_variant': 'no_variant',
|
||||
'value_ids': [Command.create({'name': "A", 'is_custom': True})],
|
||||
})
|
||||
product = self.env['product.template'].create({
|
||||
'name': "Test product",
|
||||
'attribute_line_ids': [
|
||||
Command.create({
|
||||
'attribute_id': no_variant_attribute.id,
|
||||
'value_ids': [Command.set(no_variant_attribute.value_ids.ids)],
|
||||
}),
|
||||
],
|
||||
})
|
||||
combo = self.env['product.combo'].create({
|
||||
'name': "Test combo",
|
||||
'combo_item_ids': [Command.create({'product_id': product.product_variant_id.id})],
|
||||
})
|
||||
self._create_product(
|
||||
name="Combo product",
|
||||
type='combo',
|
||||
combo_ids=[Command.link(combo.id)],
|
||||
)
|
||||
self.start_tour('/', 'website_sale_combo_configurator_single_configurable_item')
|
||||
|
|
@ -1,120 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
from odoo.addons.base.tests.common import HttpCaseWithUserPortal
|
||||
from odoo.addons.product.tests.common import ProductCommon
|
||||
from odoo.tests import tagged
|
||||
|
||||
from odoo import Command
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestWebsiteSaleFiscalPosition(ProductCommon, HttpCaseWithUserPortal):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
cls.env.company.country_id = cls.env.ref('base.us')
|
||||
cls._use_currency('USD')
|
||||
|
||||
cls.website = cls.env.ref('website.default_website')
|
||||
cls.website.company_id = cls.env.company
|
||||
|
||||
# Create a fiscal position with a mapping of taxes
|
||||
cls.tax_15_excl = cls.env['account.tax'].create({
|
||||
'name': "15% excl",
|
||||
'type_tax_use': 'sale',
|
||||
'amount_type': 'percent',
|
||||
'amount': 15,
|
||||
'price_include': False,
|
||||
'include_base_amount': False,
|
||||
})
|
||||
cls.tax_0 = cls.env['account.tax'].create({
|
||||
'name': "0%",
|
||||
'type_tax_use': 'sale',
|
||||
'amount_type': 'percent',
|
||||
'amount': 0,
|
||||
})
|
||||
belgium = cls.env.ref('base.be')
|
||||
cls.fpos_be = cls.env['account.fiscal.position'].create({
|
||||
'name': "Fiscal Position BE",
|
||||
'auto_apply': True,
|
||||
'country_id': belgium.id,
|
||||
'tax_ids': [Command.create({
|
||||
'tax_src_id': cls.tax_15_excl.id,
|
||||
'tax_dest_id': cls.tax_0.id,
|
||||
})],
|
||||
})
|
||||
cls.partner_portal.country_id = belgium
|
||||
|
||||
def test_shop_fiscal_position_products_template(self):
|
||||
"""
|
||||
The `website_sale.products` template is computationally intensive
|
||||
and therefore uses the cache.
|
||||
The goal of this test is to check that this template
|
||||
is up to date with the fiscal position detected.
|
||||
"""
|
||||
# Set setting to display tax included on the website
|
||||
config = self.env['res.config.settings'].create({})
|
||||
config.show_line_subtotals_tax_selection = "tax_included"
|
||||
config.execute()
|
||||
# Create a pricelist which will be automatically detected
|
||||
self.env['product.pricelist'].create({
|
||||
'name': 'EUROPE EUR',
|
||||
'selectable': True,
|
||||
'website_id': self.website.id,
|
||||
'country_group_ids': [Command.link(self.env.ref('base.europe').id)],
|
||||
'sequence': 1,
|
||||
'currency_id': self.env.ref('base.EUR').id,
|
||||
})
|
||||
# Create the product to be used for analysis
|
||||
self.env["product.product"].create({
|
||||
'name': "Super product",
|
||||
'list_price': 40.00,
|
||||
'taxes_id': self.tax_15_excl.ids,
|
||||
'website_published': True,
|
||||
})
|
||||
# Create a conversion rate (1 USD <=> 2 EUR)
|
||||
self.env['res.currency.rate'].search([]).unlink()
|
||||
self.env['res.currency.rate'].create({
|
||||
'company_id': self.env.company.id,
|
||||
'currency_id': self.env.ref('base.EUR').id,
|
||||
'company_rate': 2,
|
||||
'name': '2023-01-01',
|
||||
})
|
||||
|
||||
self.partner_portal.country_id = self.env.ref('base.be')
|
||||
|
||||
# [1] By going to the shop page with the portal user,
|
||||
# a t-cache key `pricelist,products` + `fiscal_position_id` is generated
|
||||
self.start_tour("/shop", 'website_sale_fiscal_position_portal_tour', login="portal")
|
||||
# [2] If we return to the page with a public user
|
||||
# and take the portal user's pricelist,
|
||||
# the prices must not be those previously calculated for the portal user.
|
||||
# Because the fiscal position differs from that of the public user.
|
||||
self.start_tour("/shop", 'website_sale_fiscal_position_public_tour', login="")
|
||||
|
||||
def test_recompute_taxes_on_address_change(self):
|
||||
tax_15_incl = self.tax_15_excl.copy({'name': "15% incl", 'price_include': True})
|
||||
self.fpos_be.tax_ids.tax_src_id = self.product.taxes_id = tax_15_incl
|
||||
self.product.website_published = True
|
||||
cart = self.env['sale.order'].create({
|
||||
'partner_id': self.partner_portal.id,
|
||||
'website_id': self.website.id,
|
||||
'order_line': [Command.create({'product_id': self.product.id})],
|
||||
})
|
||||
amount_untaxed = cart.amount_untaxed
|
||||
self.assertEqual(cart.fiscal_position_id, self.fpos_be)
|
||||
self.assertEqual(cart.order_line.tax_id, self.tax_0)
|
||||
|
||||
self.partner_portal.country_id = self.env.ref('base.us')
|
||||
self.assertNotEqual(cart.fiscal_position_id, self.fpos_be)
|
||||
self.assertEqual(cart.order_line.tax_id, tax_15_incl)
|
||||
self.assertEqual(cart.amount_untaxed, amount_untaxed, "Untaxed amount should not change")
|
||||
|
||||
cart.action_confirm()
|
||||
self.partner_portal.country_id = self.env.ref('base.be')
|
||||
self.assertEqual(
|
||||
cart.order_line.tax_id,
|
||||
tax_15_incl,
|
||||
"Tax should no longer change after order confirmation",
|
||||
)
|
||||
|
|
@ -0,0 +1,352 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from lxml import etree
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from odoo.fields import Command
|
||||
from odoo.tests import HttpCase, tagged
|
||||
|
||||
from odoo.addons.website_sale.tests.common import MockRequest
|
||||
from odoo.addons.website_sale.tests.common_gmc import WebsiteSaleGMCCommon
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestWebsiteSaleGMC(WebsiteSaleGMCCommon, HttpCase):
|
||||
|
||||
def test_gmc_xml_accessible_if_gmc_setting_enabled(self):
|
||||
response = self.url_open(self.gmc_feed.url)
|
||||
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
||||
def test_gmc_xml_not_found_if_gmc_setting_disabled(self):
|
||||
self.website.enabled_gmc_src = False
|
||||
|
||||
response = self.url_open(self.gmc_feed.url)
|
||||
|
||||
self.assertEqual(404, response.status_code, 'Not Found')
|
||||
|
||||
def test_gmc_xml_correct_xml_format(self):
|
||||
response = self.url_open(self.gmc_feed.url)
|
||||
|
||||
gmc_xml = etree.XML(response.content) # assert valid xml
|
||||
self.assertEqual(self.website.name, gmc_xml.xpath('//title')[0].text)
|
||||
self.assertURLEqual('/en', gmc_xml.xpath('//link')[0].text)
|
||||
self.assertEqual(
|
||||
'This is the homepage of the website',
|
||||
gmc_xml.xpath('//description')[0].text,
|
||||
)
|
||||
|
||||
def test_gmc_xml_localization(self):
|
||||
fr_lang = self.env['res.lang']._activate_lang('fr_FR')
|
||||
self.website.language_ids += fr_lang
|
||||
self.gmc_feed.lang_id = fr_lang
|
||||
self.red_sofa.with_context(lang=fr_lang.code).name = 'Canapé'
|
||||
self.color_attribute_red.with_context(lang=fr_lang.code).name = 'rouge'
|
||||
|
||||
self.update_items()
|
||||
|
||||
self.assertRegex(
|
||||
self.parse_http_location(self.red_sofa_item['link']).path,
|
||||
f'^\\/{fr_lang.url_code}.*',
|
||||
'The links must redirect to the french website.',
|
||||
)
|
||||
self.assertEqual(
|
||||
'Canapé (rouge)',
|
||||
self.red_sofa_item['title'],
|
||||
)
|
||||
|
||||
def test_gmc_xml_pricelist(self):
|
||||
self.gmc_feed.pricelist_id = self.eur_pricelist
|
||||
|
||||
with MockRequest(
|
||||
self.env,
|
||||
website=self.website,
|
||||
website_sale_current_pl=self.pricelist.id,
|
||||
):
|
||||
gmc_xml = etree.XML(self.gmc_feed._render_gmc_feed().encode())
|
||||
|
||||
self.assertEqual(
|
||||
'1100.0 EUR', # 1000.0 * 1.1 (EUR rate)
|
||||
gmc_xml.xpath(
|
||||
'//item[g:id="SOFA-R"]/g:price', namespaces={'g': 'http://base.google.com/ns/1.0'}
|
||||
)[0].text
|
||||
)
|
||||
|
||||
def test_gmc_items_required_fields(self):
|
||||
self.update_items()
|
||||
|
||||
for item in self.items.values():
|
||||
self.assertLessEqual(
|
||||
{
|
||||
'id',
|
||||
'title',
|
||||
'description',
|
||||
'link',
|
||||
'image_link',
|
||||
'availability',
|
||||
'price',
|
||||
'identifier_exists',
|
||||
},
|
||||
item.keys(),
|
||||
) # subseteq
|
||||
|
||||
def test_gmc_items_use_internal_reference_if_exists(self):
|
||||
"""Test prefer internal code to database id"""
|
||||
# setup: red_sofa.code = 'SOFA-R', blue_sofa.code = False
|
||||
self.update_items()
|
||||
|
||||
self.assertEqual(self.red_sofa.code, self.red_sofa_item['id'])
|
||||
self.assertEqual(self.blue_sofa.id, self.blue_sofa_item['id'])
|
||||
|
||||
def test_gmc_items_link_redirects_to_correct_product_case_default_pricelist(self):
|
||||
self.update_items()
|
||||
|
||||
for product in self.red_sofa + self.blue_sofa:
|
||||
response = self.url_open(self.items[product]['link'])
|
||||
|
||||
self.assertEqual(200, response.status_code)
|
||||
self.assertURLEqual(product.website_url, response.url)
|
||||
|
||||
def test_gmc_items_link_redirects_to_correct_product_case_specific_pricelist(self):
|
||||
self.gmc_feed.pricelist_id = self.eur_pricelist
|
||||
self.update_items()
|
||||
|
||||
for product in self.red_sofa + self.blue_sofa:
|
||||
response = self.url_open(self.items[product]['link'])
|
||||
|
||||
self.assertEqual(200, response.status_code)
|
||||
url = urlparse(product.website_url)
|
||||
self.assertURLEqual(
|
||||
f'{url.path}'
|
||||
f'?attribute_values={",".join(str(i) for i in product.product_template_attribute_value_ids.product_attribute_value_id.ids)}'
|
||||
f'&pricelist={self.eur_pricelist.id}'
|
||||
f'#{url.fragment}',
|
||||
response.url,
|
||||
)
|
||||
|
||||
def test_gmc_items_prices_match_website_prices_default(self):
|
||||
self.update_items()
|
||||
|
||||
self.assertEqual('1000.0 USD', self.red_sofa_item['price'])
|
||||
self.assertEqual('1200.0 USD', self.blue_sofa_item['price'])
|
||||
self.start_tour(
|
||||
self.red_sofa_item['link'],
|
||||
'website_sale_gmc_check_advertised_prices_red_sofa_default',
|
||||
)
|
||||
self.start_tour(
|
||||
self.blue_sofa_item['link'],
|
||||
'website_sale_gmc_check_advertised_prices_blue_sofa_default',
|
||||
)
|
||||
|
||||
def test_gmc_items_prices_match_website_prices_christmas(self):
|
||||
self.gmc_feed.pricelist_id = self._create_pricelist(
|
||||
name="EUR Christmas Sales",
|
||||
currency_id=self.eur_currency.id,
|
||||
selectable=True,
|
||||
item_ids=[
|
||||
Command.create({
|
||||
'product_tmpl_id': self.product_template_sofa.id,
|
||||
'compute_price': 'percentage',
|
||||
'percent_price': 10.0,
|
||||
'date_start': datetime.now() - timedelta(1),
|
||||
'date_end': datetime.now() + timedelta(1),
|
||||
}),
|
||||
Command.create({
|
||||
'compute_price': 'percentage',
|
||||
'percent_price': 0.0,
|
||||
}),
|
||||
],
|
||||
)
|
||||
|
||||
self.update_items()
|
||||
|
||||
# 1000.0 (list_price) * 1.1 (EUR rate) - 10% (discount)
|
||||
self.assertEqual('990.0 EUR', self.red_sofa_item['sale_price'])
|
||||
# 1200.0 (list_price) * 1.1 (EUR rate) - 10% (discount)
|
||||
self.assertEqual('1188.0 EUR', self.blue_sofa_item['sale_price'])
|
||||
# 100.0 (list_price) * 1.1 (EUR rate)
|
||||
self.assertEqual('110.0 EUR', self.items[self.blanket]['price'])
|
||||
self.assertNotIn('sale_price', self.items[self.blanket]) # no discount
|
||||
self.assertNotEqual(self.red_sofa_item['link'], self.blue_sofa_item['link'])
|
||||
self.start_tour(
|
||||
self.red_sofa_item['link'],
|
||||
'website_sale_gmc_check_advertised_prices_red_sofa_christmas',
|
||||
)
|
||||
self.start_tour(
|
||||
self.blue_sofa_item['link'],
|
||||
'website_sale_gmc_check_advertised_prices_blue_sofa_christmas',
|
||||
)
|
||||
|
||||
def test_gmc_items_prices_match_website_prices_tax_included(self):
|
||||
# 15% taxes
|
||||
self.website.show_line_subtotals_tax_selection = 'tax_included'
|
||||
|
||||
self.update_items()
|
||||
|
||||
self.assertEqual('1150.0 USD', self.red_sofa_item['price'])
|
||||
self.assertEqual('1380.0 USD', self.blue_sofa_item['price'])
|
||||
self.start_tour(
|
||||
self.red_sofa_item['link'],
|
||||
'website_sale_gmc_check_advertised_prices_red_sofa_tax_included',
|
||||
)
|
||||
self.start_tour(
|
||||
self.blue_sofa_item['link'],
|
||||
'website_sale_gmc_check_advertised_prices_blue_sofa_tax_included',
|
||||
)
|
||||
|
||||
def test_gmc_items_additional_images_limit_to_10(self):
|
||||
image = self._create_image('blue')
|
||||
self.blue_sofa.write({
|
||||
'product_variant_image_ids': [
|
||||
Command.create({
|
||||
'name': f'image {i}',
|
||||
'image_1920': image,
|
||||
})
|
||||
for i in range(12)
|
||||
]
|
||||
})
|
||||
|
||||
self.update_items()
|
||||
|
||||
self.assertEqual(10, len(self.blue_sofa_item['additional_image_link']))
|
||||
|
||||
def test_gmc_items_identifier_exists_iff_barcode_exists(self):
|
||||
self.red_sofa.barcode = '0232344532564'
|
||||
|
||||
self.update_items()
|
||||
|
||||
self.assertEqual(self.red_sofa.barcode, self.red_sofa_item['gtin'])
|
||||
self.assertEqual('yes', self.red_sofa_item['identifier_exists'])
|
||||
self.assertNotIn('gtin', self.blue_sofa_item)
|
||||
self.assertEqual('no', self.blue_sofa_item['identifier_exists'])
|
||||
|
||||
def test_gmc_items_sorted_types(self):
|
||||
# Furnitures / Sofas
|
||||
# Furnitures / Indoor Furnitures / Indoor Sofas
|
||||
furnitures_categ, sofas_categ = self._create_public_category([
|
||||
{'name': 'Furnitures'},
|
||||
{'name': 'Sofas', 'sequence': 1},
|
||||
])
|
||||
indoor_sofas_categ = self._create_public_category([
|
||||
{'name': 'Indoor Furnitures', 'parent_id': furnitures_categ.id},
|
||||
{'name': 'Indoor Sofas', 'sequence': 2},
|
||||
])
|
||||
self.public_categories = sofas_categ + indoor_sofas_categ
|
||||
self.product_template_sofa.public_categ_ids = self.public_categories
|
||||
|
||||
self.update_items()
|
||||
|
||||
self.assertListEqual(
|
||||
list(
|
||||
name.replace('/', '>')
|
||||
for name in self.public_categories.sorted('sequence').mapped('display_name')
|
||||
),
|
||||
self.red_sofa_item['product_type']
|
||||
)
|
||||
|
||||
def test_gmc_items_types_limit_to_5(self):
|
||||
self.product_template_sofa.write({
|
||||
'public_categ_ids': [
|
||||
Command.create({'name': f'Category {i}'}) for i in range(6)
|
||||
]
|
||||
})
|
||||
|
||||
self.update_items()
|
||||
|
||||
self.assertEqual(5, len(self.red_sofa_item['product_type']))
|
||||
|
||||
def test_gmc_product_variants(self):
|
||||
product_one_variant = self.env['product.template'].create({
|
||||
'name': 'Test product',
|
||||
'attribute_line_ids': [
|
||||
Command.create({
|
||||
'attribute_id': attr.attribute_id.id,
|
||||
'value_ids': [Command.link(attr.id)],
|
||||
})
|
||||
for attr in (self.color_attribute_green + self.size_attribute_l)
|
||||
],
|
||||
'list_price': 49.0,
|
||||
'is_published': True,
|
||||
}).product_variant_ids
|
||||
self.products |= product_one_variant
|
||||
|
||||
self.update_items()
|
||||
|
||||
# same template
|
||||
self.assertEqual(self.red_sofa_item['item_group_id'], self.blue_sofa_item['item_group_id'])
|
||||
# no other variant
|
||||
self.assertNotIn('item_group_id', self.items[product_one_variant])
|
||||
|
||||
def test_gmc_items_bundle_if_is_combo_product(self):
|
||||
self.update_items()
|
||||
|
||||
self.assertEqual('yes', self.items[self.sofa_bundle]['is_bundle'])
|
||||
self.assertEqual('no', self.red_sofa_item['is_bundle'])
|
||||
|
||||
def test_gmc_items_sorted_labels(self):
|
||||
tags = [f'tag {i}' for i in range(3)]
|
||||
self.product_template_sofa.write({
|
||||
'product_tag_ids': [
|
||||
Command.create({'name': tag, 'sequence': i})
|
||||
for i, tag in enumerate(tags)
|
||||
]
|
||||
})
|
||||
|
||||
self.update_items()
|
||||
|
||||
self.assertListEqual(tags, [name for _, name in self.red_sofa_item['custom_label']])
|
||||
|
||||
def test_gmc_items_tags_limit_to_5(self):
|
||||
self.product_template_sofa.write({
|
||||
'product_tag_ids': [
|
||||
Command.create({'name': f"Tag {i}", 'sequence': i})
|
||||
for i in range(10)
|
||||
]
|
||||
})
|
||||
|
||||
self.update_items()
|
||||
|
||||
self.assertEqual(5, len(self.red_sofa_item['custom_label']))
|
||||
|
||||
def test_gmc_items_default_availability_in_stock(self):
|
||||
self.update_items()
|
||||
|
||||
self.assertEqual(
|
||||
'in_stock',
|
||||
self.red_sofa_item['availability'],
|
||||
)
|
||||
|
||||
def _setup_6l_water_pack(self):
|
||||
self.env.user.group_ids |= self.env.ref('uom.group_uom')
|
||||
uom_litre = self.env.ref('uom.product_uom_pack_6')
|
||||
base_unit_litre = self.env['website.base.unit'].create({'name': 'L'})
|
||||
six_pack = self.env['product.product'].create([{
|
||||
'name': 'Water Pack 6L',
|
||||
'list_price': 12.0,
|
||||
'uom_id': uom_litre.id,
|
||||
'base_unit_count': 6.0,
|
||||
'base_unit_id': base_unit_litre.id,
|
||||
'is_published': True,
|
||||
}])
|
||||
self.products |= six_pack
|
||||
return six_pack
|
||||
|
||||
def test_gmc_items_unit_pricing_iff_product_reference_enabled(self):
|
||||
six_pack = self._setup_6l_water_pack()
|
||||
self.update_items()
|
||||
self.assertNotIn('unit_pricing_measure', self.items[six_pack])
|
||||
|
||||
# enable "Product Reference Price" setting
|
||||
self.env.user.group_ids |= self.env.ref('website_sale.group_show_uom_price')
|
||||
self.update_items()
|
||||
|
||||
self.assertEqual('6.0l', self.items[six_pack]['unit_pricing_measure'], '$12 / 6l')
|
||||
|
||||
def test_gmc_items_dont_send_unsupported_unit(self):
|
||||
six_pack = self._setup_6l_water_pack()
|
||||
six_pack.base_unit_id = False # remove `L` alias -> falls back to `Pack of 6`
|
||||
|
||||
self.update_items()
|
||||
|
||||
self.assertNotIn('unit_pricing_measure', self.items[six_pack])
|
||||
|
|
@ -4,14 +4,22 @@ import base64
|
|||
import io
|
||||
|
||||
from PIL import Image
|
||||
from odoo.tests.common import HOST
|
||||
from odoo.tools import config
|
||||
|
||||
import odoo.tests
|
||||
from odoo.fields import Command
|
||||
from odoo.tests import HttpCase, tagged
|
||||
|
||||
from odoo.addons.website.tests.common import HttpCaseWithWebsiteUser
|
||||
|
||||
|
||||
@odoo.tests.common.tagged('post_install', '-at_install')
|
||||
class TestWebsiteSaleImage(odoo.tests.HttpCase):
|
||||
def _create_image(color='black', dims=(1920, 1080), format='JPEG'):
|
||||
f = io.BytesIO()
|
||||
Image.new('RGB', dims, color).save(f, format) # type: ignore
|
||||
f.seek(0)
|
||||
return base64.b64encode(f.read())
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestWebsiteSaleImage(HttpCaseWithWebsiteUser):
|
||||
|
||||
# registry_test_mode = False # uncomment to save the product to test in browser
|
||||
|
||||
|
|
@ -25,81 +33,68 @@ class TestWebsiteSaleImage(odoo.tests.HttpCase):
|
|||
color_blue = '#4169E1'
|
||||
name_blue = 'Royal Blue'
|
||||
|
||||
self.env['product.pricelist'].sudo().search([]).action_archive()
|
||||
|
||||
# create the color attribute
|
||||
product_attribute = self.env['product.attribute'].create({
|
||||
'name': 'Beautiful Color',
|
||||
'display_type': 'color',
|
||||
'value_ids': [
|
||||
Command.create({
|
||||
'name': name_red,
|
||||
'html_color': color_red,
|
||||
'sequence': 1,
|
||||
}),
|
||||
Command.create({
|
||||
'name': name_green,
|
||||
'html_color': color_green,
|
||||
'sequence': 2,
|
||||
}),
|
||||
Command.create({
|
||||
'name': name_blue,
|
||||
'html_color': color_blue,
|
||||
'sequence': 3,
|
||||
}),
|
||||
]
|
||||
})
|
||||
|
||||
# create the color attribute values
|
||||
attr_values = self.env['product.attribute.value'].create([{
|
||||
'name': name_red,
|
||||
'attribute_id': product_attribute.id,
|
||||
'html_color': color_red,
|
||||
'sequence': 1,
|
||||
}, {
|
||||
'name': name_green,
|
||||
'attribute_id': product_attribute.id,
|
||||
'html_color': color_green,
|
||||
'sequence': 2,
|
||||
}, {
|
||||
'name': name_blue,
|
||||
'attribute_id': product_attribute.id,
|
||||
'html_color': color_blue,
|
||||
'sequence': 3,
|
||||
}])
|
||||
|
||||
# first image (blue) for the template
|
||||
f = io.BytesIO()
|
||||
Image.new('RGB', (1920, 1080), color_blue).save(f, 'JPEG')
|
||||
f.seek(0)
|
||||
blue_image = base64.b64encode(f.read())
|
||||
blue_image = _create_image(color=color_blue)
|
||||
|
||||
# second image (red) for the variant 1, small image (no zoom)
|
||||
f = io.BytesIO()
|
||||
Image.new('RGB', (800, 500), color_red).save(f, 'JPEG')
|
||||
f.seek(0)
|
||||
red_image = base64.b64encode(f.read())
|
||||
red_image = _create_image(color=color_red, dims=(800, 500))
|
||||
|
||||
# second image (green) for the variant 2, big image (zoom)
|
||||
f = io.BytesIO()
|
||||
Image.new('RGB', (1920, 1080), color_green).save(f, 'JPEG')
|
||||
f.seek(0)
|
||||
green_image = base64.b64encode(f.read())
|
||||
green_image = _create_image(color=color_green)
|
||||
|
||||
# Template Extra Image 1
|
||||
f = io.BytesIO()
|
||||
Image.new('RGB', (124, 147)).save(f, 'GIF')
|
||||
f.seek(0)
|
||||
image_gif = base64.b64encode(f.read())
|
||||
image_gif = _create_image(dims=(124, 147), format='GIF')
|
||||
|
||||
# Template Extra Image 2
|
||||
image_svg = base64.b64encode(b'<svg></svg>')
|
||||
|
||||
# Red Variant Extra Image 1
|
||||
f = io.BytesIO()
|
||||
Image.new('RGB', (767, 247)).save(f, 'BMP')
|
||||
f.seek(0)
|
||||
image_bmp = base64.b64encode(f.read())
|
||||
image_bmp = _create_image(dims=(767, 247), format='BMP')
|
||||
|
||||
# Green Variant Extra Image 1
|
||||
f = io.BytesIO()
|
||||
Image.new('RGB', (2147, 3251)).save(f, 'PNG')
|
||||
f.seek(0)
|
||||
image_png = base64.b64encode(f.read())
|
||||
image_png = _create_image(dims=(2147, 3251), format='PNG')
|
||||
|
||||
# create the template, without creating the variants
|
||||
template = self.env['product.template'].with_context(create_product_product=True).create({
|
||||
template = self.env['product.template'].create({
|
||||
'name': 'A Colorful Image',
|
||||
'product_template_image_ids': [(0, 0, {'name': 'image 1', 'image_1920': image_gif}), (0, 0, {'name': 'image 4', 'image_1920': image_svg})],
|
||||
'product_template_image_ids': [
|
||||
Command.create({'name': 'image 1', 'image_1920': image_gif}),
|
||||
Command.create({'name': 'image 4', 'image_1920': image_svg}),
|
||||
],
|
||||
'attribute_line_ids': [
|
||||
Command.create({
|
||||
'attribute_id': product_attribute.id,
|
||||
'value_ids': [Command.set(product_attribute.value_ids.ids)],
|
||||
})
|
||||
]
|
||||
})
|
||||
|
||||
# set the color attribute and values on the template
|
||||
line = self.env['product.template.attribute.line'].create([{
|
||||
'attribute_id': product_attribute.id,
|
||||
'product_tmpl_id': template.id,
|
||||
'value_ids': [(6, 0, attr_values.ids)]
|
||||
}])
|
||||
line = template.attribute_line_ids
|
||||
value_red = line.product_template_value_ids[0]
|
||||
value_green = line.product_template_value_ids[1]
|
||||
|
||||
|
|
@ -205,32 +200,15 @@ class TestWebsiteSaleImage(odoo.tests.HttpCase):
|
|||
# self.env.cr.commit() # uncomment to save the product to test in browser
|
||||
|
||||
# Make sure we have zoom on click
|
||||
self.env['ir.ui.view'].with_context(active_test=False).search(
|
||||
[('key', 'in', ('website_sale.product_picture_magnify_hover', 'website_sale.product_picture_magnify_click', 'website_sale.product_picture_magnify_both'))]
|
||||
).write({'active': False})
|
||||
self.env['ir.ui.view'].with_context(active_test=False).search(
|
||||
[('key', '=', 'website_sale.product_picture_magnify_click')]
|
||||
).write({'active': True})
|
||||
|
||||
# Ensure that only one pricelist is available during the test, with the company currency.
|
||||
# Ensure that no pricelist is available during the test.
|
||||
# This ensures that tours with triggers on the amounts will run properly.
|
||||
# To this purpose, we will ensure that only the public_pricelist is available for the default_website.
|
||||
public_pricelist = self.env.ref('product.list0')
|
||||
default_website = self.env.ref('website.default_website')
|
||||
website_2 = self.env.ref('website.website2', raise_if_not_found=False)
|
||||
if not website_2:
|
||||
website_2 = self.env['website'].create({
|
||||
'name': 'My Website 2',
|
||||
'domain': '',
|
||||
'sequence': 20,
|
||||
})
|
||||
self.env['product.pricelist'].search([
|
||||
('id', '!=', public_pricelist.id),
|
||||
('website_id', 'in', [False, default_website.id])]
|
||||
).website_id = website_2
|
||||
public_pricelist.currency_id = self.env.company.currency_id
|
||||
self.env['product.pricelist'].search([]).action_archive()
|
||||
|
||||
self.start_tour("/", 'shop_zoom', login="admin")
|
||||
self.start_tour("/", 'shop_zoom', login="website_user")
|
||||
|
||||
# CASE: unlink move image to fallback if fallback image empty
|
||||
template.image_1920 = False
|
||||
|
|
@ -286,34 +264,30 @@ class TestWebsiteSaleImage(odoo.tests.HttpCase):
|
|||
self.assertEqual(variant_image.product_variant_id.id, product.id)
|
||||
|
||||
def test_02_image_holder(self):
|
||||
f = io.BytesIO()
|
||||
Image.new('RGB', (800, 500), '#FF0000').save(f, 'JPEG')
|
||||
f.seek(0)
|
||||
image = base64.b64encode(f.read())
|
||||
image = _create_image(color='#FF0000', dims=(800, 500))
|
||||
|
||||
# create the color attribute
|
||||
product_attribute = self.env['product.attribute'].create({
|
||||
'name': 'Beautiful Color',
|
||||
'display_type': 'color',
|
||||
'value_ids': [
|
||||
Command.create({
|
||||
'name': 'Red',
|
||||
'sequence': 1,
|
||||
}),
|
||||
Command.create({
|
||||
'name': 'Green',
|
||||
'sequence': 2,
|
||||
}),
|
||||
Command.create({
|
||||
'name': 'Blue',
|
||||
'sequence': 3,
|
||||
}),
|
||||
]
|
||||
})
|
||||
|
||||
# create the color attribute values
|
||||
attr_values = self.env['product.attribute.value'].create([{
|
||||
'name': 'Red',
|
||||
'attribute_id': product_attribute.id,
|
||||
'sequence': 1,
|
||||
}, {
|
||||
'name': 'Green',
|
||||
'attribute_id': product_attribute.id,
|
||||
'sequence': 2,
|
||||
}, {
|
||||
'name': 'Blue',
|
||||
'attribute_id': product_attribute.id,
|
||||
'sequence': 3,
|
||||
}])
|
||||
|
||||
# create the template, without creating the variants
|
||||
template = self.env['product.template'].with_context(create_product_product=True).create({
|
||||
template = self.env['product.template'].with_context(create_product_product=False).create({
|
||||
'name': 'Test subject',
|
||||
})
|
||||
|
||||
|
|
@ -324,7 +298,7 @@ class TestWebsiteSaleImage(odoo.tests.HttpCase):
|
|||
line = self.env['product.template.attribute.line'].create([{
|
||||
'attribute_id': product_attribute.id,
|
||||
'product_tmpl_id': template.id,
|
||||
'value_ids': [(6, 0, attr_values.ids)]
|
||||
'value_ids': [Command.set(product_attribute.value_ids.ids)]
|
||||
}])
|
||||
value_red = line.product_template_value_ids[0]
|
||||
product_red = template._get_variant_for_combination(value_red)
|
||||
|
|
@ -337,7 +311,7 @@ class TestWebsiteSaleImage(odoo.tests.HttpCase):
|
|||
# when there are no template image but there are variants, the image must be obtained from the first variant
|
||||
self.assertEqual(product_red, template._get_image_holder())
|
||||
|
||||
product_red.toggle_active()
|
||||
product_red.action_archive()
|
||||
|
||||
# but when some variants are not available, the image must be obtained from the first available variant
|
||||
self.assertEqual(product_green, template._get_image_holder())
|
||||
|
|
@ -347,73 +321,64 @@ class TestWebsiteSaleImage(odoo.tests.HttpCase):
|
|||
# when there is a template image, the image must be obtained from the template
|
||||
self.assertEqual(template, template._get_image_holder())
|
||||
|
||||
@odoo.tests.common.tagged('post_install', '-at_install')
|
||||
class TestEnvironmentWebsiteSaleImage(odoo.tests.HttpCase):
|
||||
def setUp(self):
|
||||
super(TestEnvironmentWebsiteSaleImage, self).setUp()
|
||||
# Attachment needed for the replacement of images
|
||||
IrAttachment = self.env['ir.attachment']
|
||||
base = "http://%s:%s" % (HOST, config['http_port'])
|
||||
IrAttachment.create({
|
||||
'public': True,
|
||||
'name': 's_default_image.jpg',
|
||||
'type': 'url',
|
||||
'url': base + '/web/image/website.s_banner_default_image.jpg',
|
||||
})
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestWebsiteSaleRemoveImage(HttpCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
# First image (blue) for the template.
|
||||
color_blue = '#4169E1'
|
||||
name_blue = 'Royal Blue'
|
||||
# Red for the variant.
|
||||
color_red = '#CD5C5C'
|
||||
name_red = 'Indian Red'
|
||||
# Green for the replacement
|
||||
color_green = '#228B22'
|
||||
|
||||
# Attachment needed for the replacement of images
|
||||
cls.env['ir.attachment'].create({
|
||||
'public': True,
|
||||
'name': 'green.jpg',
|
||||
'type': 'binary',
|
||||
'datas': _create_image(color=color_green)
|
||||
})
|
||||
|
||||
# Create the color attribute.
|
||||
self.product_attribute = self.env['product.attribute'].create({
|
||||
cls.product_attribute = cls.env['product.attribute'].create({
|
||||
'name': 'Beautiful Color',
|
||||
'display_type': 'color',
|
||||
})
|
||||
|
||||
# create the color attribute values
|
||||
self.attr_values = self.env['product.attribute.value'].create([{
|
||||
cls.attr_values = cls.env['product.attribute.value'].create([{
|
||||
'name': name_blue,
|
||||
'attribute_id': self.product_attribute.id,
|
||||
'attribute_id': cls.product_attribute.id,
|
||||
'html_color': color_blue,
|
||||
'sequence': 1,
|
||||
}, {
|
||||
'name': name_red,
|
||||
'attribute_id': self.product_attribute.id,
|
||||
'attribute_id': cls.product_attribute.id,
|
||||
'html_color': color_red,
|
||||
'sequence': 2,
|
||||
},
|
||||
])
|
||||
f = io.BytesIO()
|
||||
Image.new('RGB', (1920, 1080), color_blue).save(f, 'JPEG')
|
||||
f.seek(0)
|
||||
blue_image = base64.b64encode(f.read())
|
||||
|
||||
self.template = self.env['product.template'].with_context(create_product_product=True).create({
|
||||
cls.template = cls.env['product.template'].with_context(create_product_product=False).create({
|
||||
'name': 'Test Remove Image',
|
||||
'image_1920': blue_image,
|
||||
'image_1920': _create_image(color=color_blue),
|
||||
})
|
||||
|
||||
@odoo.tests.common.tagged('post_install', '-at_install')
|
||||
class TestRemoveWebsiteSaleImageNoVariant(TestEnvironmentWebsiteSaleImage):
|
||||
def setUp(self):
|
||||
super(TestRemoveWebsiteSaleImageNoVariant, self).setUp()
|
||||
def test_website_sale_add_and_remove_main_product_image_no_variant(self):
|
||||
self.product = self.env['product.product'].create({
|
||||
'product_tmpl_id': self.template.id,
|
||||
})
|
||||
|
||||
def test_website_sale_add_and_remove_main_product_image_no_variant(self):
|
||||
self.start_tour(self.env['website'].get_client_action_url('/'), 'add_and_remove_main_product_image_no_variant', login='admin')
|
||||
self.assertFalse(self.template.image_1920)
|
||||
self.assertFalse(self.product.image_1920)
|
||||
|
||||
@odoo.tests.common.tagged('post_install', '-at_install')
|
||||
class TestRemoveWebsiteSaleImageVariants(TestEnvironmentWebsiteSaleImage):
|
||||
def setUp(self):
|
||||
super(TestRemoveWebsiteSaleImageVariants, self).setUp()
|
||||
def test_website_sale_remove_main_product_image_with_variant(self):
|
||||
# Set the color attribute and values on the template.
|
||||
self.env['product.template.attribute.line'].create([{
|
||||
'attribute_id': self.product_attribute.id,
|
||||
|
|
@ -423,7 +388,6 @@ class TestRemoveWebsiteSaleImageVariants(TestEnvironmentWebsiteSaleImage):
|
|||
self.product = self.env['product.product'].create({
|
||||
'product_tmpl_id': self.template.id,
|
||||
})
|
||||
def test_website_sale_remove_main_product_image_with_variant(self):
|
||||
self.start_tour(self.env['website'].get_client_action_url('/'), 'remove_main_product_image_with_variant', login='admin')
|
||||
self.assertFalse(self.template.image_1920)
|
||||
self.assertFalse(self.product.image_1920)
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ class TestWebsiteSaleInvoice(AccountPaymentCommon, SaleCommon):
|
|||
self.amount = self.sale_order.amount_total
|
||||
tx = self._create_transaction(flow='redirect', sale_order_ids=[self.sale_order.id], state='done')
|
||||
with mute_logger('odoo.addons.sale.models.payment_transaction'):
|
||||
tx._reconcile_after_done()
|
||||
tx._post_process()
|
||||
|
||||
self.assertEqual(self.sale_order.website_id.id, self.website.id)
|
||||
self.assertEqual(self.sale_order.invoice_ids.website_id.id, self.website.id)
|
||||
|
|
|
|||
|
|
@ -1,22 +1,29 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
import odoo
|
||||
from odoo import fields
|
||||
|
||||
from odoo import SUPERUSER_ID, fields
|
||||
from odoo.tests import tagged
|
||||
from odoo.tests.common import HttpCase
|
||||
|
||||
from odoo.addons.mail.tests.common import MailCommon
|
||||
from odoo.addons.base.tests.common import HttpCaseWithUserPortal
|
||||
from odoo.addons.website_sale.tests.common import WebsiteSaleCommon
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestWebsiteSaleMail(HttpCase):
|
||||
@tagged('post_install', '-at_install', 'mail_thread')
|
||||
class TestWebsiteSaleMail(HttpCaseWithUserPortal):
|
||||
|
||||
def test_01_shop_mail_tour(self):
|
||||
"""The goal of this test is to make sure sending SO by email works."""
|
||||
self.env.ref('base.user_admin').write({
|
||||
'email': 'mitchell.admin@example.com',
|
||||
})
|
||||
self.env['product.product'].create({
|
||||
'name': 'Acoustic Bloc Screens',
|
||||
'list_price': 2950.0,
|
||||
'sale_ok': True,
|
||||
'website_published': True,
|
||||
})
|
||||
self.env['res.partner'].create({
|
||||
|
|
@ -42,7 +49,85 @@ class TestWebsiteSaleMail(HttpCase):
|
|||
self.start_tour("/", 'shop_mail', login="admin")
|
||||
new_mail = self.env['mail.mail'].search([('create_date', '>=', start_time),
|
||||
('body_html', 'ilike', 'https://my-test-domain.com')],
|
||||
order='create_date DESC', limit=1)
|
||||
order='create_date DESC', limit=None)
|
||||
self.assertTrue(new_mail)
|
||||
self.assertIn('Your', new_mail.body_html)
|
||||
self.assertIn('order', new_mail.body_html)
|
||||
|
||||
def test_shop_product_mail_action_redirection(self):
|
||||
product_template = self.env['product.template'].create({
|
||||
'name': "test product template",
|
||||
'sale_ok': True,
|
||||
'website_published': True,
|
||||
})
|
||||
url = f'/mail/view?model=product.template&res_id={product_template.id}'
|
||||
shop_url = f'/shop/test-product-template-{product_template.id}'
|
||||
|
||||
with self.subTest(user='admin'):
|
||||
self.authenticate('admin', 'admin')
|
||||
res = self.url_open(url)
|
||||
self.assertEqual(res.status_code, 200)
|
||||
self.assertTrue(res.request.path_url.startswith('/odoo/product.template'))
|
||||
|
||||
with self.subTest(user='portal'):
|
||||
self.authenticate('portal', 'portal')
|
||||
res = self.url_open(url)
|
||||
self.assertEqual(res.status_code, 200)
|
||||
self.assertEqual(res.request.path_url, shop_url)
|
||||
|
||||
with self.subTest(user=None):
|
||||
self.authenticate(None, None)
|
||||
res = self.url_open(url)
|
||||
self.assertEqual(res.status_code, 200)
|
||||
self.assertEqual(res.request.path_url, shop_url)
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install', 'mail_thread')
|
||||
class TestWebsiteSaleMails(MailCommon, WebsiteSaleCommon):
|
||||
|
||||
def test_salesman_assignation(self):
|
||||
self.website.salesperson_id = self.user_admin
|
||||
MailThread = odoo.addons.mail.models.mail_thread.MailThread
|
||||
base_method = MailThread._message_create
|
||||
superuser = self.env['res.users'].browse(SUPERUSER_ID)
|
||||
|
||||
# Public user
|
||||
with patch.object(
|
||||
MailThread, '_message_create', autospec=True, side_effect=base_method
|
||||
) as patcher:
|
||||
self.cart.with_user(self.public_user).with_context(tracking_disable=False, mail_no_track=False).sudo().action_confirm()
|
||||
patcher.assert_called()
|
||||
|
||||
order, msg_values = None, {}
|
||||
for (record, call_args), _whatever in patcher.call_args_list:
|
||||
if record._name == 'sale.order':
|
||||
order = record
|
||||
msg_values = call_args[0]
|
||||
break
|
||||
|
||||
self.assertEqual(order, self.cart)
|
||||
self.assertEqual(msg_values['author_id'], superuser.partner_id.id)
|
||||
self.assertEqual(msg_values['partner_ids'], self.partner_admin.ids)
|
||||
self.assertEqual(msg_values['subject'], f"You have been assigned to {order.name}")
|
||||
|
||||
# Portal user
|
||||
user_portal = self._create_portal_user()
|
||||
portal_partner = user_portal.partner_id
|
||||
portal_user_cart = self.cart.copy({'partner_id': portal_partner.id, 'user_id': False})
|
||||
with patch.object(
|
||||
MailThread, '_message_create', autospec=True, side_effect=base_method
|
||||
) as patcher:
|
||||
portal_user_cart.with_user(user_portal).with_context(tracking_disable=False, mail_no_track=False).sudo().action_confirm()
|
||||
patcher.assert_called()
|
||||
|
||||
order, msg_values = None, {}
|
||||
for (record, call_args), _whatever in patcher.call_args_list:
|
||||
if record._name == 'sale.order':
|
||||
order = record
|
||||
msg_values = call_args[0]
|
||||
break
|
||||
|
||||
self.assertEqual(order, portal_user_cart)
|
||||
self.assertEqual(msg_values['author_id'], superuser.partner_id.id)
|
||||
self.assertEqual(msg_values['partner_ids'], self.partner_admin.ids)
|
||||
self.assertEqual(msg_values['subject'], f"You have been assigned to {order.name}")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
from odoo.addons.sale.tests.common import SaleCommon
|
||||
from odoo.tests import tagged
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestWebsiteSaleOrderEmailTemplate(SaleCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.website = cls.env['website'].create({
|
||||
'name': 'Test Website for Email Template',
|
||||
})
|
||||
cls.sale_order.website_id = cls.website.id
|
||||
|
||||
def test_website_specific_confirmation_template_is_used(self):
|
||||
"""Ensure _get_confirmation_template returns the website-specific template when set."""
|
||||
template = self.env['mail.template'].create({
|
||||
'name': "Website Custom Confirmation Template",
|
||||
'model_id': self.env.ref('sale.model_sale_order').id,
|
||||
'subject': "Website Confirmation",
|
||||
'body_html': '<p>Hello</p>',
|
||||
})
|
||||
self.website.confirmation_email_template_id = template
|
||||
with patch.object(self.env.registry['sale.order'], '_send_order_notification_mail') as mock:
|
||||
self.sale_order._send_order_confirmation_mail()
|
||||
mock.assert_called_once()
|
||||
returned_confirmation_template = mock.call_args[0][0]
|
||||
self.assertEqual(returned_confirmation_template, template)
|
||||
|
|
@ -1,19 +1,16 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import logging
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from freezegun import freeze_time
|
||||
from unittest.mock import patch
|
||||
|
||||
from freezegun import freeze_time
|
||||
|
||||
from odoo.fields import Command
|
||||
from odoo.tests import tagged, TransactionCase
|
||||
from odoo.tests import tagged
|
||||
from odoo.tools import SQL
|
||||
|
||||
from odoo.addons.base.tests.common import TransactionCaseWithUserDemo, HttpCaseWithUserPortal
|
||||
from odoo.addons.website.tools import MockRequest
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
from odoo.addons.base.tests.common import HttpCaseWithUserPortal, TransactionCaseWithUserDemo
|
||||
from odoo.addons.website_sale.tests.common import MockRequest, WebsiteSaleCommon
|
||||
|
||||
r''' /!\/!\
|
||||
Calling `get_pricelist_available` after setting `property_product_pricelist` on
|
||||
|
|
@ -35,80 +32,85 @@ Try to keep one call to `get_pricelist_available` by test method.
|
|||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestWebsitePriceList(TransactionCase):
|
||||
class TestWebsitePriceList(WebsiteSaleCommon):
|
||||
|
||||
def setUp(self):
|
||||
super(TestWebsitePriceList, self).setUp()
|
||||
self.env.user.partner_id.country_id = False # Remove country to avoid property pricelist computed.
|
||||
self.website = self.env.ref('website.default_website')
|
||||
self.website.user_id = self.env.user
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.env.user.partner_id.country_id = False # Remove country to avoid property pricelist computed.
|
||||
|
||||
(self.env['product.pricelist'].search([]) - self.env.ref('product.list0')).write({'website_id': False, 'active': False})
|
||||
self.benelux = self.env['res.country.group'].create({
|
||||
cls.pricelist.name = "Public Pricelist" # reduce diff in existing tests
|
||||
cls.country_be = cls.env.ref('base.be')
|
||||
cls.benelux = cls.env['res.country.group'].create({
|
||||
'name': 'BeNeLux',
|
||||
'country_ids': [(6, 0, (self.env.ref('base.be') + self.env.ref('base.lu') + self.env.ref('base.nl')).ids)]
|
||||
'country_ids': [Command.set((cls.country_be + cls.env.ref('base.lu') + cls.env.ref('base.nl')).ids)]
|
||||
})
|
||||
self.list_benelux = self.env['product.pricelist'].create({
|
||||
cls.curr_eur = cls._enable_currency('EUR')
|
||||
cls.list_benelux = cls.env['product.pricelist'].create({
|
||||
'name': 'Benelux',
|
||||
'selectable': True,
|
||||
'website_id': self.website.id,
|
||||
'country_group_ids': [(4, self.benelux.id)],
|
||||
'website_id': cls.website.id,
|
||||
'country_group_ids': [Command.link(cls.benelux.id)],
|
||||
'currency_id': cls.curr_eur.id,
|
||||
'sequence': 2,
|
||||
})
|
||||
item_benelux = self.env['product.pricelist.item'].create({
|
||||
'pricelist_id': self.list_benelux.id,
|
||||
'compute_price': 'percentage',
|
||||
'base': 'list_price',
|
||||
'percent_price': 10,
|
||||
'currency_id': self.env.ref('base.EUR').id,
|
||||
'item_ids': [
|
||||
Command.create({
|
||||
'compute_price': 'percentage',
|
||||
'base': 'list_price',
|
||||
'percent_price': 10,
|
||||
}),
|
||||
]
|
||||
})
|
||||
|
||||
|
||||
self.list_christmas = self.env['product.pricelist'].create({
|
||||
cls.europe = cls.env.ref('base.europe')
|
||||
cls.list_christmas = cls.env['product.pricelist'].create({
|
||||
'name': 'Christmas',
|
||||
'selectable': False,
|
||||
'website_id': self.website.id,
|
||||
'country_group_ids': [(4, self.env.ref('base.europe').id)],
|
||||
'website_id': cls.website.id,
|
||||
'country_group_ids': [Command.link(cls.europe.id)],
|
||||
'sequence': 20,
|
||||
})
|
||||
item_christmas = self.env['product.pricelist.item'].create({
|
||||
'pricelist_id': self.list_christmas.id,
|
||||
'compute_price': 'formula',
|
||||
'base': 'list_price',
|
||||
'price_discount': 20,
|
||||
'item_ids': [
|
||||
Command.create({
|
||||
'compute_price': 'formula',
|
||||
'base': 'list_price',
|
||||
'price_discount': 20,
|
||||
}),
|
||||
]
|
||||
})
|
||||
|
||||
list_europe = self.env['product.pricelist'].create({
|
||||
cls.list_europe = cls.env['product.pricelist'].create({
|
||||
'name': 'EUR',
|
||||
'selectable': True,
|
||||
'website_id': self.website.id,
|
||||
'country_group_ids': [(4, self.env.ref('base.europe').id)],
|
||||
'website_id': cls.website.id,
|
||||
'country_group_ids': [Command.link(cls.europe.id)],
|
||||
'sequence': 3,
|
||||
'currency_id': self.env.ref('base.EUR').id,
|
||||
'currency_id': cls.curr_eur.id,
|
||||
'item_ids': [
|
||||
Command.create({
|
||||
'compute_price': 'formula',
|
||||
'base': 'list_price',
|
||||
}),
|
||||
]
|
||||
})
|
||||
item_europe = self.env['product.pricelist.item'].create({
|
||||
'pricelist_id': list_europe.id,
|
||||
'compute_price': 'formula',
|
||||
'base': 'list_price',
|
||||
})
|
||||
self.env.ref('product.list0').website_id = self.website.id
|
||||
self.website.pricelist_id = self.ref('product.list0')
|
||||
|
||||
ca_group = self.env['res.country.group'].create({
|
||||
ca_group = cls.env['res.country.group'].create({
|
||||
'name': 'Canada',
|
||||
'country_ids': [(6, 0, [self.ref('base.ca')])]
|
||||
'country_ids': [Command.set([cls.env.ref('base.ca').id])]
|
||||
})
|
||||
self.env['product.pricelist'].create({
|
||||
cls.env['product.pricelist'].create({
|
||||
'name': 'Canada',
|
||||
'selectable': True,
|
||||
'website_id': self.website.id,
|
||||
'country_group_ids': [(6, 0, [ca_group.id])],
|
||||
'website_id': cls.website.id,
|
||||
'country_group_ids': [Command.set(ca_group.ids)],
|
||||
'sequence': 10
|
||||
})
|
||||
self.args = {
|
||||
cls.args = {
|
||||
'show': False,
|
||||
'current_pl': False,
|
||||
}
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
patcher = patch('odoo.addons.website_sale.models.website.Website.get_pricelist_available', wraps=self._get_pricelist_available)
|
||||
self.startPatcher(patcher)
|
||||
|
||||
|
|
@ -162,11 +164,12 @@ class TestWebsitePriceList(TransactionCase):
|
|||
def test_get_pricelist_available_promocode(self):
|
||||
christmas_pl = self.list_christmas.id
|
||||
|
||||
# Christmas Pricelist only available for EU countries
|
||||
country_list = {
|
||||
False: True,
|
||||
'BE': True,
|
||||
'IT': True,
|
||||
'US': True,
|
||||
'US': False,
|
||||
'CA': False
|
||||
}
|
||||
|
||||
|
|
@ -181,7 +184,7 @@ class TestWebsitePriceList(TransactionCase):
|
|||
|
||||
def test_get_pricelist_available_show_with_auto_property(self):
|
||||
show = True
|
||||
self.env.user.partner_id.country_id = self.env.ref('base.be') # Add EUR pricelist auto
|
||||
self.env.user.partner_id.country_id = self.country_be # Add EUR pricelist auto
|
||||
current_pl = False
|
||||
|
||||
country_list = {
|
||||
|
|
@ -197,16 +200,16 @@ class TestWebsitePriceList(TransactionCase):
|
|||
% (country, len(pls), pls.mapped('name'), len(result), result))
|
||||
|
||||
def test_pricelist_combination(self):
|
||||
# Enable discounts to view discount in sale_order
|
||||
self.env.user.group_ids += self.env.ref('sale.group_discount_per_so_line')
|
||||
|
||||
product = self.env['product.product'].create({
|
||||
'name': 'Super Product',
|
||||
'list_price': 100,
|
||||
'taxes_id': False,
|
||||
})
|
||||
current_website = self.env['website'].get_current_website()
|
||||
website_pricelist = current_website.get_current_pricelist()
|
||||
website_pricelist.write({
|
||||
'discount_policy': 'with_discount',
|
||||
'item_ids': [(5, 0, 0), (0, 0, {
|
||||
self.pricelist.write({
|
||||
'item_ids': [Command.clear(), Command.create({
|
||||
'applied_on': '1_product',
|
||||
'product_tmpl_id': product.product_tmpl_id.id,
|
||||
'min_quantity': 500,
|
||||
|
|
@ -216,34 +219,33 @@ class TestWebsitePriceList(TransactionCase):
|
|||
})
|
||||
promo_pricelist = self.env['product.pricelist'].create({
|
||||
'name': 'Super Pricelist',
|
||||
'discount_policy': 'without_discount',
|
||||
'item_ids': [(0, 0, {
|
||||
'item_ids': [Command.create({
|
||||
'applied_on': '1_product',
|
||||
'product_tmpl_id': product.product_tmpl_id.id,
|
||||
'base': 'pricelist',
|
||||
'base_pricelist_id': website_pricelist.id,
|
||||
'compute_price': 'formula',
|
||||
'price_discount': 25
|
||||
'base_pricelist_id': self.pricelist.id,
|
||||
'compute_price': 'percentage',
|
||||
'percent_price': 25,
|
||||
})]
|
||||
})
|
||||
so = self.env['sale.order'].create({
|
||||
'partner_id': self.env.user.partner_id.id,
|
||||
'order_line': [(0, 0, {
|
||||
'website_id': self.website.id,
|
||||
'pricelist_id': self.pricelist.id,
|
||||
'order_line': [Command.create({
|
||||
'name': product.name,
|
||||
'product_id': product.id,
|
||||
'product_uom_qty': 1,
|
||||
'product_uom': product.uom_id.id,
|
||||
'price_unit': product.list_price,
|
||||
'tax_id': False,
|
||||
})]
|
||||
'tax_ids': False,
|
||||
})],
|
||||
})
|
||||
sol = so.order_line
|
||||
self.assertEqual(sol.price_total, 100.0)
|
||||
so.pricelist_id = promo_pricelist
|
||||
with MockRequest(self.env, website=current_website, sale_order_id=so.id):
|
||||
so._cart_update(product_id=product.id, line_id=sol.id, set_qty=500)
|
||||
self.assertEqual(sol.price_unit, 37.0, 'Both reductions should be applied')
|
||||
self.assertEqual(sol.price_reduce, 27.75, 'Both reductions should be applied')
|
||||
so._cart_update_line_quantity(line_id=sol.id, quantity=500)
|
||||
self.assertEqual(sol.price_unit, 100.0, 'Both reductions should be applied')
|
||||
self.assertEqual(sol.discount, 72.25, 'Both reductions should be applied')
|
||||
self.assertEqual(sol.price_total, 13875)
|
||||
|
||||
def test_pricelist_with_no_list_price(self):
|
||||
|
|
@ -252,58 +254,37 @@ class TestWebsitePriceList(TransactionCase):
|
|||
'list_price': 0,
|
||||
'taxes_id': False,
|
||||
})
|
||||
current_website = self.env['website'].get_current_website()
|
||||
website_pricelist = current_website.get_current_pricelist()
|
||||
website_pricelist.write({
|
||||
'discount_policy': 'without_discount',
|
||||
'item_ids': [(5, 0, 0), (0, 0, {
|
||||
'applied_on': '1_product',
|
||||
'product_tmpl_id': product.product_tmpl_id.id,
|
||||
'min_quantity': 0,
|
||||
'compute_price': 'fixed',
|
||||
'fixed_price': 10,
|
||||
})]
|
||||
self.pricelist.write({
|
||||
'item_ids': [
|
||||
Command.clear(),
|
||||
Command.create({
|
||||
'applied_on': '1_product',
|
||||
'product_tmpl_id': product.product_tmpl_id.id,
|
||||
'min_quantity': 0,
|
||||
'compute_price': 'fixed',
|
||||
'fixed_price': 10,
|
||||
}),
|
||||
]
|
||||
})
|
||||
so = self.env['sale.order'].create({
|
||||
'partner_id': self.env.user.partner_id.id,
|
||||
'order_line': [(0, 0, {
|
||||
'website_id': self.website.id,
|
||||
'pricelist_id': self.pricelist.id,
|
||||
'order_line': [Command.create({
|
||||
'name': product.name,
|
||||
'product_id': product.id,
|
||||
'product_uom_qty': 5,
|
||||
'product_uom': product.uom_id.id,
|
||||
'price_unit': product.list_price,
|
||||
'tax_id': False,
|
||||
'tax_ids': False,
|
||||
})]
|
||||
})
|
||||
sol = so.order_line
|
||||
self.assertEqual(sol.price_total, 0)
|
||||
so.pricelist_id = website_pricelist
|
||||
with MockRequest(self.env, website=current_website, sale_order_id=so.id):
|
||||
so._cart_update(product_id=product.id, line_id=sol.id, set_qty=6)
|
||||
so._cart_update_line_quantity(line_id=sol.id, quantity=6)
|
||||
self.assertEqual(sol.price_unit, 10.0, 'Pricelist price should be applied')
|
||||
self.assertEqual(sol.price_reduce, 10.0, 'Pricelist price should be applied')
|
||||
self.assertEqual(sol.discount, 0, 'Pricelist price should be applied')
|
||||
self.assertEqual(sol.price_total, 60.0)
|
||||
|
||||
def test_get_right_discount(self):
|
||||
""" Test that `_get_sales_prices` from `product_template`
|
||||
returns a dict with just `price_reduce` (no discount) as key
|
||||
when the product is tax included.
|
||||
"""
|
||||
self.env.company.country_id = self.env.ref('base.us')
|
||||
tax = self.env['account.tax'].create({
|
||||
'name': "Tax 10",
|
||||
'amount': 10,
|
||||
})
|
||||
|
||||
product = self.env['product.template'].create({
|
||||
'name': 'Event Product',
|
||||
'list_price': 10.0,
|
||||
'taxes_id': tax,
|
||||
})
|
||||
|
||||
prices = product._get_sales_prices(self.list_christmas)
|
||||
self.assertFalse('base_price' in prices[product.id])
|
||||
|
||||
def test_pricelist_item_based_on_cost_for_templates(self):
|
||||
""" Test that `_get_sales_prices` from `product_template` computes the correct price when
|
||||
the pricelist item is based on the cost of the product.
|
||||
|
|
@ -318,31 +299,78 @@ class TestWebsitePriceList(TransactionCase):
|
|||
})
|
||||
|
||||
pa = self.env['product.attribute'].create({'name': 'Attribute'})
|
||||
pav1 = self.env['product.attribute.value'].create({'name': 'Value1', 'attribute_id': pa.id})
|
||||
pav2 = self.env['product.attribute.value'].create({'name': 'Value2', 'attribute_id': pa.id})
|
||||
pav1, pav2 = self.env['product.attribute.value'].create([
|
||||
{'name': 'Value1', 'attribute_id': pa.id},
|
||||
{'name': 'Value2', 'attribute_id': pa.id},
|
||||
])
|
||||
|
||||
product_template = self.env['product.template'].create({
|
||||
'name': 'Product Template', 'list_price': 10.0, 'standard_price': 5.0
|
||||
})
|
||||
self.assertEqual(product_template.standard_price, 5)
|
||||
price = product_template._get_sales_prices(pricelist)[product_template.id]['price_reduce']
|
||||
msg = "Template has no variants, the price should be computed based on the template's cost."
|
||||
self.assertEqual(price, 4.5, msg)
|
||||
with MockRequest(
|
||||
self.env, website=self.website, website_sale_current_pl=pricelist.id
|
||||
) as request:
|
||||
self.assertEqual(request.pricelist, pricelist)
|
||||
price = product_template._get_sales_prices(self.website)[product_template.id]['price_reduce']
|
||||
msg = "Template has no variants, the price should be computed based on the template's cost."
|
||||
self.assertEqual(price, 4.5, msg)
|
||||
|
||||
product_template.attribute_line_ids = [Command.create({
|
||||
'attribute_id': pa.id, 'value_ids': [Command.set([pav1.id, pav2.id])]
|
||||
})]
|
||||
msg = "Product template with variants should have no cost."
|
||||
self.assertEqual(product_template.standard_price, 0, msg)
|
||||
self.assertEqual(product_template.product_variant_ids[0].standard_price, 0)
|
||||
product_template.attribute_line_ids = [Command.create({
|
||||
'attribute_id': pa.id, 'value_ids': [Command.set([pav1.id, pav2.id])]
|
||||
})]
|
||||
msg = "Product template with variants should have no cost."
|
||||
self.assertEqual(product_template.standard_price, 0, msg)
|
||||
self.assertEqual(product_template.product_variant_ids[0].standard_price, 0)
|
||||
|
||||
price = product_template._get_sales_prices(pricelist)[product_template.id]['price_reduce']
|
||||
msg = "Template has variants, the price should be computed based on the 1st variant's cost."
|
||||
self.assertEqual(price, 0, msg)
|
||||
price = product_template._get_sales_prices(self.website)[product_template.id]['price_reduce']
|
||||
msg = "Template has variants, the price should be computed based on the 1st variant's cost."
|
||||
self.assertEqual(price, 0, msg)
|
||||
|
||||
product_template.product_variant_ids[0].standard_price = 20
|
||||
price = product_template._get_sales_prices(pricelist)[product_template.id]['price_reduce']
|
||||
self.assertEqual(price, 18, msg)
|
||||
product_template.product_variant_ids[0].standard_price = 20
|
||||
|
||||
price = product_template._get_sales_prices(self.website)[product_template.id]['price_reduce']
|
||||
self.assertEqual(price, 18, msg)
|
||||
|
||||
def test_base_price_with_discount_on_pricelist_tax_included(self):
|
||||
"""
|
||||
Tests that the base price of a product with tax included
|
||||
and discount from a price list is correctly displayed in the shop
|
||||
|
||||
ex: A product with a price of $61.98 ($75 tax incl. of 21%) and a discount of 20%
|
||||
should display the base price of $75
|
||||
"""
|
||||
self.env['res.config.settings'].create({ # Set Settings:
|
||||
'show_line_subtotals_tax_selection': 'tax_included', # Set "Tax Included" on the "Display Product Prices"
|
||||
'group_product_price_comparison': True, # price comparison
|
||||
}).execute()
|
||||
|
||||
product_tmpl = self.env['product.template'].create({
|
||||
'name': 'Test Product',
|
||||
'type': 'consu',
|
||||
'list_price': 61.98, # 75 tax incl.
|
||||
'taxes_id': [
|
||||
Command.create({
|
||||
'name': '21%',
|
||||
'type_tax_use': 'sale',
|
||||
'amount': 21,
|
||||
})
|
||||
],
|
||||
'is_published': True,
|
||||
})
|
||||
self.pricelist.write({
|
||||
'item_ids': [Command.create({
|
||||
'percent_price': 20,
|
||||
'compute_price': 'percentage',
|
||||
'product_tmpl_id': product_tmpl.id,
|
||||
})],
|
||||
})
|
||||
with MockRequest(
|
||||
self.website.env, website=self.website, website_sale_current_pl=self.pricelist.id
|
||||
) as request:
|
||||
self.assertEqual(request.pricelist, self.pricelist)
|
||||
res = product_tmpl._get_sales_prices(self.website)
|
||||
self.assertEqual(res[product_tmpl.id]['base_price'], 75)
|
||||
|
||||
def test_pricelist_item_validity_period(self):
|
||||
""" Test that if a cart was created before a validity period,
|
||||
|
|
@ -364,8 +392,6 @@ class TestWebsitePriceList(TransactionCase):
|
|||
'list_price': 100,
|
||||
'taxes_id': False,
|
||||
})
|
||||
current_website = self.env['website'].get_current_website()
|
||||
current_website.pricelist_id = pricelist
|
||||
with freeze_time(today) as frozen_time:
|
||||
so = self.env['sale.order'].create({
|
||||
'partner_id': self.env.user.partner_id.id,
|
||||
|
|
@ -374,21 +400,53 @@ class TestWebsitePriceList(TransactionCase):
|
|||
'name': product.name,
|
||||
'product_id': product.id,
|
||||
'product_uom_qty': 1,
|
||||
'product_uom': product.uom_id.id,
|
||||
'product_uom_id': product.uom_id.id,
|
||||
'price_unit': product.list_price,
|
||||
'tax_id': False,
|
||||
})],
|
||||
'website_id': current_website.id,
|
||||
'website_id': self.website.id,
|
||||
})
|
||||
sol = so.order_line
|
||||
self.assertEqual(sol.price_total, 100.0)
|
||||
|
||||
frozen_time.move_to(tomorrow + timedelta(seconds=10))
|
||||
with MockRequest(self.env, website=current_website, sale_order_id=so.id):
|
||||
so._cart_update(product_id=product.id, line_id=sol.id, set_qty=2)
|
||||
so._cart_update_line_quantity(line_id=sol.id, quantity=2)
|
||||
self.assertEqual(sol.price_unit, 80.0, 'Reduction should be applied')
|
||||
self.assertEqual(sol.price_total, 160)
|
||||
|
||||
def test_pricelist_anonymous_user(self):
|
||||
list_benelux_2 = self.list_benelux.sudo().copy({
|
||||
'name': 'Benelux 2',
|
||||
'item_ids': [
|
||||
Command.create({
|
||||
'compute_price': 'percentage',
|
||||
'base': 'list_price',
|
||||
'percent_price': 20,
|
||||
}),
|
||||
]
|
||||
})
|
||||
order_sudo = self.env['sale.order'].sudo().create({
|
||||
'partner_id': self.public_partner.id,
|
||||
'pricelist_id': list_benelux_2.id,
|
||||
'order_line': [Command.create({
|
||||
'name': self.product.name,
|
||||
'product_id': self.product.id,
|
||||
})],
|
||||
})
|
||||
# Creating partner with address of belgium
|
||||
partner = self.env['res.partner'].create({
|
||||
'name': 'Test Partner',
|
||||
'company_id': False,
|
||||
'country_id': self.env.ref('base.be').id,
|
||||
})
|
||||
website = self.website.with_user(self.public_user)
|
||||
with MockRequest(
|
||||
website.env, website=website,
|
||||
website_sale_current_pl=list_benelux_2.id,
|
||||
website_sale_selected_pl_id=list_benelux_2.id
|
||||
) as request:
|
||||
order_sudo._update_address({'partner_id': partner.id})
|
||||
self.assertEqual(order_sudo.pricelist_id, list_benelux_2)
|
||||
|
||||
def simulate_frontend_context(self, website_id=1):
|
||||
# Mock this method will be enough to simulate frontend context in most methods
|
||||
def get_request_website():
|
||||
|
|
@ -398,66 +456,68 @@ def simulate_frontend_context(self, website_id=1):
|
|||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestWebsitePriceListAvailable(TransactionCase):
|
||||
def setUp(self):
|
||||
super(TestWebsitePriceListAvailable, self).setUp()
|
||||
Pricelist = self.env['product.pricelist']
|
||||
Website = self.env['website']
|
||||
class TestWebsitePriceListAvailable(WebsiteSaleCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls._enable_pricelists()
|
||||
Pricelist = cls.env['product.pricelist']
|
||||
Website = cls.env['website']
|
||||
|
||||
# Set up 2 websites
|
||||
self.website = Website.browse(1)
|
||||
self.website2 = Website.create({'name': 'Website 2'})
|
||||
cls.website2 = Website.create({'name': 'Website 2'})
|
||||
|
||||
# Remove existing pricelists and create new ones
|
||||
existing_pricelists = Pricelist.search([])
|
||||
self.backend_pl = Pricelist.create({
|
||||
cls.backend_pl = Pricelist.create({
|
||||
'name': 'Backend Pricelist',
|
||||
'website_id': False,
|
||||
})
|
||||
self.generic_pl_select = Pricelist.create({
|
||||
cls.generic_pl_select = Pricelist.create({
|
||||
'name': 'Generic Selectable Pricelist',
|
||||
'selectable': True,
|
||||
'website_id': False,
|
||||
})
|
||||
self.generic_pl_code = Pricelist.create({
|
||||
cls.generic_pl_code = Pricelist.create({
|
||||
'name': 'Generic Code Pricelist',
|
||||
'code': 'GENERICCODE',
|
||||
'website_id': False,
|
||||
})
|
||||
self.generic_pl_code_select = Pricelist.create({
|
||||
cls.generic_pl_code_select = Pricelist.create({
|
||||
'name': 'Generic Code Selectable Pricelist',
|
||||
'code': 'GENERICCODESELECT',
|
||||
'selectable': True,
|
||||
'website_id': False,
|
||||
})
|
||||
self.w1_pl = Pricelist.create({
|
||||
cls.w1_pl = Pricelist.create({
|
||||
'name': 'Website 1 Pricelist',
|
||||
'website_id': self.website.id,
|
||||
'website_id': cls.website.id,
|
||||
})
|
||||
self.w1_pl_select = Pricelist.create({
|
||||
cls.w1_pl_select = Pricelist.create({
|
||||
'name': 'Website 1 Pricelist Selectable',
|
||||
'website_id': self.website.id,
|
||||
'website_id': cls.website.id,
|
||||
'selectable': True,
|
||||
})
|
||||
self.w1_pl_code_select = Pricelist.create({
|
||||
cls.w1_pl_code_select = Pricelist.create({
|
||||
'name': 'Website 1 Pricelist Code Selectable',
|
||||
'website_id': self.website.id,
|
||||
'website_id': cls.website.id,
|
||||
'code': 'W1CODESELECT',
|
||||
'selectable': True,
|
||||
})
|
||||
self.w1_pl_code = Pricelist.create({
|
||||
cls.w1_pl_code = Pricelist.create({
|
||||
'name': 'Website 1 Pricelist Code',
|
||||
'website_id': self.website.id,
|
||||
'website_id': cls.website.id,
|
||||
'code': 'W1CODE',
|
||||
})
|
||||
self.w2_pl = Pricelist.create({
|
||||
cls.w2_pl = Pricelist.create({
|
||||
'name': 'Website 2 Pricelist',
|
||||
'website_id': self.website2.id,
|
||||
'website_id': cls.website2.id,
|
||||
})
|
||||
existing_pricelists.write({'active': False})
|
||||
|
||||
self.website = self.env['website'].browse(1)
|
||||
existing_pricelists.action_archive()
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
simulate_frontend_context(self)
|
||||
|
||||
def test_get_pricelist_available(self):
|
||||
|
|
@ -478,7 +538,7 @@ class TestWebsitePriceListAvailable(TransactionCase):
|
|||
# Real case if for public user. His `property_product_pricelist` need to be set as it is passed
|
||||
# through `_get_pl_partner_order` as the `website_pl` when searching for available pricelists
|
||||
# for active users.
|
||||
public_partner = self.env.ref('base.public_partner')
|
||||
public_partner = self.public_partner
|
||||
self.assertFalse(public_partner.active, "Ensure public partner is inactive (purpose of this test)")
|
||||
pl = public_partner.property_product_pricelist
|
||||
self.assertEqual(len(pl), 1, "Inactive partner should still get a `property_product_pricelist`")
|
||||
|
|
@ -487,9 +547,36 @@ class TestWebsitePriceListAvailable(TransactionCase):
|
|||
@tagged('post_install', '-at_install')
|
||||
class TestWebsitePriceListAvailableGeoIP(TestWebsitePriceListAvailable):
|
||||
def setUp(self):
|
||||
super(TestWebsitePriceListAvailableGeoIP, self).setUp()
|
||||
super().setUp()
|
||||
# clean `property_product_pricelist` for partner for this test (clean setup)
|
||||
self.env['ir.property'].search([('res_id', '=', 'res.partner,%s' % self.env.user.partner_id.id)]).unlink()
|
||||
self.env.invalidate_all()
|
||||
for field in self.env.registry.many2one_company_dependents['res.partner']:
|
||||
self.env.cr.execute(SQL(
|
||||
"""
|
||||
UPDATE %(table)s
|
||||
SET %(field)s = (
|
||||
SELECT jsonb_object_agg(key, value)
|
||||
FROM jsonb_each(%(field)s)
|
||||
WHERE value != %(id_)s
|
||||
)
|
||||
WHERE %(field)s IS NOT NULL
|
||||
""",
|
||||
table=SQL.identifier(self.env[field.model_name]._table),
|
||||
field=SQL.identifier(field.name),
|
||||
id_=SQL('to_jsonb(%s::int)', self.env.user.partner_id.id),
|
||||
))
|
||||
if fields_ := self.env.registry.many2one_company_dependents['res.partner']:
|
||||
field_ids = [self.env['ir.model.fields']._get_ids(field.model_name).get(field.name) for field in fields_]
|
||||
self.env.cr.execute(SQL(
|
||||
"""
|
||||
DELETE FROM ir_default
|
||||
WHERE field_id IN %(field_ids)s
|
||||
AND json_value = %(id_text)s
|
||||
""",
|
||||
field_ids=tuple(field_ids),
|
||||
id_text=str(self.env.user.partner_id.id),
|
||||
))
|
||||
self.env.registry.clear_cache()
|
||||
|
||||
# set different country groups on pricelists
|
||||
c_EUR = self.env.ref('base.europe')
|
||||
|
|
@ -499,14 +586,17 @@ class TestWebsitePriceListAvailableGeoIP(TestWebsitePriceListAvailable):
|
|||
})
|
||||
|
||||
self.BE = self.env.ref('base.be')
|
||||
self.US = self.env.ref('base.us')
|
||||
NL = self.env.ref('base.nl')
|
||||
c_BE = self.env['res.country.group'].create({'name': 'Belgium', 'country_ids': [(6, 0, [self.BE.id])]})
|
||||
c_NL = self.env['res.country.group'].create({'name': 'Netherlands', 'country_ids': [(6, 0, [NL.id])]})
|
||||
c_BE, c_NL = self.env['res.country.group'].create([
|
||||
{'name': 'Belgium', 'country_ids': [(6, 0, [self.BE.id])]},
|
||||
{'name': 'Netherlands', 'country_ids': [(6, 0, [NL.id])]},
|
||||
])
|
||||
|
||||
(self.backend_pl + self.generic_pl_select + self.generic_pl_code + self.w1_pl_select).write({'country_group_ids': [(6, 0, [c_BE.id])]})
|
||||
(self.generic_pl_code_select + self.w1_pl + self.w2_pl).write({'country_group_ids': [(6, 0, [c_BENELUX.id])]})
|
||||
(self.w1_pl_code).write({'country_group_ids': [(6, 0, [c_EUR.id])]})
|
||||
(self.w1_pl_code_select).write({'country_group_ids': [(6, 0, [c_NL.id])]})
|
||||
(self.backend_pl + self.generic_pl_select + self.generic_pl_code + self.w1_pl_select).write({'country_group_ids': [Command.set(c_BE.ids)]})
|
||||
(self.generic_pl_code_select + self.w1_pl + self.w2_pl).write({'country_group_ids': [Command.set(c_BENELUX.ids)]})
|
||||
(self.w1_pl_code).write({'country_group_ids': [Command.set(c_EUR.ids)]})
|
||||
(self.w1_pl_code_select).write({'country_group_ids': [Command.set(c_NL.ids)]})
|
||||
|
||||
# pricelist | selectable | website | code | country group |
|
||||
# ----------------------------------------------------------------------|
|
||||
|
|
@ -524,7 +614,7 @@ class TestWebsitePriceListAvailableGeoIP(TestWebsitePriceListAvailable):
|
|||
self.website1_be_pl = self.generic_pl_select + self.generic_pl_code + self.w1_pl_select + self.generic_pl_code_select + self.w1_pl + self.w1_pl_code
|
||||
|
||||
def test_get_pricelist_available_geoip(self):
|
||||
# Test get all available pricelists with geoip and no partner pricelist (ir.property)
|
||||
# Test get all available pricelists with geoip and no partner pricelist
|
||||
|
||||
# property_product_pricelist will also be returned in the available pricelists
|
||||
self.website1_be_pl += self.env.user.partner_id.property_product_pricelist
|
||||
|
|
@ -534,14 +624,14 @@ class TestWebsitePriceListAvailableGeoIP(TestWebsitePriceListAvailable):
|
|||
self.assertEqual(pls, self.website1_be_pl, "Only pricelists for BE and accessible on website should be returned, and the partner pl")
|
||||
|
||||
def test_get_pricelist_available_geoip2(self):
|
||||
# Test get all available pricelists with geoip and a partner pricelist (ir.property) not website compliant
|
||||
# Test get all available pricelists with geoip and a partner pricelist not website compliant
|
||||
self.env.user.partner_id.property_product_pricelist = self.backend_pl
|
||||
with patch('odoo.addons.website_sale.models.website.Website._get_geoip_country_code', return_value=self.BE.code):
|
||||
pls = self.website.get_pricelist_available()
|
||||
self.assertEqual(pls, self.website1_be_pl, "Only pricelists for BE and accessible on website should be returned as partner pl is not website compliant")
|
||||
|
||||
def test_get_pricelist_available_geoip3(self):
|
||||
# Test get all available pricelists with geoip and a partner pricelist (ir.property) website compliant (but not geoip compliant)
|
||||
# Test get all available pricelists with geoip and a partner pricelist website compliant (but not geoip compliant)
|
||||
self.env.user.partner_id.property_product_pricelist = self.w1_pl_code_select
|
||||
with patch('odoo.addons.website_sale.models.website.Website._get_geoip_country_code', return_value=self.BE.code):
|
||||
pls = self.website.get_pricelist_available()
|
||||
|
|
@ -554,11 +644,45 @@ class TestWebsitePriceListAvailableGeoIP(TestWebsitePriceListAvailable):
|
|||
pls_to_return += self.env.user.partner_id.property_product_pricelist
|
||||
|
||||
current_pl = self.w1_pl_code
|
||||
with patch('odoo.addons.website_sale.models.website.Website._get_geoip_country_code', return_value=self.BE.code), \
|
||||
patch('odoo.addons.website_sale.models.website.Website._get_cached_pricelist_id', return_value=current_pl.id):
|
||||
with (
|
||||
patch('odoo.addons.website_sale.models.website.Website._get_geoip_country_code', return_value=self.BE.code),
|
||||
MockRequest(self.env, website=self.website, website_sale_current_pl=current_pl.id),
|
||||
):
|
||||
pls = self.website.get_pricelist_available(show_visible=True)
|
||||
self.assertEqual(pls, pls_to_return + current_pl, "Only pricelists for BE, accessible en website and selectable should be returned. It should also return the applied promo pl")
|
||||
|
||||
def test_get_pricelist_available_geoip5(self):
|
||||
# Test get all available pricelists with geoip for a country not existing in any pricelists
|
||||
|
||||
with patch('odoo.addons.website_sale.models.website.Website._get_geoip_country_code', return_value=self.US.code):
|
||||
pricelists = self.website.get_pricelist_available()
|
||||
self.assertFalse(pricelists, "Pricelists specific to NL and BE should not be returned for US.")
|
||||
|
||||
def test_get_pricelist_available_geoip6(self):
|
||||
"""Remove country group from certain pricelists, and check that pricelists
|
||||
with country group get prioritized when geoip is available."""
|
||||
exclude = self.backend_pl + self.generic_pl_code + self.w1_pl_select + self.w1_pl_code
|
||||
exclude.country_group_ids = False
|
||||
self.website1_be_pl -= exclude
|
||||
|
||||
with patch(
|
||||
'odoo.addons.website_sale.models.website.Website._get_geoip_country_code',
|
||||
return_value=self.BE.code,
|
||||
):
|
||||
pls = self.website.get_pricelist_available()
|
||||
|
||||
for pl in pls:
|
||||
self.assertIn(
|
||||
self.BE,
|
||||
pl.country_group_ids.country_ids,
|
||||
"Pricelists should have a country group that includes BE",
|
||||
)
|
||||
self.assertEqual(
|
||||
pls,
|
||||
self.website1_be_pl,
|
||||
"Only pricelists for BE and accessible on website should be returned",
|
||||
)
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestWebsitePriceListHttp(HttpCaseWithUserPortal):
|
||||
|
|
@ -595,7 +719,7 @@ class TestWebsitePriceListMultiCompany(TransactionCaseWithUserDemo):
|
|||
- Set website's company to company 2
|
||||
- Demo user will still be in company 1
|
||||
'''
|
||||
super(TestWebsitePriceListMultiCompany, self).setUp()
|
||||
super().setUp()
|
||||
|
||||
self.demo_user = self.user_demo
|
||||
|
||||
|
|
@ -607,9 +731,6 @@ class TestWebsitePriceListMultiCompany(TransactionCaseWithUserDemo):
|
|||
Website = self.env['website']
|
||||
self.website = self.env.ref('website.default_website')
|
||||
self.website.company_id = self.company2
|
||||
# Delete unused website, it will make PL manipulation easier, avoiding
|
||||
# UserError being thrown when a website wouldn't have any PL left.
|
||||
Website.search([('id', '!=', self.website.id)]).unlink()
|
||||
self.website2 = Website.create({
|
||||
'name': 'Website 2',
|
||||
'company_id': self.company1.id,
|
||||
|
|
@ -633,16 +754,11 @@ class TestWebsitePriceListMultiCompany(TransactionCaseWithUserDemo):
|
|||
# Ensure everything was done correctly
|
||||
self.assertEqual(self.demo_user.partner_id.with_company(self.company1.id).property_product_pricelist, self.c1_pl)
|
||||
self.assertEqual(self.demo_user.partner_id.with_company(self.company2.id).property_product_pricelist, self.c2_pl)
|
||||
irp1 = self.env['ir.property'].with_company(self.company1)._get("property_product_pricelist", "res.partner", self.demo_user.partner_id.id)
|
||||
irp2 = self.env['ir.property'].with_company(self.company2)._get("property_product_pricelist", "res.partner", self.demo_user.partner_id.id)
|
||||
self.assertEqual((irp1, irp2), (self.c1_pl, self.c2_pl), "Ensure there is an `ir.property` for demo partner for every company, and that the pricelist is the company specific one.")
|
||||
# ---------------------------------- IR.PROPERTY -------------------------------------
|
||||
# id | name | res_id | company_id | value_reference
|
||||
# ------------------------------------------------------------------------------------
|
||||
# 1 | 'property_product_pricelist' | | 1 | product.pricelist,1
|
||||
# 2 | 'property_product_pricelist' | | 2 | product.pricelist,2
|
||||
# 3 | 'property_product_pricelist' | res.partner,8 | 1 | product.pricelist,10
|
||||
# 4 | 'property_product_pricelist' | res.partner,8 | 2 | product.pricelist,11
|
||||
# property_product_pricelist has been cached
|
||||
field = self.env['res.partner']._fields['property_product_pricelist']
|
||||
cache_rp1 = self.env.cache.get(self.demo_user.partner_id.with_company(self.company1.id), field)
|
||||
cache_rp2 = self.env.cache.get(self.demo_user.partner_id.with_company(self.company2.id), field)
|
||||
self.assertEqual((cache_rp1, cache_rp2), (self.c1_pl.id, self.c2_pl.id), "Ensure the pricelist is the company specific one.")
|
||||
|
||||
def test_property_product_pricelist_multi_company(self):
|
||||
''' Test that the `property_product_pricelist` of `res.partner` is read
|
||||
|
|
@ -651,13 +767,13 @@ class TestWebsitePriceListMultiCompany(TransactionCaseWithUserDemo):
|
|||
is not the same as its user's company.
|
||||
|
||||
Here, as demo user (company1), we will visit website1 (company2).
|
||||
It should return the ir.property for demo user for company2 and not
|
||||
It should return the data for demo user for company2 and not
|
||||
for the company1 as we should get the website's company pricelist
|
||||
and not the demo user's current company pricelist.
|
||||
'''
|
||||
simulate_frontend_context(self, self.website.id)
|
||||
|
||||
# First check: It should return ir.property,4 as company_id is
|
||||
# First check: It should return c2_pl as company_id is
|
||||
# website.company_id and not env.user.company_id
|
||||
company_id = self.website.company_id.id
|
||||
partner = self.demo_user.partner_id.with_company(company_id)
|
||||
|
|
@ -689,12 +805,14 @@ class TestWebsitePriceListMultiCompany(TransactionCaseWithUserDemo):
|
|||
# self.c2_pl | self.website | self.company2 |
|
||||
# c2_pl2 | self.website | self.company2 |
|
||||
|
||||
self.demo_user.groups_id += self.env.ref('sales_team.group_sale_manager')
|
||||
self.demo_user.group_ids += self.env.ref('sales_team.group_sale_manager')
|
||||
|
||||
# The test is here: while having access only to self.company2 records,
|
||||
# archive should not raise an error
|
||||
self.demo_user.group_ids += self.env.ref('product.group_product_manager')
|
||||
self.c2_pl.with_user(self.demo_user).with_context(allowed_company_ids=self.company2.ids).write({'active': False})
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestWebsiteSaleSession(HttpCaseWithUserPortal):
|
||||
|
||||
|
|
@ -703,6 +821,9 @@ class TestWebsiteSaleSession(HttpCaseWithUserPortal):
|
|||
The objective is to verify that the pricelist
|
||||
changes correctly according to the user.
|
||||
"""
|
||||
self.env.user.write(
|
||||
{'group_ids': [Command.link(self.env.ref('product.group_product_pricelist').id)]}
|
||||
)
|
||||
website = self.env.ref('website.default_website')
|
||||
test_user = self.env['res.users'].create({
|
||||
'name': 'Toto',
|
||||
|
|
@ -723,4 +844,4 @@ class TestWebsiteSaleSession(HttpCaseWithUserPortal):
|
|||
'code': 'User_pricelist',
|
||||
})
|
||||
test_user.partner_id.property_product_pricelist = user_pricelist
|
||||
self.start_tour("/shop", 'website_sale_shop_pricelist_tour', login="")
|
||||
self.start_tour("/shop", 'website_sale.website_sale_shop_pricelist_tour', login="")
|
||||
|
|
|
|||
|
|
@ -1,144 +0,0 @@
|
|||
# coding: utf-8
|
||||
import itertools
|
||||
|
||||
from odoo.tests import tagged
|
||||
from odoo.addons.website.tools import MockRequest
|
||||
from odoo.addons.sale.tests.test_sale_product_attribute_value_config import TestSaleProductAttributeValueCommon
|
||||
from odoo.addons.website_sale.controllers.main import WebsiteSale
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class WebsiteSaleProductTests(TestSaleProductAttributeValueCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.WebsiteSaleController = WebsiteSale()
|
||||
cls.website = cls.env.ref('website.default_website')
|
||||
cls.website.company_id = cls.env.company
|
||||
|
||||
cls.tax_5 = cls.env['account.tax'].create({
|
||||
'name': '5% Tax',
|
||||
'amount_type': 'percent',
|
||||
'amount': 5,
|
||||
'price_include': False,
|
||||
'include_base_amount': False,
|
||||
'type_tax_use': 'sale',
|
||||
})
|
||||
cls.tax_10 = cls.env['account.tax'].create({
|
||||
'name': '10% Tax',
|
||||
'amount_type': 'percent',
|
||||
'amount': 10,
|
||||
'price_include': False,
|
||||
'include_base_amount': False,
|
||||
'type_tax_use': 'sale',
|
||||
})
|
||||
cls.tax_15 = cls.env['account.tax'].create({
|
||||
'name': '15% Tax',
|
||||
'amount_type': 'percent',
|
||||
'amount': 15,
|
||||
'price_include': False,
|
||||
'include_base_amount': False,
|
||||
'type_tax_use': 'sale',
|
||||
})
|
||||
cls.fiscal_country = cls.env['res.country'].create({
|
||||
'name': "Super Fiscal Position",
|
||||
'code': 'SFP',
|
||||
})
|
||||
cls.product = cls.env['product.product'].create({
|
||||
'name': 'Super Product',
|
||||
'list_price': 100.0,
|
||||
})
|
||||
|
||||
def test_website_sale_contextual_price(self):
|
||||
contextual_price = self.computer._get_contextual_price()
|
||||
self.assertEqual(0.0, contextual_price, "With no pricelist context, the contextual price should be 0.")
|
||||
|
||||
current_website = self.env['website'].get_current_website()
|
||||
pricelist = current_website.get_current_pricelist()
|
||||
|
||||
# make sure the pricelist has a 10% discount
|
||||
self.env['product.pricelist.item'].create({
|
||||
'price_discount': 10,
|
||||
'compute_price': 'formula',
|
||||
'pricelist_id': pricelist.id,
|
||||
})
|
||||
discount_rate = 0.9
|
||||
currency_ratio = 2
|
||||
pricelist.currency_id = self._setup_currency(currency_ratio)
|
||||
with MockRequest(self.env, website=self.website):
|
||||
contextual_price = self.computer._get_contextual_price()
|
||||
self.assertEqual(
|
||||
2000.0 * currency_ratio * discount_rate, contextual_price,
|
||||
"With a website pricelist context, the contextual price should be the one defined for the website's pricelist."
|
||||
)
|
||||
|
||||
def test_get_contextual_price_tax_selection(self):
|
||||
"""
|
||||
`_get_contextual_price_tax_selection` is used to display the price on the website (e.g. in the carousel).
|
||||
We test that the contextual price is correctly computed. That is, it is coherent with the price displayed on the product when in the cart.
|
||||
"""
|
||||
param_main_product_tax_included = [True, False]
|
||||
param_show_line_subtotals_tax_selection = ['tax_included', 'tax_excluded']
|
||||
param_extra_tax = [False, 'included', 'excluded']
|
||||
param_fpos = [False, 'to_tax_excluded']
|
||||
parameters = itertools.product(param_main_product_tax_included, param_show_line_subtotals_tax_selection, param_extra_tax, param_fpos)
|
||||
|
||||
for main_product_tax_included, show_line_subtotals_tax_selection, extra_tax, fpos in parameters:
|
||||
with self.subTest(main_product_tax_included=main_product_tax_included, show_line_subtotals_tax_selection=show_line_subtotals_tax_selection, extra_tax=extra_tax, fpos=fpos):
|
||||
# set "show_line_subtotals_tax_selection" parameter
|
||||
config = self.env['res.config.settings'].create({})
|
||||
config.show_line_subtotals_tax_selection = show_line_subtotals_tax_selection
|
||||
config.execute()
|
||||
|
||||
# set "main_product_tax_included" parameter
|
||||
if main_product_tax_included:
|
||||
self.tax_15.price_include = True
|
||||
self.tax_15.include_base_amount = True
|
||||
self.product.taxes_id = self.tax_15
|
||||
tax_ids = [self.tax_15.id]
|
||||
|
||||
# set "extra_tax" parameter
|
||||
if extra_tax:
|
||||
if extra_tax == 'included':
|
||||
self.tax_5.price_include = True
|
||||
self.tax_5.include_base_amount = True
|
||||
tax_ids.append(self.tax_5.id)
|
||||
|
||||
# set "fpos" parameter
|
||||
if fpos:
|
||||
if fpos == 'to_tax_included':
|
||||
self.tax_10.price_include = True
|
||||
self.tax_10.include_base_amount = True
|
||||
|
||||
fiscal_position = self.env['account.fiscal.position'].create({
|
||||
'name': 'Super Fiscal Position',
|
||||
'auto_apply': True,
|
||||
'country_id': self.fiscal_country.id,
|
||||
})
|
||||
self.env['account.fiscal.position.tax'].create({
|
||||
'position_id': fiscal_position.id,
|
||||
'tax_src_id': self.tax_15.id,
|
||||
'tax_dest_id': self.tax_10.id,
|
||||
})
|
||||
self.env.user.partner_id.country_id = self.fiscal_country
|
||||
|
||||
# define the website pricelist
|
||||
current_website = self.env['website'].get_current_website()
|
||||
pricelist = current_website.get_current_pricelist()
|
||||
pricelist.currency_id = self.product.currency_id
|
||||
self.env['product.pricelist.item'].create({
|
||||
'price_discount': 0,
|
||||
'compute_price': 'formula',
|
||||
'pricelist_id': pricelist.id,
|
||||
})
|
||||
|
||||
with MockRequest(self.env, website=self.website, website_sale_current_pl=pricelist.id):
|
||||
contextual_price = self.product._get_contextual_price_tax_selection()
|
||||
self.WebsiteSaleController.cart_update_json(product_id=self.product.id, add_qty=1)
|
||||
sale_order = self.website.sale_get_order()
|
||||
|
||||
if show_line_subtotals_tax_selection == 'tax_included':
|
||||
self.assertAlmostEqual(sale_order.amount_total, contextual_price)
|
||||
else:
|
||||
self.assertAlmostEqual(sale_order.amount_untaxed, contextual_price)
|
||||
|
|
@ -1,42 +1,46 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.sale.tests.test_sale_product_attribute_value_config import TestSaleProductAttributeValueCommon
|
||||
from odoo import Command
|
||||
from odoo.fields import Command
|
||||
from odoo.tests import tagged
|
||||
from odoo.addons.website.tools import MockRequest
|
||||
|
||||
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
|
||||
from odoo.addons.product.tests.test_product_attribute_value_config import (
|
||||
TestProductAttributeValueCommon,
|
||||
)
|
||||
from odoo.addons.website_sale.tests.common import MockRequest
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install', 'product_attribute')
|
||||
class TestWebsiteSaleProductAttributeValueConfig(TestSaleProductAttributeValueCommon):
|
||||
class TestWebsiteSaleProductAttributeValueConfig(AccountTestInvoicingCommon, TestProductAttributeValueCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls, chart_template_ref=None):
|
||||
super().setUpClass(chart_template_ref=chart_template_ref)
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
# Use the testing environment.
|
||||
cls.env['website'].get_current_website().company_id = cls.env.company
|
||||
cls.computer.company_id = cls.env.company
|
||||
cls.computer = cls.computer.with_env(cls.env)
|
||||
cls.other_currency = cls.setup_other_currency('GBP')
|
||||
|
||||
def test_get_combination_info(self):
|
||||
# Setup pricelist: make sure the pricelist has a 10% discount
|
||||
pricelist = self.env['product.pricelist'].create({
|
||||
'name': "test_get_combination_info",
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
'discount_policy': 'with_discount',
|
||||
'company_id': self.env.company.id,
|
||||
'item_ids': [Command.create({
|
||||
'price_discount': 10,
|
||||
'compute_price': 'formula',
|
||||
})],
|
||||
})
|
||||
|
||||
# Setup website.
|
||||
website = self.env['website'].create({
|
||||
'name': "Test website",
|
||||
'company_id': self.env.company.id,
|
||||
'user_id': self.env.user.id,
|
||||
'pricelist_ids': [Command.set(pricelist.ids)],
|
||||
})
|
||||
|
||||
# Setup pricelist: make sure the pricelist has a 10% discount
|
||||
self.env['product.pricelist'].search([]).action_archive()
|
||||
pricelist = self.env['product.pricelist'].create({
|
||||
'name': "test_get_combination_info",
|
||||
'currency_id': self.other_currency.id,
|
||||
'company_id': self.env.company.id,
|
||||
'item_ids': [Command.create({
|
||||
'percent_price': 10,
|
||||
'compute_price': 'percentage',
|
||||
})],
|
||||
'website_id': website.id,
|
||||
})
|
||||
|
||||
# Setup product with 15% tax.
|
||||
|
|
@ -51,42 +55,22 @@ class TestWebsiteSaleProductAttributeValueConfig(TestSaleProductAttributeValueCo
|
|||
currency_ratio = 2
|
||||
|
||||
# CASE: B2B setting (default)
|
||||
combination_info = product_template._get_combination_info(pricelist=pricelist)
|
||||
self.assertEqual(combination_info['price'], 2222 * discount_rate * currency_ratio)
|
||||
self.assertEqual(combination_info['list_price'], 2222 * discount_rate * currency_ratio)
|
||||
self.assertEqual(combination_info['price_extra'], 222 * currency_ratio)
|
||||
self.assertEqual(combination_info['has_discounted_price'], False)
|
||||
with MockRequest(product_template.env, website=website, website_sale_current_pl=pricelist.id):
|
||||
combination_info = product_template._get_combination_info()
|
||||
self.assertEqual(combination_info['price'], 2222 * discount_rate * currency_ratio)
|
||||
self.assertEqual(combination_info['list_price'], 2222 * currency_ratio)
|
||||
self.assertEqual(combination_info['has_discounted_price'], True)
|
||||
|
||||
# CASE: B2C setting
|
||||
group_tax_included = self.env.ref('account.group_show_line_subtotals_tax_included').with_context(active_test=False)
|
||||
group_tax_excluded = self.env.ref('account.group_show_line_subtotals_tax_excluded').with_context(active_test=False)
|
||||
group_tax_excluded.users -= self.env.user
|
||||
group_tax_included.users |= self.env.user
|
||||
# CASE: B2C setting
|
||||
website.show_line_subtotals_tax_selection = 'tax_included'
|
||||
|
||||
combination_info = product_template._get_combination_info(pricelist=pricelist)
|
||||
self.assertEqual(combination_info['price'], 2222 * discount_rate * currency_ratio * tax_ratio)
|
||||
self.assertEqual(combination_info['list_price'], 2222 * discount_rate * currency_ratio * tax_ratio)
|
||||
self.assertEqual(combination_info['price_extra'], round(222 * currency_ratio * tax_ratio, 2))
|
||||
self.assertEqual(combination_info['has_discounted_price'], False)
|
||||
|
||||
# CASE: pricelist 'without_discount'
|
||||
pricelist.discount_policy = 'without_discount'
|
||||
|
||||
combination_info = product_template._get_combination_info(pricelist=pricelist)
|
||||
self.assertEqual(combination_info['price'], pricelist.currency_id.round(2222 * discount_rate * currency_ratio * tax_ratio), 0)
|
||||
self.assertEqual(combination_info['list_price'], pricelist.currency_id.round(2222 * currency_ratio * tax_ratio), 0)
|
||||
self.assertEqual(combination_info['price_extra'], pricelist.currency_id.round(222 * currency_ratio * tax_ratio), 0)
|
||||
self.assertEqual(combination_info['has_discounted_price'], True)
|
||||
combination_info = product_template._get_combination_info()
|
||||
self.assertEqual(combination_info['price'], 2222 * discount_rate * currency_ratio * tax_ratio)
|
||||
self.assertAlmostEqual(combination_info['list_price'], 2222 * currency_ratio * tax_ratio)
|
||||
self.assertEqual(combination_info['has_discounted_price'], True)
|
||||
|
||||
def test_get_combination_info_with_fpos(self):
|
||||
# Setup product.
|
||||
self.env.user.partner_id.write({
|
||||
'country_id': False,
|
||||
'property_product_pricelist': self.env.ref('product.list0').id,
|
||||
})
|
||||
current_website = self.env['website'].get_current_website()
|
||||
pricelist = current_website.get_current_pricelist()
|
||||
(self.env['product.pricelist'].search([]) - pricelist).write({'active': False})
|
||||
product = self.env['product.template'].create({
|
||||
'name': 'Test Product',
|
||||
'list_price': 2000,
|
||||
|
|
@ -94,10 +78,19 @@ class TestWebsiteSaleProductAttributeValueConfig(TestSaleProductAttributeValueCo
|
|||
'company_id': self.env.company.id,
|
||||
})
|
||||
|
||||
# Setup website.
|
||||
website = self.env['website'].create({
|
||||
'name': "Test website",
|
||||
'company_id': self.env.company.id,
|
||||
'user_id': self.env.user.id,
|
||||
})
|
||||
|
||||
# Setup pricelist: make sure the pricelist has a 10% discount
|
||||
pricelist = self.env['product.pricelist'].create({
|
||||
self.env['product.pricelist'].search([]).action_archive()
|
||||
self.env['product.pricelist'].create({
|
||||
'name': "test_get_combination_info",
|
||||
'company_id': self.env.company.id,
|
||||
'website_id': website.id,
|
||||
'item_ids': [Command.create({
|
||||
'applied_on': "1_product",
|
||||
'base': "list_price",
|
||||
|
|
@ -107,14 +100,6 @@ class TestWebsiteSaleProductAttributeValueConfig(TestSaleProductAttributeValueCo
|
|||
})],
|
||||
})
|
||||
|
||||
# Setup website.
|
||||
website = self.env['website'].create({
|
||||
'name': "Test website",
|
||||
'company_id': self.env.company.id,
|
||||
'user_id': self.env.user.id,
|
||||
'pricelist_ids': [Command.set(pricelist.ids)],
|
||||
})
|
||||
|
||||
product = product.with_context(website_id=website.id)
|
||||
|
||||
# Setup product attributes.
|
||||
|
|
@ -126,192 +111,67 @@ class TestWebsiteSaleProductAttributeValueConfig(TestSaleProductAttributeValueCo
|
|||
computer_ssd_attribute_lines.product_template_value_ids[0].price_extra = 200
|
||||
|
||||
# Enable tax included
|
||||
group_tax_included = self.env.ref('account.group_show_line_subtotals_tax_included').with_context(active_test=False)
|
||||
group_tax_excluded = self.env.ref('account.group_show_line_subtotals_tax_excluded').with_context(active_test=False)
|
||||
group_tax_excluded.users -= self.env.user
|
||||
group_tax_included.users |= self.env.user
|
||||
website.show_line_subtotals_tax_selection = 'tax_included'
|
||||
|
||||
combination_info = product._get_combination_info(pricelist=pricelist)
|
||||
with MockRequest(product.env, website=website):
|
||||
combination_info = product._get_combination_info()
|
||||
self.assertEqual(combination_info['price'], 575, "500$ + 15% tax")
|
||||
self.assertEqual(combination_info['list_price'], 575, "500$ + 15% tax (2)")
|
||||
self.assertEqual(combination_info['price_extra'], 230, "200$ + 15% tax")
|
||||
|
||||
# Setup fiscal position 15% => 0%.
|
||||
us_country = self.env.ref('base.us')
|
||||
tax0 = self.env['account.tax'].create({'name': "Test tax 0", 'amount': 0})
|
||||
self.env['account.fiscal.position'].create({
|
||||
jp_country = self.env.ref('base.jp')
|
||||
fp = self.env['account.fiscal.position'].create({
|
||||
'name': "test_get_combination_info_with_fpos",
|
||||
'auto_apply': True,
|
||||
'country_id': us_country.id,
|
||||
'tax_ids': [Command.create({
|
||||
'tax_src_id': self.company_data['default_tax_sale'].id,
|
||||
'tax_dest_id': tax0.id,
|
||||
})],
|
||||
'country_id': jp_country.id,
|
||||
})
|
||||
tax0 = self.env['account.tax'].create({'name': "Test tax 0", 'amount': 0, 'fiscal_position_ids': [Command.link(fp.id)], 'original_tax_ids': [Command.set(self.company_data['default_tax_sale'].ids)]})
|
||||
|
||||
# Now with fiscal position, taxes should be mapped
|
||||
self.env.user.partner_id.country_id = us_country
|
||||
combination_info = product._get_combination_info(pricelist=pricelist)
|
||||
self.env.user.partner_id.country_id = jp_country
|
||||
with MockRequest(product.env, website=website):
|
||||
combination_info = product._get_combination_info()
|
||||
self.assertEqual(combination_info['price'], 500, "500% + 0% tax (mapped from fp 15% -> 0%)")
|
||||
self.assertEqual(combination_info['list_price'], 500, "500% + 0% tax (mapped from fp 15% -> 0%)")
|
||||
self.assertEqual(combination_info['price_extra'], 200, "200% + 0% tax (mapped from fp 15% -> 0%)")
|
||||
|
||||
# Try same flow with tax included
|
||||
self.company_data['default_tax_sale'].price_include = True
|
||||
self.company_data['default_tax_sale'].price_include_override = 'tax_included'
|
||||
|
||||
# Reset / Safety check
|
||||
self.env.user.partner_id.country_id = None
|
||||
combination_info = product._get_combination_info(pricelist=pricelist)
|
||||
with MockRequest(product.env, website=website):
|
||||
combination_info = product._get_combination_info()
|
||||
self.assertEqual(combination_info['price'], 500, "434.78$ + 15% tax")
|
||||
self.assertEqual(combination_info['list_price'], 500, "434.78$ + 15% tax (2)")
|
||||
self.assertEqual(combination_info['price_extra'], 200, "173.91$ + 15% tax")
|
||||
|
||||
# Now with fiscal position, taxes should be mapped
|
||||
self.env.user.partner_id.country_id = us_country.id
|
||||
combination_info = product._get_combination_info(pricelist=pricelist)
|
||||
self.env.user.partner_id.country_id = jp_country.id
|
||||
with MockRequest(product.env, website=website):
|
||||
combination_info = product._get_combination_info()
|
||||
self.assertEqual(round(combination_info['price'], 2), 434.78, "434.78$ + 0% tax (mapped from fp 15% -> 0%)")
|
||||
self.assertEqual(round(combination_info['list_price'], 2), 434.78, "434.78$ + 0% tax (mapped from fp 15% -> 0%)")
|
||||
self.assertEqual(combination_info['price_extra'], 173.91, "173.91$ + 0% tax (mapped from fp 15% -> 0%)")
|
||||
|
||||
# Try same flow with tax included for apply tax
|
||||
tax0.write({'name': "Test tax 5", 'amount': 5, 'price_include': True})
|
||||
combination_info = product._get_combination_info(pricelist=pricelist)
|
||||
tax0.write({'name': "Test tax 5", 'amount': 5, 'price_include_override': 'tax_included'})
|
||||
with MockRequest(product.env, website=website):
|
||||
combination_info = product._get_combination_info()
|
||||
self.assertEqual(round(combination_info['price'], 2), 456.52, "434.78$ + 5% tax (mapped from fp 15% -> 5% for BE)")
|
||||
self.assertEqual(round(combination_info['list_price'], 2), 456.52, "434.78$ + 5% tax (mapped from fp 15% -> 5% for BE)")
|
||||
self.assertEqual(combination_info['price_extra'], 182.61, "173.91$ + 5% tax (mapped from fp 15% -> 5% for BE)")
|
||||
|
||||
def test_hide_attribute_value_without_matching_product_variant(self):
|
||||
"""Ensure attribute values are hidden if they don't have a matching product variant"""
|
||||
self.ssd_attribute.preview_variants = 'visible'
|
||||
|
||||
@tagged('post_install', '-at_install', 'product_pricelist')
|
||||
class TestWebsiteSaleProductPricelist(TestSaleProductAttributeValueCommon):
|
||||
def test_cart_update_with_fpos(self):
|
||||
# We will test that the mapping of an 10% included tax by a 6% by a fiscal position is taken into account when updating the cart
|
||||
self.env.user.partner_id.write({
|
||||
'country_id': False,
|
||||
'property_product_pricelist': self.env.ref('product.list0').id,
|
||||
})
|
||||
current_website = self.env['website'].get_current_website()
|
||||
pricelist = current_website.get_current_pricelist()
|
||||
(self.env['product.pricelist'].search([]) - pricelist).write({'active': False})
|
||||
# Add 10% tax on product
|
||||
tax10 = self.env['account.tax'].create({'name': "Test tax 10", 'amount': 10, 'price_include': True, 'amount_type': 'percent'})
|
||||
tax6 = self.env['account.tax'].create({'name': "Test tax 6", 'amount': 6, 'price_include': True, 'amount_type': 'percent'})
|
||||
|
||||
test_product = self.env['product.template'].create({
|
||||
'name': 'Test Product',
|
||||
'list_price': 110,
|
||||
'taxes_id': [(6, 0, [tax10.id])],
|
||||
}).with_context(website_id=current_website.id)
|
||||
|
||||
# Add discout of 50% for pricelist
|
||||
pricelist.item_ids = self.env['product.pricelist.item'].create({
|
||||
'applied_on': "1_product",
|
||||
'base': "list_price",
|
||||
'compute_price': "percentage",
|
||||
'percent_price': 50,
|
||||
'product_tmpl_id': test_product.id,
|
||||
product_template = self.env['product.template'].create({
|
||||
'name': 'Test Product Template',
|
||||
})
|
||||
|
||||
pricelist.discount_policy = 'without_discount'
|
||||
|
||||
# Create fiscal position mapping taxes 10% -> 6%
|
||||
fpos = self.env['account.fiscal.position'].create({
|
||||
'name': 'test',
|
||||
})
|
||||
self.env['account.fiscal.position.tax'].create({
|
||||
'position_id': fpos.id,
|
||||
'tax_src_id': tax10.id,
|
||||
'tax_dest_id': tax6.id,
|
||||
})
|
||||
so = self.env['sale.order'].create({
|
||||
'partner_id': self.env.user.partner_id.id,
|
||||
})
|
||||
sol = self.env['sale.order.line'].create({
|
||||
'product_id': test_product.product_variant_id.id,
|
||||
'order_id': so.id,
|
||||
})
|
||||
self.assertEqual(round(sol.price_total), 55.0, "110$ with 50% discount 10% included tax")
|
||||
self.assertEqual(round(sol.price_tax), 5.0, "110$ with 50% discount 10% included tax")
|
||||
so.pricelist_id = pricelist
|
||||
so.fiscal_position_id = fpos
|
||||
sol._compute_tax_id()
|
||||
with MockRequest(self.env, website=current_website, sale_order_id=so.id):
|
||||
so._cart_update(product_id=test_product.product_variant_id.id, line_id=sol.id, set_qty=2)
|
||||
self.assertEqual(round(sol.price_total), 106, "2 units @ 100$ with 50% discount + 6% tax (mapped from fp 10% -> 6%)")
|
||||
|
||||
def test_cart_update_with_fpos_no_variant_product(self):
|
||||
# We will test that the mapping of an 10% included tax by a 0% by a fiscal position is taken into account when updating the cart for no_variant product
|
||||
self.env.user.partner_id.write({
|
||||
'country_id': False,
|
||||
'property_product_pricelist': self.env.ref('product.list0').id,
|
||||
})
|
||||
current_website = self.env['website'].get_current_website()
|
||||
pricelist = current_website.get_current_pricelist()
|
||||
(self.env['product.pricelist'].search([]) - pricelist).write({'active': False})
|
||||
# Add 10% tax on product
|
||||
tax10 = self.env['account.tax'].create({'name': "Test tax 10", 'amount': 10, 'price_include': True, 'amount_type': 'percent', 'type_tax_use': 'sale'})
|
||||
tax0 = self.env['account.tax'].create({'name': "Test tax 0", 'amount': 0, 'price_include': True, 'amount_type': 'percent', 'type_tax_use': 'sale'})
|
||||
|
||||
# Create fiscal position mapping taxes 10% -> 0%
|
||||
fpos = self.env['account.fiscal.position'].create({
|
||||
'name': 'test',
|
||||
})
|
||||
self.env['account.fiscal.position.tax'].create({
|
||||
'position_id': fpos.id,
|
||||
'tax_src_id': tax10.id,
|
||||
'tax_dest_id': tax0.id,
|
||||
self.env['product.template.attribute.line'].create({
|
||||
'product_tmpl_id': product_template.id,
|
||||
'attribute_id': self.ssd_attribute.id,
|
||||
'value_ids': [Command.set((self.ssd_256.id, self.ssd_512.id))],
|
||||
})
|
||||
|
||||
product = self.env['product.product'].create({
|
||||
'name': 'prod_no_variant',
|
||||
'list_price': 110,
|
||||
'taxes_id': [(6, 0, [tax10.id])],
|
||||
'type': 'consu',
|
||||
})
|
||||
|
||||
# create an attribute with one variant
|
||||
product_attribute = self.env['product.attribute'].create({
|
||||
'name': 'test_attr',
|
||||
'display_type': 'radio',
|
||||
'create_variant': 'no_variant',
|
||||
})
|
||||
|
||||
# create attribute value
|
||||
a1 = self.env['product.attribute.value'].create({
|
||||
'name': 'pa_value',
|
||||
'attribute_id': product_attribute.id,
|
||||
'sequence': 1,
|
||||
})
|
||||
|
||||
# set variant value to product template
|
||||
product_template = self.env['product.template'].search(
|
||||
[('name', '=', 'prod_no_variant')], limit=1)
|
||||
|
||||
product_template.attribute_line_ids = [(0, 0, {
|
||||
'attribute_id': product_attribute.id,
|
||||
'value_ids': [(6, 0, [a1.id])],
|
||||
})]
|
||||
|
||||
# publish the product on website
|
||||
product_template.is_published = True
|
||||
|
||||
# create a so for user using the fiscal position
|
||||
|
||||
so = self.env['sale.order'].create({
|
||||
'partner_id': self.env.user.partner_id.id,
|
||||
})
|
||||
sol = self.env['sale.order.line'].create({
|
||||
'name': product_template.name,
|
||||
'product_id': product.id,
|
||||
'product_uom_qty': 1,
|
||||
'product_uom': product_template.uom_id.id,
|
||||
'price_unit': product_template.list_price,
|
||||
'order_id': so.id,
|
||||
'tax_id': [(6, 0, [tax10.id])],
|
||||
})
|
||||
self.assertEqual(round(sol.price_total), 110.0, "110$ with 10% included tax")
|
||||
so.pricelist_id = pricelist
|
||||
so.fiscal_position_id = fpos
|
||||
sol._compute_tax_id()
|
||||
with MockRequest(self.env, website=current_website, sale_order_id=so.id):
|
||||
so._cart_update(product_id=product.id, line_id=sol.id, set_qty=2)
|
||||
self.assertEqual(round(sol.price_total), 200, "200$ with public price+ 0% tax (mapped from fp 10% -> 0%)")
|
||||
product_template.product_variant_ids.unlink()
|
||||
previewed_attribute_values = product_template._get_previewed_attribute_values()
|
||||
self.assertFalse(previewed_attribute_values)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,488 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from odoo.fields import Command
|
||||
from odoo.tests import HttpCase, tagged
|
||||
|
||||
from odoo.addons.website_sale.controllers.product_configurator import (
|
||||
WebsiteSaleProductConfiguratorController,
|
||||
)
|
||||
from odoo.addons.website_sale.tests.common import MockRequest, WebsiteSaleCommon
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestWebsiteSaleProductConfigurator(HttpCase, WebsiteSaleCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
cls.pc_controller = WebsiteSaleProductConfiguratorController()
|
||||
|
||||
def test_02_variants_modal_window(self):
|
||||
"""
|
||||
The objective is to verify that the data concerning the variants are well transmitted
|
||||
even when passing through a modal window (product configurator).
|
||||
|
||||
We create a product with the different attributes and we will modify them.
|
||||
If the information is not correctly transmitted,
|
||||
the default values of the variants will be used (the first one).
|
||||
"""
|
||||
|
||||
always_attribute, dynamic_attribute, never_attribute, never_attribute_custom = self.env['product.attribute'].create([
|
||||
{
|
||||
'name': 'Always attribute size',
|
||||
'display_type': 'radio',
|
||||
'create_variant': 'always'
|
||||
},
|
||||
{
|
||||
'name': 'Dynamic attribute size',
|
||||
'display_type': 'radio',
|
||||
'create_variant': 'dynamic'
|
||||
},
|
||||
{
|
||||
'name': 'Never attribute size',
|
||||
'display_type': 'radio',
|
||||
'create_variant': 'no_variant'
|
||||
},
|
||||
{
|
||||
'name': 'Never attribute size custom',
|
||||
'display_type': 'radio',
|
||||
'create_variant': 'no_variant'
|
||||
}
|
||||
])
|
||||
always_S, always_M, dynamic_S, dynamic_M, never_S, never_M, never_custom_no, never_custom_yes = self.env['product.attribute.value'].create([
|
||||
{
|
||||
'name': 'S always',
|
||||
'attribute_id': always_attribute.id,
|
||||
},
|
||||
{
|
||||
'name': 'M always',
|
||||
'attribute_id': always_attribute.id,
|
||||
},
|
||||
{
|
||||
'name': 'S dynamic',
|
||||
'attribute_id': dynamic_attribute.id,
|
||||
},
|
||||
{
|
||||
'name': 'M dynamic',
|
||||
'attribute_id': dynamic_attribute.id,
|
||||
},
|
||||
{
|
||||
'name': 'S never',
|
||||
'attribute_id': never_attribute.id,
|
||||
},
|
||||
{
|
||||
'name': 'M never',
|
||||
'attribute_id': never_attribute.id,
|
||||
},
|
||||
{
|
||||
'name': 'No never custom',
|
||||
'attribute_id': never_attribute_custom.id,
|
||||
},
|
||||
{
|
||||
'name': 'Yes never custom',
|
||||
'attribute_id': never_attribute_custom.id,
|
||||
'is_custom': True,
|
||||
}
|
||||
])
|
||||
|
||||
product_short = self.env['product.template'].create({
|
||||
'name': 'Short (TEST)',
|
||||
'website_published': True,
|
||||
'attribute_line_ids': [
|
||||
Command.create({
|
||||
'attribute_id': always_attribute.id,
|
||||
'value_ids': [(4, always_S.id), (4, always_M.id)],
|
||||
}),
|
||||
Command.create({
|
||||
'attribute_id': dynamic_attribute.id,
|
||||
'value_ids': [(4, dynamic_S.id), (4, dynamic_M.id)],
|
||||
}),
|
||||
Command.create({
|
||||
'attribute_id': never_attribute.id,
|
||||
'value_ids': [(4, never_S.id), (4, never_M.id)],
|
||||
}),
|
||||
Command.create({
|
||||
'attribute_id': never_attribute_custom.id,
|
||||
'value_ids': [(4, never_custom_no.id), (4, never_custom_yes.id)],
|
||||
}),
|
||||
]
|
||||
})
|
||||
|
||||
# Add an optional product to trigger the modal window
|
||||
optional_product = self.env['product.template'].create({
|
||||
'name': 'Optional product (TEST)',
|
||||
'website_published': True,
|
||||
})
|
||||
product_short.optional_product_ids = [(4, optional_product.id)]
|
||||
|
||||
old_sale_order = self.env['sale.order'].search([])
|
||||
self.start_tour("/", 'tour_variants_modal_window')
|
||||
|
||||
# Check the name of the created sale order line
|
||||
new_sale_order = self.env['sale.order'].search([]) - old_sale_order
|
||||
new_order_line = new_sale_order.order_line
|
||||
self.assertEqual(new_order_line.name, 'Short (TEST) (M always, M dynamic)\nNever attribute size: M never\nNever attribute size custom: Yes never custom: TEST')
|
||||
|
||||
def test_product_configurator_optional_products(self):
|
||||
""" Test that the product configurator is shown if the product has optional products. """
|
||||
main_product = self.env['product.template'].create({
|
||||
'name': "Main product",
|
||||
'website_published': True,
|
||||
'optional_product_ids': [
|
||||
Command.create({
|
||||
'name': "Optional product",
|
||||
'website_published': True,
|
||||
})
|
||||
],
|
||||
})
|
||||
|
||||
with MockRequest(self.env, website=self.website):
|
||||
show_configurator = self.pc_controller.website_sale_should_show_product_configurator(
|
||||
product_template_id=main_product.id, ptav_ids=[], is_product_configured=False
|
||||
)
|
||||
|
||||
self.assertTrue(show_configurator)
|
||||
|
||||
def test_optional_products_not_visible_on_other_websites(self):
|
||||
"""Optional products assigned to a different website should not be shown"""
|
||||
second_website = self.env['website'].create({'name': 'second website'})
|
||||
optional_product = self.env['product.template'].create({
|
||||
'name': "Optional product",
|
||||
'website_published': True,
|
||||
'website_id': second_website.id
|
||||
})
|
||||
|
||||
main_product = self.env['product.template'].create({
|
||||
'name': "Main product",
|
||||
'website_published': True,
|
||||
'optional_product_ids': [Command.set(optional_product.ids)],
|
||||
})
|
||||
|
||||
with MockRequest(self.env, website=self.website):
|
||||
show_configurator = self.pc_controller.website_sale_should_show_product_configurator(
|
||||
product_template_id=main_product.id, ptav_ids=[], is_product_configured=False
|
||||
)
|
||||
|
||||
self.assertFalse(show_configurator)
|
||||
|
||||
def test_product_configurator_single_variant(self):
|
||||
""" Test that the product configurator isn't shown if the product has a single variant. """
|
||||
attribute = self.env['product.attribute'].create({
|
||||
'name': "Attribute",
|
||||
'value_ids': [Command.create({'name': "A"})],
|
||||
})
|
||||
main_product = self.env['product.template'].create({
|
||||
'name': "Main product",
|
||||
'website_published': True,
|
||||
'attribute_line_ids': [
|
||||
Command.create({
|
||||
'attribute_id': attribute.id,
|
||||
'value_ids': [Command.set(attribute.value_ids.ids)],
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
with MockRequest(self.env, website=self.website):
|
||||
show_configurator = self.pc_controller.website_sale_should_show_product_configurator(
|
||||
product_template_id=main_product.id, ptav_ids=[], is_product_configured=False
|
||||
)
|
||||
|
||||
self.assertFalse(show_configurator)
|
||||
|
||||
def test_product_configurator_configured_with_empty_multi_checkbox(self):
|
||||
""" Test that the product configurator isn't shown if the product is configured and has a
|
||||
multi-checkbox attribute with no selected values.
|
||||
"""
|
||||
multi_attribute = self.env['product.attribute'].create({
|
||||
'name': "Multi-checkbox attribute",
|
||||
'display_type': 'multi',
|
||||
'create_variant': 'no_variant',
|
||||
'value_ids': [
|
||||
Command.create({'name': "A"}),
|
||||
Command.create({'name': "B"}),
|
||||
],
|
||||
})
|
||||
main_product = self.env['product.template'].create({
|
||||
'name': "Main product",
|
||||
'website_published': True,
|
||||
'attribute_line_ids': [
|
||||
Command.create({
|
||||
'attribute_id': multi_attribute.id,
|
||||
'value_ids': [Command.set(multi_attribute.value_ids.ids)],
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
with MockRequest(self.env, website=self.website):
|
||||
show_configurator = self.pc_controller.website_sale_should_show_product_configurator(
|
||||
product_template_id=main_product.id, ptav_ids=[], is_product_configured=True
|
||||
)
|
||||
|
||||
self.assertFalse(show_configurator)
|
||||
|
||||
def test_product_configurator_only_no_variant_attributes(self):
|
||||
""" Test that the product configurator is shown if the product isn't configured and has only
|
||||
no_variant attributes.
|
||||
"""
|
||||
no_variant_attribute = self.env['product.attribute'].create({
|
||||
'name': "No variant attribute",
|
||||
'create_variant': 'no_variant',
|
||||
'value_ids': [
|
||||
Command.create({'name': "A"}),
|
||||
Command.create({'name': "B"}),
|
||||
],
|
||||
})
|
||||
main_product = self.env['product.template'].create({
|
||||
'name': "Main product",
|
||||
'website_published': True,
|
||||
'attribute_line_ids': [
|
||||
Command.create({
|
||||
'attribute_id': no_variant_attribute.id,
|
||||
'value_ids': [Command.set(no_variant_attribute.value_ids.ids)],
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
with MockRequest(self.env, website=self.website):
|
||||
show_configurator = self.pc_controller.website_sale_should_show_product_configurator(
|
||||
product_template_id=main_product.id, ptav_ids=[], is_product_configured=False
|
||||
)
|
||||
|
||||
self.assertTrue(show_configurator)
|
||||
|
||||
def test_product_configurator_only_dynamic_attributes(self):
|
||||
""" Test that the product configurator is shown if the product isn't configured and has only
|
||||
dynamic attributes.
|
||||
"""
|
||||
dynamic_attribute = self.env['product.attribute'].create({
|
||||
'name': "Dynamic attribute",
|
||||
'create_variant': 'dynamic',
|
||||
'value_ids': [
|
||||
Command.create({'name': "A"}),
|
||||
Command.create({'name': "B"}),
|
||||
],
|
||||
})
|
||||
main_product = self.env['product.template'].create({
|
||||
'name': "Main product",
|
||||
'website_published': True,
|
||||
'attribute_line_ids': [
|
||||
Command.create({
|
||||
'attribute_id': dynamic_attribute.id,
|
||||
'value_ids': [Command.set(dynamic_attribute.value_ids.ids)],
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
with MockRequest(self.env, website=self.website):
|
||||
show_configurator = self.pc_controller.website_sale_should_show_product_configurator(
|
||||
product_template_id=main_product.id, ptav_ids=[], is_product_configured=False
|
||||
)
|
||||
|
||||
self.assertTrue(show_configurator)
|
||||
|
||||
def test_product_configurator_single_custom_attribute(self):
|
||||
""" Test that the product configurator is shown if the product isn't configured and has a
|
||||
single custom attribute.
|
||||
"""
|
||||
custom_attribute = self.env['product.attribute'].create({
|
||||
'name': "Custom attribute",
|
||||
'value_ids': [
|
||||
Command.create({
|
||||
'name': "Custom value",
|
||||
'is_custom': True,
|
||||
}),
|
||||
],
|
||||
})
|
||||
main_product = self.env['product.template'].create({
|
||||
'name': "Main product",
|
||||
'website_published': True,
|
||||
'attribute_line_ids': [
|
||||
Command.create({
|
||||
'attribute_id': custom_attribute.id,
|
||||
'value_ids': [Command.set(custom_attribute.value_ids.ids)],
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
with MockRequest(self.env, website=self.website):
|
||||
show_configurator = self.pc_controller.website_sale_should_show_product_configurator(
|
||||
product_template_id=main_product.id, ptav_ids=[], is_product_configured=False
|
||||
)
|
||||
|
||||
self.assertTrue(show_configurator)
|
||||
|
||||
def test_product_configurator_sale_not_ok(self):
|
||||
""" Test that the product configurator skips optional products which aren't `sale_ok`. """
|
||||
optional_product = self.env['product.template'].create({
|
||||
'name': "Optional product",
|
||||
'website_published': True,
|
||||
'sale_ok': False,
|
||||
})
|
||||
main_product = self.env['product.template'].create({
|
||||
'name': "Main product",
|
||||
'website_published': True,
|
||||
'optional_product_ids': [Command.set(optional_product.ids)],
|
||||
})
|
||||
|
||||
with MockRequest(self.env, website=self.website):
|
||||
show_configurator = self.pc_controller.website_sale_should_show_product_configurator(
|
||||
product_template_id=main_product.id, ptav_ids=[], is_product_configured=False
|
||||
)
|
||||
configurator_values = self.pc_controller.website_sale_product_configurator_get_values(
|
||||
product_template_id=main_product.id,
|
||||
quantity=1,
|
||||
currency_id=self.currency.id,
|
||||
so_date='2000-01-01',
|
||||
pricelist_id=self.pricelist.id,
|
||||
)
|
||||
|
||||
self.assertFalse(show_configurator)
|
||||
self.assertListEqual(configurator_values['optional_products'], [])
|
||||
|
||||
def test_product_configurator_extra_price_taxes(self):
|
||||
""" Test that the product configurator applies taxes to PTAV extra prices. """
|
||||
self.website.show_line_subtotals_tax_selection = 'tax_included'
|
||||
tax = self.env['account.tax'].create({'name': "Tax", 'amount': 10})
|
||||
attribute = self.env['product.attribute'].create({
|
||||
'name': "Attribute",
|
||||
'value_ids': [Command.create({'name': "A", 'default_extra_price': 1})],
|
||||
})
|
||||
product = self.env['product.template'].create({
|
||||
'name': "Product",
|
||||
'website_published': True,
|
||||
'attribute_line_ids': [
|
||||
Command.create({
|
||||
'attribute_id': attribute.id,
|
||||
'value_ids': [Command.set(attribute.value_ids.ids)],
|
||||
}),
|
||||
],
|
||||
'taxes_id': tax,
|
||||
})
|
||||
|
||||
with MockRequest(self.env, website=self.website):
|
||||
ptav_price_extra = self.pc_controller._get_ptav_price_extra(
|
||||
product.attribute_line_ids.product_template_value_ids,
|
||||
self.currency,
|
||||
datetime(2000, 1, 1),
|
||||
product,
|
||||
)
|
||||
|
||||
self.assertEqual(ptav_price_extra, 1.1)
|
||||
|
||||
def test_product_configurator_zero_priced(self):
|
||||
""" Test that the product configurator prevents the sale of zero-priced products. """
|
||||
self.website.prevent_zero_price_sale = True
|
||||
price_attribute = self.env['product.attribute'].create({
|
||||
'name': "Price",
|
||||
'value_ids': [
|
||||
Command.create({'name': "Zero-priced"}),
|
||||
Command.create({'name': "Nonzero-priced", 'default_extra_price': 1}),
|
||||
],
|
||||
})
|
||||
optional_product = self.env['product.template'].create({
|
||||
'name': "Optional product",
|
||||
'website_published': True,
|
||||
'list_price': 0,
|
||||
'attribute_line_ids': [
|
||||
Command.create({
|
||||
'attribute_id': price_attribute.id,
|
||||
'value_ids': [Command.set(price_attribute.value_ids.ids)],
|
||||
}),
|
||||
],
|
||||
})
|
||||
self.env['product.template'].create({
|
||||
'name': "Main product",
|
||||
'website_published': True,
|
||||
'optional_product_ids': [Command.set(optional_product.ids)],
|
||||
})
|
||||
self.start_tour('/', 'website_sale_product_configurator_zero_priced')
|
||||
|
||||
def test_product_configurator_strikethrough_price(self):
|
||||
""" Test that the product configurator displays the strikethrough price correctly. """
|
||||
self.env['res.config.settings'].create({
|
||||
'group_product_price_comparison': True,
|
||||
# Need to enable pricelists for self.pricelist to be considered and applied
|
||||
'group_product_pricelist': True,
|
||||
}).execute()
|
||||
self.website.show_line_subtotals_tax_selection = 'tax_included'
|
||||
tax = self.env['account.tax'].create({'name': "Tax", 'amount': 10})
|
||||
optional_product = self.env['product.template'].create({
|
||||
'name': "Optional product",
|
||||
'website_published': True,
|
||||
'list_price': 5,
|
||||
'compare_list_price': 10,
|
||||
'taxes_id': tax,
|
||||
})
|
||||
main_product = self.env['product.template'].create({
|
||||
'name': "Main product",
|
||||
'website_published': True,
|
||||
'list_price': 100,
|
||||
'optional_product_ids': [Command.set(optional_product.ids)],
|
||||
'taxes_id': tax,
|
||||
})
|
||||
self.pricelist.item_ids = [
|
||||
Command.create({
|
||||
'applied_on': '1_product',
|
||||
'percent_price': 50,
|
||||
'compute_price': 'percentage',
|
||||
'product_tmpl_id': main_product.id,
|
||||
}),
|
||||
]
|
||||
self.start_tour('/shop', 'website_sale_product_configurator_strikethrough_price')
|
||||
|
||||
def test_get_product_combination_multi_attribute_with_archived_variant_and_inactive_ptav(self):
|
||||
"""
|
||||
This test covers a case where a product has multiple attributes and one
|
||||
of the attribute values corresponds to an archived variant, with its
|
||||
ptav_active set to False.
|
||||
|
||||
In this scenario, a valid combination should still be possible, and the
|
||||
resulting combination product must not be the archived variant.
|
||||
"""
|
||||
attribute_single = self.env['product.attribute'].create({
|
||||
'name': "attribute single",
|
||||
'value_ids': [
|
||||
Command.create({
|
||||
'name': "single",
|
||||
}),
|
||||
],
|
||||
})
|
||||
attribute_multi = self.env['product.attribute'].create({
|
||||
'name': "attribute multi",
|
||||
'value_ids': [
|
||||
Command.create({'name': "first"}),
|
||||
Command.create({'name': "second"}),
|
||||
Command.create({'name': "third"}),
|
||||
],
|
||||
})
|
||||
main_product = self.env['product.template'].create({
|
||||
'name': "Main product",
|
||||
'website_published': True,
|
||||
'attribute_line_ids': [
|
||||
Command.create({
|
||||
'attribute_id': attribute_single.id,
|
||||
'value_ids': [Command.set(attribute_single.value_ids.ids)],
|
||||
}),
|
||||
Command.create({
|
||||
'attribute_id': attribute_multi.id,
|
||||
'value_ids': [Command.set(attribute_multi.value_ids.ids)],
|
||||
}),
|
||||
],
|
||||
})
|
||||
main_product.product_variant_ids.filtered(
|
||||
lambda product: product.product_template_attribute_value_ids[1].name == 'first',
|
||||
).action_archive()
|
||||
main_product.attribute_line_ids[1].product_template_value_ids[0].ptav_active = False
|
||||
with MockRequest(self.env, website=self.website):
|
||||
product_values = self.pc_controller._prepare_product_values(
|
||||
main_product,
|
||||
self.env['product.public.category'],
|
||||
attribute_values=str(attribute_single.value_ids.id),
|
||||
)
|
||||
is_combination_possible = product_values['combination_info']['is_combination_possible']
|
||||
combination_product_id = product_values['combination_info']['product_id']
|
||||
self.assertTrue(is_combination_possible)
|
||||
self.assertTrue(self.env['product.product'].browse(combination_product_id).active)
|
||||
|
|
@ -0,0 +1,333 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import Command
|
||||
from odoo.tests import tagged, HttpCase
|
||||
from odoo.tools import SQL
|
||||
|
||||
from odoo.addons.product.tests.test_product_attribute_value_config import (
|
||||
TestProductAttributeValueCommon,
|
||||
)
|
||||
from odoo.addons.website_sale.tests.common import MockRequest, WebsiteSaleCommon
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestWebsiteSaleProductFilters(WebsiteSaleCommon, TestProductAttributeValueCommon, HttpCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
cls.WebsiteSnippetFilter = cls.env['website.snippet.filter'].with_context({
|
||||
'limit': 16,
|
||||
'search_domain': [],
|
||||
'allowed_company_ids': cls.env.company.ids,
|
||||
})
|
||||
|
||||
# Computer accessories
|
||||
cls.color_attribute = cls.env['product.attribute'].create({
|
||||
'name': "Color",
|
||||
'value_ids': [
|
||||
Command.create({'name': name})
|
||||
for name in ("Black", "Grey", "White", "Beige", "Red", "Pink")
|
||||
],
|
||||
})
|
||||
cls.env['product.template.attribute.line'].create({
|
||||
'product_tmpl_id': cls.computer_case.id,
|
||||
'attribute_id': cls.color_attribute.id,
|
||||
'value_ids': [Command.set(cls.color_attribute.value_ids.ids)],
|
||||
})
|
||||
cls.computer_case.website_published = True
|
||||
cls.black_case_M = cls.computer_case.product_variant_ids.filtered_domain([
|
||||
('product_template_attribute_value_ids.name', 'in', "Black"),
|
||||
('product_template_attribute_value_ids.name', 'in', "M"),
|
||||
])
|
||||
cls.pink_case_M = cls.computer_case.product_variant_ids.filtered_domain([
|
||||
('product_template_attribute_value_ids.name', 'in', "Pink"),
|
||||
('product_template_attribute_value_ids.name', 'in', "M"),
|
||||
])
|
||||
cls.pink_case_L = cls.computer_case.product_variant_ids.filtered_domain([
|
||||
('product_template_attribute_value_ids.name', 'in', "Pink"),
|
||||
('product_template_attribute_value_ids.name', 'in', "L"),
|
||||
])
|
||||
cls.monitor = cls.env['product.template'].create({
|
||||
'name': "Super Computer Monitor",
|
||||
'list_price': 200,
|
||||
'website_published': True,
|
||||
})
|
||||
cls.accessories = cls.computer_case.product_variant_ids + cls.monitor.product_variant_id
|
||||
cls.computer.write({
|
||||
'company_id': False,
|
||||
'website_published': True,
|
||||
'accessory_product_ids': cls.accessories.ids,
|
||||
})
|
||||
|
||||
# Computer alternatives
|
||||
cls.windows_pc = cls._create_product(
|
||||
name='Windows PC',
|
||||
lst_price=1000.0,
|
||||
standard_price=800.0,
|
||||
alternative_product_ids=[Command.set(cls.computer.ids)],
|
||||
).product_tmpl_id
|
||||
cls.mac = cls._create_product(
|
||||
name='Mac',
|
||||
uom_id=cls.uom_dozen.id,
|
||||
lst_price=200.0,
|
||||
standard_price=160.0,
|
||||
alternative_product_ids=[Command.link(cls.computer.id), Command.link(cls.windows_pc.id)]
|
||||
).product_tmpl_id
|
||||
|
||||
# More generic products to get the number of product templates to 17
|
||||
generics = cls.env['product.template'].create([{
|
||||
'name': f"Generic product {i}",
|
||||
'company_id': False,
|
||||
'website_published': True,
|
||||
} for i in range(1, 13)])
|
||||
|
||||
cls.product_tmpls = (
|
||||
cls.computer_case + cls.monitor + cls.computer + cls.windows_pc + cls.mac + generics
|
||||
)
|
||||
|
||||
# Archive all products not relevant to the test suite, bypassing ORM constraints
|
||||
cls.env.invalidate_all()
|
||||
cls.env.cr.execute(SQL('; ').join(
|
||||
SQL(
|
||||
'UPDATE %s SET active = false WHERE id NOT IN %s',
|
||||
SQL.identifier(recs._table), recs._ids,
|
||||
) for recs in (cls.product_tmpls.product_variant_ids, cls.product_tmpls)
|
||||
))
|
||||
|
||||
def test_latest_sold_filter(self):
|
||||
"""Check the latest sold filter after selling 1 computer and 3 different cases.
|
||||
|
||||
When showing variants, the computer should be the most sold product.
|
||||
When hiding variants, the case should be the most sold product.
|
||||
"""
|
||||
computer = self.computer.product_variant_id
|
||||
self.empty_cart.write({
|
||||
'website_id': self.website.id,
|
||||
'order_line': [
|
||||
Command.create({'product_id': product_id})
|
||||
for product_id in (computer + self.pink_case_M + self.pink_case_L).ids
|
||||
],
|
||||
})
|
||||
self.empty_cart.action_confirm()
|
||||
|
||||
self.cart.order_line.unlink()
|
||||
self.cart.write({
|
||||
'website_id': self.website.id,
|
||||
'order_line': [
|
||||
Command.create({'product_id': product_id})
|
||||
for product_id in (computer + self.black_case_M).ids
|
||||
],
|
||||
})
|
||||
self.cart.action_confirm()
|
||||
|
||||
dyn_filter = self.env.ref('website_sale.dynamic_filter_latest_sold_products')
|
||||
with MockRequest(self.env, website=self.website):
|
||||
with_variants = self.WebsiteSnippetFilter.with_context(
|
||||
dynamic_filter=dyn_filter,
|
||||
hide_variants=False,
|
||||
website_id=self.website.id,
|
||||
)._get_products('latest_sold')
|
||||
self.assertSetEqual(
|
||||
{p['product_id'] for p in with_variants},
|
||||
{computer.id, *(self.pink_case_L + self.pink_case_M + self.black_case_M).ids},
|
||||
'"Latest sold" filter should return 4 products without hiding variants',
|
||||
)
|
||||
self.assertEqual(
|
||||
with_variants[0]['product_id'],
|
||||
computer.id,
|
||||
"When showing variants, `computer` should be the most sold product",
|
||||
)
|
||||
|
||||
no_variants = self.WebsiteSnippetFilter.with_context(
|
||||
dynamic_filter=dyn_filter,
|
||||
hide_variants=True,
|
||||
website_id=self.website.id,
|
||||
)._get_products('latest_sold')
|
||||
self.assertSetEqual(
|
||||
{p['product_id'] for p in no_variants},
|
||||
{computer.id, self.computer_case.product_variant_id.id},
|
||||
'"Latest sold" filter should return 2 products when hiding variants',
|
||||
)
|
||||
self.assertEqual(
|
||||
no_variants[0]['product_id'],
|
||||
self.computer_case.product_variant_id.id,
|
||||
"When hiding variants, `computer_case` should be the most sold product",
|
||||
)
|
||||
|
||||
def test_latest_viewed_filter(self):
|
||||
"""Check the latest viewed filter after viewing 2 different cases and 1 computer.
|
||||
|
||||
When showing variants, the filter should return 3 items.
|
||||
When hiding variants, the filter should return 2 items.
|
||||
"""
|
||||
viewed_products = self.black_case_M + self.pink_case_L + self.computer.product_variant_id
|
||||
dyn_filter = self.env.ref('website_sale.dynamic_filter_latest_viewed_products')
|
||||
with MockRequest(self.env, website=self.website):
|
||||
visitor = self.env['website.visitor']._upsert_visitor(self.env.user.partner_id.id)
|
||||
self.env['website.track'].create([{
|
||||
'visitor_id': visitor[0],
|
||||
'product_id': product_id,
|
||||
} for product_id in viewed_products.ids])
|
||||
with_variants = self.WebsiteSnippetFilter.with_context(
|
||||
dynamic_filter=dyn_filter,
|
||||
hide_variants=False,
|
||||
)._get_products('latest_viewed')
|
||||
self.assertSetEqual(
|
||||
{p['product_id'] for p in with_variants},
|
||||
set(viewed_products.ids),
|
||||
'When showing variants, "Latest viewed" filter should return viewed variants',
|
||||
)
|
||||
|
||||
no_variants = self.WebsiteSnippetFilter.with_context(
|
||||
dynamic_filter=dyn_filter,
|
||||
hide_variants=True,
|
||||
)._get_products('latest_viewed')
|
||||
self.assertSetEqual(
|
||||
{p['product_id'] for p in no_variants},
|
||||
{self.computer_case.product_variant_id.id, self.computer.product_variant_id.id},
|
||||
'When hiding variants, "Latest viewed" filter should return 1 variant per template',
|
||||
)
|
||||
|
||||
def test_recently_sold_with_filter(self):
|
||||
"""Check the recently-sold-with filter after selling 1 computer, 1 monitor & 1 case.
|
||||
|
||||
When showing variants, the filter should return the sold variants.
|
||||
When hiding variants, the filter should return the default variants.
|
||||
"""
|
||||
computer = self.computer.product_variant_id
|
||||
monitor = self.monitor.product_variant_id
|
||||
self.empty_cart.write({
|
||||
'website_id': self.website.id,
|
||||
'order_line': [
|
||||
Command.create({'product_id': product_id})
|
||||
for product_id in (computer + monitor + self.pink_case_L).ids
|
||||
],
|
||||
})
|
||||
self.empty_cart.action_confirm()
|
||||
|
||||
dyn_filter = self.env.ref('website_sale.dynamic_filter_cross_selling_recently_sold_with')
|
||||
with MockRequest(self.env, website=self.website):
|
||||
with_variants = self.WebsiteSnippetFilter.with_context(
|
||||
dynamic_filter=dyn_filter,
|
||||
hide_variants=False,
|
||||
website_id=self.website.id,
|
||||
)._get_products('recently_sold_with', product_template_id=str(self.computer.id))
|
||||
self.assertSetEqual(
|
||||
{p['product_id'] for p in with_variants},
|
||||
{self.monitor.product_variant_id.id, self.pink_case_L.id},
|
||||
'"Recently sold with" filter should return sold variants when showing variants',
|
||||
)
|
||||
|
||||
no_variants = self.WebsiteSnippetFilter.with_context(
|
||||
dynamic_filter=dyn_filter,
|
||||
hide_variants=True,
|
||||
website_id=self.website.id,
|
||||
)._get_products('recently_sold_with', product_template_id=str(self.computer.id))
|
||||
self.assertSetEqual(
|
||||
{p['product_id'] for p in no_variants},
|
||||
{self.monitor.product_variant_id.id, self.computer_case.product_variant_id.id},
|
||||
'"Recently sold with" filter should return generic variants when hiding variants',
|
||||
)
|
||||
|
||||
def test_accessories_filter(self):
|
||||
"""Check the accessories filter on the computer product.
|
||||
|
||||
When showing variants, the filter should return 16 (limit) accessory products.
|
||||
When hiding variants, the filter should return 2 products: monitor & case.
|
||||
"""
|
||||
dyn_filter = self.env.ref('website_sale.dynamic_filter_cross_selling_accessories')
|
||||
with MockRequest(self.env, website=self.website):
|
||||
with_variants = self.WebsiteSnippetFilter.with_context(
|
||||
dynamic_filter=dyn_filter,
|
||||
hide_variants=False,
|
||||
)._get_products('accessories', product_template_id=str(self.computer.id))
|
||||
self.assertListEqual(
|
||||
[p['product_id'] for p in with_variants],
|
||||
self.computer_case.product_variant_ids.ids[:16],
|
||||
"Accessories filter should return 16 results when showing variants",
|
||||
)
|
||||
|
||||
no_variants = self.WebsiteSnippetFilter.with_context(
|
||||
dynamic_filter=dyn_filter,
|
||||
hide_variants=True,
|
||||
)._get_products('accessories', product_template_id=str(self.computer.id))
|
||||
self.assertListEqual(
|
||||
[p['product_id'] for p in no_variants],
|
||||
self.accessories.product_variant_id.ids,
|
||||
"Accessories filter should return 2 results when hiding variants",
|
||||
)
|
||||
|
||||
def test_alternative_products_filter(self):
|
||||
"""Check the alternative products filter on the Mac product.
|
||||
|
||||
When showing variants, the filter should return 16 (limit) alternative products.
|
||||
When hiding variants, the filter should return 2 products: computer & Windows PC.
|
||||
"""
|
||||
dyn_filter = self.env.ref('website_sale.dynamic_filter_cross_selling_alternative_products')
|
||||
with MockRequest(self.env, website=self.website):
|
||||
with_variants = self.WebsiteSnippetFilter.with_context(
|
||||
dynamic_filter=dyn_filter,
|
||||
hide_variants=False,
|
||||
)._get_products('alternative_products', product_template_id=str(self.mac.id))
|
||||
self.assertListEqual(
|
||||
[p['product_id'] for p in with_variants],
|
||||
self.mac.alternative_product_ids.product_variant_ids.ids[:16],
|
||||
"Alternative products filter should return 16 results when showing variants",
|
||||
)
|
||||
|
||||
no_variants = self.WebsiteSnippetFilter.with_context(
|
||||
dynamic_filter=dyn_filter,
|
||||
hide_variants=True,
|
||||
)._get_products('alternative_products', product_template_id=str(self.mac.id))
|
||||
self.assertListEqual(
|
||||
[p['product_id'] for p in no_variants],
|
||||
[self.computer.product_variant_id.id, self.windows_pc.product_variant_id.id],
|
||||
"Alternative products filter should return 2 results when hiding variants",
|
||||
)
|
||||
|
||||
def test_newest_products_filter(self):
|
||||
"""Check the newest products filter.
|
||||
|
||||
When showing variants, the filter should return 16 variants with repeating templates.
|
||||
When hiding variants, the filter should return 16 templates, all unique.
|
||||
|
||||
This filter is unique in that it's defined in `data/data.xml`, and hence can't be called
|
||||
via the `_get_products` method.
|
||||
"""
|
||||
# Ensure we're working with a known set of products
|
||||
self.env['product.template'].search([('id', 'not in', self.product_tmpls.ids)]).write({
|
||||
'sale_ok': False,
|
||||
})
|
||||
|
||||
dyn_filter = self.env.ref('website_sale.dynamic_filter_newest_products')
|
||||
with MockRequest(self.env, website=self.website):
|
||||
with_variants = dyn_filter._prepare_values(search_domain=[])
|
||||
self.assertEqual(
|
||||
len(with_variants),
|
||||
16,
|
||||
"When displaying newest variants, 16 records should be shown",
|
||||
)
|
||||
self.assertLess(
|
||||
len({p['product_template_id'] for p in with_variants}),
|
||||
16,
|
||||
"When displaying newest variants, some product templates should be repeating",
|
||||
)
|
||||
|
||||
no_variants = dyn_filter._prepare_values(search_domain=['hide_variants'])
|
||||
self.assertEqual(len(no_variants), 16)
|
||||
self.assertEqual(
|
||||
len({p['product_template_id'] for p in no_variants}),
|
||||
16,
|
||||
"When displaying newest product templates, 16 unique templates should be shown",
|
||||
)
|
||||
|
||||
def test_shop_attribute_filters_remain_when_changing_page(self):
|
||||
self.env['product.attribute'].search([]).write({'visibility': 'hidden'})
|
||||
self.color_attribute.visibility = 'visible'
|
||||
self.size_attribute.visibility = 'visible'
|
||||
self.env['website'].get_current_website().shop_ppg = 1
|
||||
computer_case_copy = self.computer_case.copy()
|
||||
computer_case_copy.website_published = True
|
||||
self.start_tour('/shop', 'shop_attribute_filters_remain_when_changing_page')
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import Command
|
||||
from odoo.tests import HttpCase, tagged
|
||||
|
||||
from odoo.addons.product.tests.common import ProductVariantsCommon
|
||||
from odoo.addons.website_sale.tests.common import WebsiteSaleCommon
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestWebsiteSaleProductPage(HttpCase, ProductVariantsCommon, WebsiteSaleCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
cls.product_template_sofa.website_published = True
|
||||
|
||||
def test_toggle_contact_us_button_visibility(self):
|
||||
"""Check that the "Contact Us" button:
|
||||
- is shown for zero-priced products
|
||||
- is hidden for other products
|
||||
- is not displayed at the same time as the "Add to Cart" button
|
||||
"""
|
||||
self.website.prevent_zero_price_sale = True
|
||||
|
||||
self.product_template_sofa.list_price = 0
|
||||
red_sofa = self.product_template_sofa.product_variant_ids[:1]
|
||||
red_sofa.product_template_attribute_value_ids.price_extra = 20
|
||||
|
||||
self.start_tour(red_sofa.website_url, 'website_sale_contact_us_button')
|
||||
|
||||
def test_product_reviews_reactions_public(self):
|
||||
""" Check that public users can not react to reviews """
|
||||
password = "Pl1bhD@2!kXZ"
|
||||
manager = self.env.ref("base.user_admin")
|
||||
manager.write({"password": password})
|
||||
|
||||
self.env["ir.ui.view"].with_context(active_test=False).search([
|
||||
("key", "=", "website_sale.product_comment")
|
||||
]).write({"active": True})
|
||||
|
||||
self.product_product_7 = self.env["product.product"].create({
|
||||
"name": "Storage Box Test",
|
||||
"standard_price": 70.0,
|
||||
"list_price": 79.0,
|
||||
"website_published": True,
|
||||
"invoice_policy": "delivery",
|
||||
})
|
||||
message = self.product_product_7.product_tmpl_id.message_post(
|
||||
body="Bad box!",
|
||||
message_type="comment",
|
||||
rating_value="1",
|
||||
subtype_xmlid="mail.mt_comment"
|
||||
)
|
||||
self.authenticate(manager.login, password)
|
||||
self.make_jsonrpc_request(
|
||||
"/mail/message/reaction",
|
||||
{
|
||||
"action": "add",
|
||||
"content": "😊",
|
||||
"message_id": message.id,
|
||||
},
|
||||
)
|
||||
|
||||
self.start_tour("/", 'website_sale_product_reviews_reactions_public', login=None)
|
||||
|
||||
def test_product_pricelist_qty_change(self):
|
||||
"""Check that pricelist discounts based on product quantity display when applicable."""
|
||||
self.env['res.config.settings'].create({'group_product_pricelist': True}).execute()
|
||||
self.pricelist.item_ids = [
|
||||
Command.clear(),
|
||||
Command.create({
|
||||
'categ_id': self.product_category.id,
|
||||
'compute_price': 'percentage',
|
||||
'min_quantity': 5.0,
|
||||
'percent_price': 50.0,
|
||||
}),
|
||||
]
|
||||
self.start_tour(self.product.website_url, 'website_sale_product_pricelist_qty_change')
|
||||
|
||||
def test_product_unpublished_without_category(self):
|
||||
"""Test that products created from frontend are unpublished without category"""
|
||||
self.start_tour("/", 'product_unpublished_without_category', login="admin")
|
||||
product = self.env['product.product'].search(
|
||||
[('name', '=', 'Product Without Category')],
|
||||
limit=1,
|
||||
)
|
||||
self.assertTrue(product)
|
||||
self.assertFalse(product.website_published)
|
||||
|
||||
def test_product_published_with_category(self):
|
||||
"""Test that products with category are published"""
|
||||
self.env['product.public.category'].create({'name': 'Test Category'})
|
||||
self.start_tour("/", 'product_published_with_category', login="admin")
|
||||
product = self.env['product.product'].search(
|
||||
[('name', '=', 'Product With Category')],
|
||||
limit=1,
|
||||
)
|
||||
self.assertTrue(product)
|
||||
self.assertTrue(product.website_published)
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
from datetime import timedelta
|
||||
|
||||
from odoo.addons.website_sale.tests.common import WebsiteSaleCommon
|
||||
|
||||
|
||||
class TestProductRibbon(WebsiteSaleCommon):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
# Manual ribbon
|
||||
self.manual_ribbon = self.env['product.ribbon'].create({
|
||||
'name': "Manual Ribbon",
|
||||
'assign': 'manual',
|
||||
})
|
||||
|
||||
# Sale ribbon
|
||||
self.sale_ribbon = self.env['product.ribbon'].create({
|
||||
'name': "Sale Ribbon",
|
||||
'assign': 'sale',
|
||||
})
|
||||
|
||||
# New ribbon
|
||||
self.new_ribbon = self.env['product.ribbon'].create({
|
||||
'name': "New Ribbon",
|
||||
'assign': 'new',
|
||||
})
|
||||
|
||||
self.auto_assign_ribbon = self.env['product.ribbon'].search([('assign', '!=', 'manual')])
|
||||
|
||||
def test_manual_ribbon_assignment(self):
|
||||
self.product.website_ribbon_id = self.manual_ribbon.id
|
||||
products_prices = {'base_price': 100, 'price_reduce': 100}
|
||||
ribbon = self.product.product_tmpl_id._get_ribbon(products_prices, self.auto_assign_ribbon)
|
||||
self.assertEqual(
|
||||
ribbon, self.manual_ribbon, "Manual ribbon should be returned",
|
||||
)
|
||||
|
||||
def test_sale_ribbon_assignment(self):
|
||||
self.product.list_price = 100
|
||||
products_prices = {'base_price': 100, 'price_reduce': 80} # discounted
|
||||
ribbon = self.product.product_tmpl_id._get_ribbon(products_prices, self.auto_assign_ribbon)
|
||||
self.assertEqual(
|
||||
ribbon, self.sale_ribbon, "Sale ribbon should be returned",
|
||||
)
|
||||
|
||||
def test_new_ribbon_assignment(self):
|
||||
self.product.publish_date -= timedelta(days=10)
|
||||
products_prices = {'base_price': 100, 'price_reduce': 100}
|
||||
ribbon = self.product.product_tmpl_id._get_ribbon(products_prices, self.auto_assign_ribbon)
|
||||
self.assertEqual(
|
||||
ribbon,
|
||||
self.new_ribbon,
|
||||
"New ribbon should be returned for recently published products",
|
||||
)
|
||||
|
||||
def test_no_ribbon_if_none_match(self):
|
||||
self.product.publish_date -= timedelta(days=100)
|
||||
products_prices = {'base_price': 100, 'price_reduce': 100}
|
||||
ribbon = self.product.product_tmpl_id._get_ribbon(products_prices, self.auto_assign_ribbon, self.product)
|
||||
self.assertFalse(
|
||||
ribbon, "No ribbon should be returned when no condition is matched",
|
||||
)
|
||||
|
||||
def test_ribbon_priority_assignment(self):
|
||||
self.product.website_ribbon_id = self.manual_ribbon.id
|
||||
self.product.publish_date -= timedelta(days=10)
|
||||
products_prices = {'base_price': 100, 'price_reduce': 80} # discounted
|
||||
self.sale_ribbon.sequence = 1
|
||||
self.new_ribbon.sequence = 2
|
||||
ribbon = self.product.product_tmpl_id._get_ribbon(products_prices, self.auto_assign_ribbon)
|
||||
self.assertEqual(
|
||||
ribbon,
|
||||
self.manual_ribbon,
|
||||
"Manual ribbon should have the highest priority",
|
||||
)
|
||||
|
||||
self.product.website_ribbon_id = False
|
||||
ribbon = self.product.product_tmpl_id._get_ribbon(products_prices, self.auto_assign_ribbon)
|
||||
self.assertEqual(
|
||||
ribbon,
|
||||
self.sale_ribbon,
|
||||
"Sale ribbon should have the highest priority",
|
||||
)
|
||||
|
||||
self.new_ribbon.sequence = 1
|
||||
self.sale_ribbon.sequence = 2
|
||||
self.auto_assign_ribbon = self.env['product.ribbon'].search([('assign', '!=', 'manual')])
|
||||
ribbon = self.product.product_tmpl_id._get_ribbon(products_prices, self.auto_assign_ribbon)
|
||||
self.assertEqual(
|
||||
ribbon,
|
||||
self.new_ribbon,
|
||||
"New ribbon should have the highest priority",
|
||||
)
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from odoo.fields import Command, Date
|
||||
from odoo.tests import tagged
|
||||
|
||||
from odoo.addons.website_sale.tests.common import MockRequest, WebsiteSaleCommon
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestWebsiteSaleProductTemplate(WebsiteSaleCommon):
|
||||
|
||||
def test_website_sale_get_configurator_display_price(self):
|
||||
self.website.show_line_subtotals_tax_selection = 'tax_included'
|
||||
tax = self.env['account.tax'].create({'name': "Test tax", 'amount': 10})
|
||||
product = self._create_product(list_price=100, taxes_id=[Command.link(tax.id)])
|
||||
|
||||
env = self.env(user=self.public_user)
|
||||
with MockRequest(env, website=self.website.with_env(env)):
|
||||
configurator_price = self.env['product.template']._get_configurator_display_price(
|
||||
product_or_template=product,
|
||||
quantity=3,
|
||||
date=datetime(2000, 1, 1),
|
||||
currency=self.currency,
|
||||
pricelist=self.pricelist,
|
||||
)
|
||||
|
||||
self.assertEqual(configurator_price[0], 110)
|
||||
|
||||
def test_markup_data_uses_group_schema_when_multiple_variants(self):
|
||||
product_attribute = self.env['product.attribute'].create({
|
||||
'name': 'Test attribute',
|
||||
'create_variant': 'always',
|
||||
'value_ids': [
|
||||
Command.create({'name': 'Test value 1'}),
|
||||
Command.create({'name': 'Test value 2'}),
|
||||
],
|
||||
})
|
||||
product_template = self.env['product.template'].create({
|
||||
'name': 'Test product',
|
||||
'attribute_line_ids': [
|
||||
Command.create({
|
||||
'attribute_id': product_attribute.id,
|
||||
'value_ids': [Command.set(product_attribute.value_ids.ids)],
|
||||
}),
|
||||
],
|
||||
})
|
||||
website = self.website
|
||||
with MockRequest(website.env, website=website):
|
||||
markup_data = product_template._to_markup_data(self.website)
|
||||
self.assertEqual(markup_data['@type'], 'ProductGroup')
|
||||
self.assertEqual(len(markup_data['hasVariant']), 2)
|
||||
|
||||
def test_markup_data_uses_product_schema_when_single_variant(self):
|
||||
product_template = self.env['product.template'].create({'name': 'Test product'})
|
||||
website = self.website
|
||||
with MockRequest(website.env, website=website):
|
||||
markup_data = product_template._to_markup_data(self.website)
|
||||
self.assertEqual(markup_data['@type'], 'Product')
|
||||
|
||||
def test_markup_data_uses_taxes_excluded_price_when_configured_on_website(self):
|
||||
self.env['res.config.settings'].create({
|
||||
'show_line_subtotals_tax_selection': 'tax_excluded'
|
||||
}).execute()
|
||||
with MockRequest(self.website.env, website=self.website):
|
||||
markup_data = self.product._to_markup_data(self.website)
|
||||
self.assertEqual(
|
||||
markup_data['offers']['price'],
|
||||
self.website.currency_id.round(self.product.base_unit_price),
|
||||
)
|
||||
|
||||
def test_markup_data_uses_taxes_included_price_when_configured_on_website(self):
|
||||
self.env['res.config.settings'].create({
|
||||
'show_line_subtotals_tax_selection': 'tax_included'
|
||||
}).execute()
|
||||
self.product.price_extra = 10
|
||||
with MockRequest(self.website.env, website=self.website):
|
||||
product_tmpl = self.product.product_tmpl_id
|
||||
markup_data = self.product._to_markup_data(self.website)
|
||||
self.assertEqual(
|
||||
markup_data['offers']['price'],
|
||||
self.website.currency_id.round(
|
||||
self.product.base_unit_price * (1 + product_tmpl.taxes_id[0].amount / 100)
|
||||
),
|
||||
)
|
||||
|
||||
def test_markup_data_converts_price_to_website_currency(self):
|
||||
company_currency = self.env.company.currency_id
|
||||
# Find a currency different from the company currency.
|
||||
self.website.currency_id = self.env['res.currency'].with_context(active_test=False).search([
|
||||
('name', '!=', company_currency.name)
|
||||
], limit=1)
|
||||
with MockRequest(self.env, website=self.website):
|
||||
markup = self.product._to_markup_data(self.website)
|
||||
# Expected converted price
|
||||
expected_price = company_currency._convert(
|
||||
self.product.list_price,
|
||||
self.website.currency_id,
|
||||
company=self.env.company,
|
||||
date=Date.from_string('2020-01-01'),
|
||||
)
|
||||
self.assertAlmostEqual(markup['offers']['price'], expected_price, places=2)
|
||||
|
||||
def test_remove_archived_products_from_cart(self):
|
||||
"""Archived products shouldn't appear in carts"""
|
||||
self.product.action_archive()
|
||||
self.assertNotIn(
|
||||
self.product, self.cart.order_line.product_id,
|
||||
"Archived product should be deleted from the cart.",
|
||||
)
|
||||
self.service_product.product_tmpl_id.action_archive()
|
||||
self.assertNotIn(
|
||||
self.service_product, self.cart.order_line.product_id,
|
||||
"All products from archived product templates should be removed from the cart.",
|
||||
)
|
||||
|
||||
def test_get_additionnal_combination_info_converts_price_to_website_currency(self):
|
||||
company_currency = self.env.company.currency_id
|
||||
# Find a currency different from the company currency.
|
||||
self.website.currency_id = self.env['res.currency'].with_context(active_test=False).search([
|
||||
('name', '!=', company_currency.name)
|
||||
], limit=1)
|
||||
with MockRequest(self.env, website=self.website):
|
||||
result = self.env['product.template']._get_additionnal_combination_info(
|
||||
self.product, 1.0, self.product.uom_id, Date.from_string('2020-01-01'), self.website
|
||||
)
|
||||
# Expected converted price
|
||||
expected_price = company_currency._convert(
|
||||
self.product.list_price,
|
||||
self.website.currency_id,
|
||||
company=self.env.company,
|
||||
date=Date.from_string('2020-01-01'),
|
||||
)
|
||||
self.assertAlmostEqual(result['price'], expected_price, places=2)
|
||||
|
|
@ -1,19 +1,27 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# 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.tests import tagged
|
||||
|
||||
from odoo.addons.base.tests.common import HttpCaseWithUserPortal
|
||||
from odoo.addons.http_routing.tests.common import MockRequest
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestWebsiteSaleReorderFromPortal(HttpCase):
|
||||
class TestWebsiteSaleReorderFromPortal(HttpCaseWithUserPortal):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.env['website'].get_current_website().enabled_portal_reorder_button = True
|
||||
|
||||
def test_website_sale_reorder_from_portal(self):
|
||||
product_1, product_2 = self.env['product.product'].create([
|
||||
cls.website = cls.env['website'].get_current_website()
|
||||
cls.website.write({
|
||||
'prevent_zero_price_sale': False,
|
||||
})
|
||||
cls.empty_cart = cls.env['sale.order'].create({
|
||||
'partner_id': cls.partner_portal.id,
|
||||
})
|
||||
cls.product_1, cls.product_2 = cls.env['product.product'].create([
|
||||
{
|
||||
'name': 'Reorder Product 1',
|
||||
'sale_ok': True,
|
||||
|
|
@ -25,6 +33,8 @@ class TestWebsiteSaleReorderFromPortal(HttpCase):
|
|||
'website_published': True,
|
||||
},
|
||||
])
|
||||
|
||||
def test_website_sale_reorder_from_portal(self):
|
||||
no_variant_attribute = self.env['product.attribute'].create({
|
||||
'name': 'Size',
|
||||
'create_variant': 'no_variant',
|
||||
|
|
@ -46,15 +56,15 @@ class TestWebsiteSaleReorderFromPortal(HttpCase):
|
|||
ptav_s = ptavs.filtered(lambda ptav: ptav.product_attribute_value_id == s)
|
||||
ptav_l = ptavs.filtered(lambda ptav: ptav.product_attribute_value_id == l)
|
||||
user_admin = self.env.ref('base.user_admin')
|
||||
order = self.env['sale.order'].create({
|
||||
order = self.empty_cart
|
||||
order.write({
|
||||
'partner_id': user_admin.partner_id.id,
|
||||
'state': 'sale',
|
||||
'order_line': [
|
||||
Command.create({
|
||||
'product_id': product_1.id,
|
||||
'product_id': self.product_1.id,
|
||||
}),
|
||||
Command.create({
|
||||
'product_id': product_2.id,
|
||||
'product_id': self.product_2.id,
|
||||
}),
|
||||
Command.create({
|
||||
'product_id': no_variant_template.product_variant_id.id,
|
||||
|
|
@ -70,6 +80,7 @@ class TestWebsiteSaleReorderFromPortal(HttpCase):
|
|||
})
|
||||
],
|
||||
})
|
||||
order.action_confirm()
|
||||
order.message_subscribe(user_admin.partner_id.ids)
|
||||
|
||||
self.start_tour("/", 'website_sale_reorder_from_portal', login='admin')
|
||||
|
|
@ -79,8 +90,69 @@ class TestWebsiteSaleReorderFromPortal(HttpCase):
|
|||
order_lines = reorder_cart.order_line
|
||||
|
||||
self.assertEqual(previous_lines.product_id, order_lines.product_id)
|
||||
self.assertEqual(previous_lines.mapped('name'), order_lines.mapped('name'))
|
||||
self.assertEqual(
|
||||
previous_lines.product_id.sorted('id').mapped('name'),
|
||||
order_lines.product_id.sorted('id').mapped('name'),
|
||||
)
|
||||
self.assertEqual(
|
||||
previous_lines.product_no_variant_attribute_value_ids,
|
||||
order_lines.product_no_variant_attribute_value_ids,
|
||||
)
|
||||
|
||||
def test_is_reorder_allowed(self):
|
||||
line_published_product = Command.create({
|
||||
'product_id': self.product_1.id,
|
||||
})
|
||||
self.product_2.active = False
|
||||
line_archived_product = Command.create({
|
||||
'product_id': self.product_2.id,
|
||||
})
|
||||
line_section = Command.create({
|
||||
'name': "Free line",
|
||||
'display_type': 'line_section',
|
||||
})
|
||||
line_downpayment = Command.create({
|
||||
'name': "Down with the Payment",
|
||||
'is_downpayment': True,
|
||||
'price_unit': 5,
|
||||
})
|
||||
|
||||
order = self.empty_cart.with_user(self.user_portal).sudo()
|
||||
order.order_line = [line_section]
|
||||
order.action_confirm()
|
||||
|
||||
with MockRequest(self.env, website=self.website):
|
||||
self.assertFalse(
|
||||
order._is_reorder_allowed(),
|
||||
"Reordering a line section should not be allowed",
|
||||
)
|
||||
|
||||
order.order_line = [line_archived_product]
|
||||
self.assertFalse(
|
||||
order._is_reorder_allowed(),
|
||||
"Reordering an archived product should not be allowed",
|
||||
)
|
||||
|
||||
order.order_line = [line_published_product]
|
||||
self.assertTrue(
|
||||
order._is_reorder_allowed(),
|
||||
"Reordering a published product should be allowed",
|
||||
)
|
||||
|
||||
self.product_1.website_published = False
|
||||
self.assertFalse(
|
||||
order._is_reorder_allowed(),
|
||||
"Reordering an unpublished product should not be allowed",
|
||||
)
|
||||
|
||||
order.order_line = [line_downpayment]
|
||||
self.assertFalse(
|
||||
order._is_reorder_allowed(),
|
||||
"Reordering a down payment should not be allowed",
|
||||
)
|
||||
|
||||
self.product_2.write({'active': True, 'list_price': 0.0})
|
||||
self.assertTrue(
|
||||
order._is_reorder_allowed(),
|
||||
"Reordering a zero-priced product should be allowed when enabled",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
from odoo.fields import Command
|
||||
from odoo.tests import HttpCase
|
||||
|
||||
from odoo.addons.website_sale.tests.common import WebsiteSaleCommon
|
||||
|
||||
|
||||
class WebsiteSaleSEO(HttpCase, WebsiteSaleCommon):
|
||||
def test_website_sale_user_designer_can_edit_seo(self):
|
||||
public_categ = self.env['product.public.category'].create({'name': 'Website Category'})
|
||||
self.product.write({'public_categ_ids': [Command.link(public_categ.id)]})
|
||||
internal_user = self.env['res.users'].create({
|
||||
'name': 'Web Designer',
|
||||
'login': 'internal_user',
|
||||
'group_ids': [
|
||||
Command.link(self.ref('website.group_website_designer')),
|
||||
Command.link(self.ref('base.group_user')),
|
||||
],
|
||||
})
|
||||
self.authenticate(internal_user.login, internal_user.login)
|
||||
res = self.make_jsonrpc_request(
|
||||
'/website/get_seo_data',
|
||||
{'res_id': public_categ.id, 'res_model': 'product.public.category'},
|
||||
)
|
||||
self.assertTrue(res['can_edit_seo'])
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
from odoo.tests import tagged
|
||||
from odoo.addons.base.tests.common import BaseCommon
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestWebsiteSaleSettings(BaseCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.company = cls.env['res.company'].create({
|
||||
'name': 'Test Company',
|
||||
})
|
||||
cls.website = cls.env['website'].create({
|
||||
'name': 'Test Website',
|
||||
'company_id': cls.company.id,
|
||||
'account_on_checkout': 'mandatory',
|
||||
'auth_signup_uninvited': 'b2b',
|
||||
})
|
||||
|
||||
def test_settings_account_on_checkout(self):
|
||||
# only change auth_signup_uninvited if account_on_checkout was changed
|
||||
config = self.env['res.config.settings'].with_company(self.company)
|
||||
config.create({'account_on_checkout': 'mandatory'}).execute()
|
||||
self.assertEqual(self.website.auth_signup_uninvited, 'b2b')
|
||||
config.create({'account_on_checkout': 'optional'}).execute()
|
||||
self.assertEqual(self.website.auth_signup_uninvited, 'b2c')
|
||||
config.create({'account_on_checkout': 'disabled'}).execute()
|
||||
self.assertEqual(self.website.auth_signup_uninvited, 'b2b')
|
||||
config.create({'auth_signup_uninvited': 'b2c', 'account_on_checkout': 'disabled'}).execute()
|
||||
self.assertEqual(self.website.auth_signup_uninvited, 'b2c')
|
||||
config.create({'auth_signup_uninvited': 'b2b', 'account_on_checkout': 'mandatory'}).execute()
|
||||
self.assertEqual(self.website.auth_signup_uninvited, 'b2c')
|
||||
|
|
@ -0,0 +1,141 @@
|
|||
# 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.addons.website_sale.controllers.main import SHOP_PATH
|
||||
from odoo.addons.website_sale.tests.common import WebsiteSaleCommon
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestWebsiteSaleShopRedirects(HttpCase, WebsiteSaleCommon):
|
||||
|
||||
def test_website_sale_shop_redirects(self):
|
||||
category_a = self.env['product.public.category'].create({'name': "Category A"})
|
||||
category_b = self.env['product.public.category'].create({'name': "Category B"})
|
||||
test_product = self.env['product.template'].create({
|
||||
'name': "Test product",
|
||||
'public_categ_ids': [Command.link(category_a.id)],
|
||||
'website_published': True,
|
||||
})
|
||||
# Add a different published product to category B so that it is accessible to public users
|
||||
self._create_product(website_published=True, public_categ_ids=[Command.link(category_b.id)])
|
||||
|
||||
slug = self.env['ir.http']._slug
|
||||
|
||||
response = self.url_open(
|
||||
f'/shop?category={category_a.id}&some-key=some-value',
|
||||
allow_redirects=False,
|
||||
)
|
||||
self.assertEqual(response.status_code, 301)
|
||||
self.assertURLEqual(
|
||||
response.headers.get('Location'),
|
||||
f'/shop/category/{slug(category_a)}?some-key=some-value',
|
||||
)
|
||||
|
||||
response = self.url_open(
|
||||
f'/shop/product/{slug(test_product)}?category={category_a.id}&some-key=some-value',
|
||||
allow_redirects=False,
|
||||
)
|
||||
self.assertEqual(response.status_code, 301)
|
||||
self.assertURLEqual(
|
||||
response.headers.get('Location'),
|
||||
f'/shop/{slug(category_a)}/{slug(test_product)}?some-key=some-value',
|
||||
)
|
||||
|
||||
response = self.url_open(
|
||||
f'/shop/product/{slug(test_product)}?category=test&some-key=some-value',
|
||||
allow_redirects=False,
|
||||
)
|
||||
self.assertEqual(response.status_code, 301)
|
||||
self.assertURLEqual(
|
||||
response.headers.get('Location'),
|
||||
f'/shop/{slug(test_product)}?some-key=some-value'
|
||||
)
|
||||
|
||||
response = self.url_open(
|
||||
f'/shop/{slug(category_b)}/{slug(test_product)}?some-key=some-value',
|
||||
allow_redirects=False,
|
||||
)
|
||||
self.assertEqual(response.status_code, 301)
|
||||
self.assertURLEqual(
|
||||
response.headers.get('Location'),
|
||||
f'/shop/{slug(test_product)}?some-key=some-value',
|
||||
)
|
||||
|
||||
def test_ecommerce_product_page_url_unpublished_product(self):
|
||||
# Unpublished products should be hidden and return a 404.
|
||||
accessory_product = self.env['product.template'].create({
|
||||
'name': 'Access Product',
|
||||
'is_published': False,
|
||||
})
|
||||
url = f'{SHOP_PATH}/{accessory_product.id}'
|
||||
res = self.url_open(url)
|
||||
self.assertEqual(len(res.history), 0, "Unpublished products shouldn't redirect.")
|
||||
self.assertURLEqual(res.url, url, "Unpublished products shouldn't slug the URL.")
|
||||
self.assertEqual(res.status_code, 404, "Unpublished products should return a 404 page.")
|
||||
|
||||
def test_ecommerce_category_page_url_invalid_category(self):
|
||||
# Invalid category should return a 404.
|
||||
url = f'{SHOP_PATH}/category/999999'
|
||||
res = self.url_open(url)
|
||||
|
||||
self.assertEqual(res.status_code, 404, "Invalid category should return a 404.")
|
||||
|
||||
def test_ecommerce_product_page_url_invalid_category(self):
|
||||
# Invalid category should redirect to the canonical product page.
|
||||
accessory_product = self.env['product.template'].create({
|
||||
'name': 'Access Product',
|
||||
'is_published': True,
|
||||
})
|
||||
url = f'{SHOP_PATH}/999999/{accessory_product.id}'
|
||||
res = self.url_open(url)
|
||||
|
||||
self.assertEqual(
|
||||
len(res.history),
|
||||
1,
|
||||
"Invalid category with valid product should only redirect once to the product page.",
|
||||
)
|
||||
self.assertEqual(
|
||||
res.history[0].status_code,
|
||||
303,
|
||||
"Invalid category with valid product should redirect to the product page.",
|
||||
)
|
||||
good_url = f'{SHOP_PATH}/{self.env["ir.http"]._slug(accessory_product)}'
|
||||
self.assertURLEqual(res.url, good_url)
|
||||
|
||||
def test_ecommerce_category_page_url_unpublished_product(self):
|
||||
# Unpublished product should redirect to the canonical category page (if category provided).
|
||||
category = self.env['product.public.category'].create({
|
||||
'name': 'Test Category',
|
||||
})
|
||||
# Add a different published product to category so that it is accessible to public users
|
||||
self.env['product.template'].create({
|
||||
'name': 'Test Product',
|
||||
'is_published': True,
|
||||
'public_categ_ids': [
|
||||
Command.link(category.id),
|
||||
],
|
||||
})
|
||||
accessory_product = self.env['product.template'].create({
|
||||
'name': 'Access Product',
|
||||
'public_categ_ids': [
|
||||
Command.link(category.id),
|
||||
],
|
||||
})
|
||||
accessory_product.is_published = False
|
||||
url = f'{SHOP_PATH}/{category.id}/{accessory_product.id}'
|
||||
res = self.url_open(url)
|
||||
|
||||
self.assertEqual(
|
||||
len(res.history),
|
||||
1,
|
||||
"Unpublished product with valid category should only redirect once to the category page.",
|
||||
)
|
||||
self.assertEqual(
|
||||
res.history[0].status_code,
|
||||
303,
|
||||
"Unpublished product with valid category should redirect to the category page.",
|
||||
)
|
||||
good_url = f'{SHOP_PATH}/category/{self.env["ir.http"]._slug(category)}'
|
||||
self.assertURLEqual(res.url, good_url)
|
||||
|
|
@ -10,16 +10,12 @@ from odoo.addons.account.tests.common import AccountTestInvoicingHttpCommon
|
|||
class WebsiteSaleShopPriceListCompareListPriceDispayTests(AccountTestInvoicingHttpCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls, chart_template_ref=None):
|
||||
super().setUpClass(chart_template_ref=chart_template_ref)
|
||||
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls._enable_pricelists()
|
||||
ProductTemplate = cls.env['product.template']
|
||||
Pricelist = cls.env['product.pricelist']
|
||||
PricelistItem = cls.env['product.pricelist.item']
|
||||
Currency = cls.env['res.currency']
|
||||
CurrencyRate = cls.env['res.currency.rate']
|
||||
|
||||
# Cleanup existing pricelist.
|
||||
cls.env['website'].search([]).write({'sequence': 1000})
|
||||
website = cls.env['website'].create({
|
||||
'name': "Test website",
|
||||
|
|
@ -29,14 +25,12 @@ class WebsiteSaleShopPriceListCompareListPriceDispayTests(AccountTestInvoicingHt
|
|||
|
||||
cls.test_product_default = ProductTemplate.create({
|
||||
'name': 'test_product_default',
|
||||
'type': 'consu',
|
||||
'website_published': True,
|
||||
'list_price': 1000,
|
||||
'company_id': cls.env.company.id,
|
||||
})
|
||||
cls.test_product_with_compare_list_price = ProductTemplate.create({
|
||||
'name': 'test_product_with_compare_list_price',
|
||||
'type': 'consu',
|
||||
'website_published': True,
|
||||
'list_price': 2000,
|
||||
'compare_list_price': 2500,
|
||||
|
|
@ -45,30 +39,17 @@ class WebsiteSaleShopPriceListCompareListPriceDispayTests(AccountTestInvoicingHt
|
|||
cls.test_product_with_pricelist = ProductTemplate.create({
|
||||
'name': 'test_product_with_pricelist',
|
||||
'website_published': True,
|
||||
'type': 'consu',
|
||||
'list_price': 2000,
|
||||
'company_id': cls.env.company.id,
|
||||
})
|
||||
cls.test_product_with_pricelist_and_compare_list_price = ProductTemplate.create({
|
||||
'name': 'test_product_with_pricelist_and_compare_list_price',
|
||||
'website_published': True,
|
||||
'type': 'consu',
|
||||
'list_price': 4000,
|
||||
'compare_list_price': 4500,
|
||||
'company_id': cls.env.company.id,
|
||||
})
|
||||
|
||||
cls.test_custom_currency = Currency.create({
|
||||
'name': "Test currency",
|
||||
'symbol': 'A',
|
||||
})
|
||||
|
||||
CurrencyRate.create({
|
||||
'currency_id': cls.test_custom_currency.id,
|
||||
'name': '2000-01-01',
|
||||
'rate': 2.0,
|
||||
})
|
||||
|
||||
# Three pricelists
|
||||
Pricelist.search([]).write({'sequence': 1000})
|
||||
cls.pricelist_default = Pricelist.create({
|
||||
|
|
@ -84,7 +65,20 @@ class WebsiteSaleShopPriceListCompareListPriceDispayTests(AccountTestInvoicingHt
|
|||
'company_id': cls.env.company.id,
|
||||
'selectable': True,
|
||||
'sequence': 2,
|
||||
'discount_policy': 'with_discount',
|
||||
'item_ids': [
|
||||
Command.create({
|
||||
'applied_on': '1_product',
|
||||
'product_tmpl_id': cls.test_product_with_pricelist.id,
|
||||
'compute_price': 'fixed',
|
||||
'fixed_price': 1500,
|
||||
}),
|
||||
Command.create({
|
||||
'applied_on': '1_product',
|
||||
'product_tmpl_id': cls.test_product_with_pricelist_and_compare_list_price.id,
|
||||
'compute_price': 'percentage',
|
||||
'percent_price': 12.5,
|
||||
})
|
||||
]
|
||||
})
|
||||
cls.pricelist_without_discount = Pricelist.create({
|
||||
'name': 'pricelist_without_discount',
|
||||
|
|
@ -92,54 +86,25 @@ class WebsiteSaleShopPriceListCompareListPriceDispayTests(AccountTestInvoicingHt
|
|||
'company_id': cls.env.company.id,
|
||||
'selectable': True,
|
||||
'sequence': 3,
|
||||
'discount_policy': 'without_discount',
|
||||
})
|
||||
cls.pricelist_other_currency = Pricelist.create({
|
||||
'name': 'pricelist_other_currency',
|
||||
'website_id': website.id,
|
||||
'company_id': cls.env.company.id,
|
||||
'selectable': True,
|
||||
'sequence': 4,
|
||||
'currency_id': cls.test_custom_currency.id,
|
||||
})
|
||||
|
||||
# Pricelist items
|
||||
PricelistItem.create({
|
||||
'pricelist_id': cls.pricelist_with_discount.id,
|
||||
'applied_on': '1_product',
|
||||
'product_tmpl_id': cls.test_product_with_pricelist.id,
|
||||
'compute_price': 'fixed',
|
||||
'fixed_price': 1500,
|
||||
})
|
||||
|
||||
PricelistItem.create({
|
||||
'pricelist_id': cls.pricelist_without_discount.id,
|
||||
'applied_on': '1_product',
|
||||
'product_tmpl_id': cls.test_product_with_pricelist.id,
|
||||
'compute_price': 'fixed',
|
||||
'fixed_price': 1500,
|
||||
})
|
||||
|
||||
PricelistItem.create({
|
||||
'pricelist_id': cls.pricelist_without_discount.id,
|
||||
'applied_on': '1_product',
|
||||
'product_tmpl_id': cls.test_product_with_pricelist_and_compare_list_price.id,
|
||||
'compute_price': 'fixed',
|
||||
'fixed_price': 3500,
|
||||
})
|
||||
|
||||
PricelistItem.create({
|
||||
'pricelist_id': cls.pricelist_with_discount.id,
|
||||
'applied_on': '1_product',
|
||||
'product_tmpl_id': cls.test_product_with_pricelist_and_compare_list_price.id,
|
||||
'compute_price': 'fixed',
|
||||
'fixed_price': 3500,
|
||||
'item_ids': [
|
||||
Command.create({
|
||||
'applied_on': '1_product',
|
||||
'product_tmpl_id': cls.test_product_with_pricelist.id,
|
||||
'compute_price': 'percentage',
|
||||
'percent_price': 25,
|
||||
}),
|
||||
Command.create({
|
||||
'applied_on': '1_product',
|
||||
'product_tmpl_id': cls.test_product_with_pricelist_and_compare_list_price.id,
|
||||
'compute_price': 'percentage',
|
||||
'percent_price': 12.5,
|
||||
})
|
||||
]
|
||||
})
|
||||
|
||||
def test_compare_list_price_price_list_display(self):
|
||||
self.env.user.write({
|
||||
'groups_id': [Command.link(
|
||||
self.env.ref('website_sale.group_product_price_comparison').id
|
||||
)],
|
||||
})
|
||||
self.env['res.config.settings'].create({
|
||||
'group_product_pricelist': True,
|
||||
'group_product_price_comparison': True,
|
||||
}).execute()
|
||||
self.start_tour("/", 'compare_list_price_price_list_display', login=self.env.user.login)
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import logging
|
||||
|
||||
from odoo.tests import HttpCase, tagged
|
||||
from odoo.addons.website.tools import MockRequest
|
||||
|
||||
from odoo.addons.http_routing.tests.common import MockRequest
|
||||
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -40,20 +41,46 @@ class TestSnippets(HttpCase):
|
|||
self.start_tour('/', 'website_sale.snippet_products', login='admin')
|
||||
|
||||
def test_02_snippet_products_remove(self):
|
||||
self.user = self.env['res.users'].search([('login', '=', 'admin')])
|
||||
self.website_visitor = self.env['website.visitor'].search([('partner_id', '=', self.user.partner_id.id)])
|
||||
before_tour_product_ids = self.website_visitor.product_ids.ids
|
||||
with MockRequest(self.env, website=self.env['website'].get_current_website()):
|
||||
if not self.website_visitor:
|
||||
self.website_visitor = self.env['website.visitor'].create({'partner_id': self.user.partner_id.id})
|
||||
self.product = self.env['product.product'].create({
|
||||
'name': 'Storage Box',
|
||||
'website_published': True,
|
||||
'image_512': b'/product/static/img/product_product_9-image.jpg',
|
||||
'display_name': 'Bin',
|
||||
'description_sale': 'Pedal-based opening system',
|
||||
})
|
||||
self.website_visitor._add_viewed_product(self.product.id)
|
||||
self.start_tour('/', 'website_sale.products_snippet_recently_viewed', login='admin')
|
||||
after_tour_product_ids = self.website_visitor.product_ids.ids
|
||||
self.assertEqual(before_tour_product_ids, after_tour_product_ids, "There shouldn't be any new product in recently viewed after this tour")
|
||||
Visitor = self.env['website.visitor']
|
||||
user = self.env['res.users'].search([('login', '=', 'admin')])
|
||||
website_visitor = Visitor.search([('partner_id', '=', user.partner_id.id)])
|
||||
if not website_visitor:
|
||||
with MockRequest(user.with_user(user).env, website=self.env['website'].get_current_website()):
|
||||
website_visitor = Visitor.create({'partner_id': user.partner_id.id})
|
||||
self.assertEqual(website_visitor.name, user.name, "The visitor should be linked to the admin user, not OdooBot or anything.")
|
||||
self.product = self.env['product.product'].create({
|
||||
'name': 'Storage Box',
|
||||
'website_published': True,
|
||||
'image_512': b'/product/static/img/product_product_9-image.jpg',
|
||||
'display_name': 'Bin',
|
||||
'description_sale': 'Pedal-based opening system',
|
||||
})
|
||||
before_tour_product_ids = website_visitor.product_ids.ids
|
||||
website_visitor._add_viewed_product(self.product.id)
|
||||
|
||||
self.start_tour('/', 'website_sale.products_snippet_recently_viewed', login='admin')
|
||||
self.assertEqual(before_tour_product_ids, website_visitor.product_ids.ids, "There shouldn't be any new product in recently viewed after this tour")
|
||||
|
||||
def test_website_category_url(self):
|
||||
# Create a public category with a cover image
|
||||
category = self.env['product.public.category'].create({
|
||||
'name': "Test Category",
|
||||
})
|
||||
|
||||
self.env.company.website_id = False
|
||||
website = self.env.ref('website.default_website')
|
||||
website.update({
|
||||
'domain': "http://www.example.com",
|
||||
'company_id': self.env.company.id,
|
||||
})
|
||||
|
||||
# Simulate a request with correct context
|
||||
with MockRequest(self.env, website=website):
|
||||
original_get_base_url = self.env['product.public.category'].sudo().get_base_url()
|
||||
data = self.env['website.snippet.filter'].sudo()._prepare_category_list_data(
|
||||
parent_id=category.id,
|
||||
)
|
||||
|
||||
# Assert that the returned cover_image uses the mocked base URL
|
||||
self.assertTrue(data[0]['cover_image'].startswith(original_get_base_url))
|
||||
self.assertNotIn(website.domain, data[0]['cover_image'])
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
from odoo.tests import tagged
|
||||
from odoo.addons.website.tests.test_website_technical_page import TestWebsiteTechnicalPage
|
||||
|
||||
|
||||
@tagged("post_install", "-at_install")
|
||||
class TestWebsiteSaleTechnicalPage(TestWebsiteTechnicalPage):
|
||||
|
||||
def test_load_website_sale_technical_pages(self):
|
||||
self._validate_routes([
|
||||
"/shop",
|
||||
"/shop/confirmation",
|
||||
"/shop/extra_info",
|
||||
"/shop/payment",
|
||||
"/shop/checkout"
|
||||
])
|
||||
|
|
@ -1,37 +1,32 @@
|
|||
# coding: utf-8
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.tests import tagged
|
||||
|
||||
from odoo.addons.website_sale.controllers.main import WebsiteSale
|
||||
from odoo.addons.website.tools import MockRequest
|
||||
from odoo.tests import TransactionCase, tagged
|
||||
from odoo.addons.website_sale.tests.common import MockRequest, WebsiteSaleCommon
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class WebsiteSaleVisitorTests(TransactionCase):
|
||||
class WebsiteSaleVisitorTests(WebsiteSaleCommon):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.website = self.env.ref('website.default_website')
|
||||
self.WebsiteSaleController = WebsiteSale()
|
||||
self.cookies = {}
|
||||
|
||||
def test_create_visitor_on_tracked_product(self):
|
||||
self.WebsiteSaleController = WebsiteSale()
|
||||
existing_visitors = self.env['website.visitor'].search([])
|
||||
existing_tracks = self.env['website.track'].search([])
|
||||
|
||||
product = self.env['product.product'].create({
|
||||
'name': 'Storage Box',
|
||||
'website_published': True,
|
||||
})
|
||||
|
||||
with MockRequest(self.env, website=self.website):
|
||||
self.cookies = self.WebsiteSaleController.products_recently_viewed_update(product.id)
|
||||
cookies = self.WebsiteSaleController.products_recently_viewed_update(self.product.id)
|
||||
|
||||
new_visitors = self.env['website.visitor'].search([('id', 'not in', existing_visitors.ids)])
|
||||
new_tracks = self.env['website.track'].search([('id', 'not in', existing_tracks.ids)])
|
||||
self.assertEqual(len(new_visitors), 1, "A visitor should be created after visiting a tracked product")
|
||||
self.assertEqual(len(new_tracks), 1, "A track should be created after visiting a tracked product")
|
||||
|
||||
with MockRequest(self.env, website=self.website, cookies=self.cookies):
|
||||
self.WebsiteSaleController.products_recently_viewed_update(product.id)
|
||||
with MockRequest(self.env, website=self.website, cookies=cookies):
|
||||
self.WebsiteSaleController.products_recently_viewed_update(self.product.id)
|
||||
|
||||
new_visitors = self.env['website.visitor'].search([('id', 'not in', existing_visitors.ids)])
|
||||
new_tracks = self.env['website.track'].search([('id', 'not in', existing_tracks.ids)])
|
||||
|
|
@ -44,7 +39,7 @@ class WebsiteSaleVisitorTests(TransactionCase):
|
|||
'list_price': 320.0,
|
||||
})
|
||||
|
||||
with MockRequest(self.env, website=self.website, cookies=self.cookies):
|
||||
with MockRequest(self.env, website=self.website, cookies=cookies):
|
||||
self.WebsiteSaleController.products_recently_viewed_update(product.id)
|
||||
|
||||
new_visitors = self.env['website.visitor'].search([('id', 'not in', existing_visitors.ids)])
|
||||
|
|
@ -58,7 +53,6 @@ class WebsiteSaleVisitorTests(TransactionCase):
|
|||
new_company = self.env['res.company'].create({
|
||||
'name': 'Test Company',
|
||||
})
|
||||
public_user = self.env.ref('base.public_user')
|
||||
|
||||
product = self.env['product.product'].create({
|
||||
'name': 'Test Product',
|
||||
|
|
@ -66,17 +60,19 @@ class WebsiteSaleVisitorTests(TransactionCase):
|
|||
'sale_ok': True,
|
||||
})
|
||||
|
||||
self.website = self.website.with_user(public_user).with_context(website_id=self.website.id)
|
||||
snippet_filter = self.env.ref('website_sale.dynamic_filter_newest_products')
|
||||
website = self.website.with_user(self.public_user)
|
||||
with MockRequest(website.env, website=website):
|
||||
snippet_filter = self.env.ref('website_sale.dynamic_filter_newest_products')
|
||||
res = snippet_filter._prepare_values(limit=16, search_domain=[])
|
||||
|
||||
res = snippet_filter._prepare_values(16, [])
|
||||
res_products = [res_product['_record'] for res_product in res]
|
||||
self.assertIn(product, res_products)
|
||||
|
||||
product.product_tmpl_id.company_id = new_company
|
||||
product.product_tmpl_id.flush_recordset(['company_id'])
|
||||
|
||||
res = snippet_filter._prepare_values(16, [])
|
||||
with MockRequest(website.env, website=website):
|
||||
res = snippet_filter._prepare_values(limit=16, search_domain=[])
|
||||
res_products = [res_product['_record'] for res_product in res]
|
||||
self.assertNotIn(product, res_products)
|
||||
|
||||
|
|
@ -100,20 +96,20 @@ class WebsiteSaleVisitorTests(TransactionCase):
|
|||
snippet_filter = self.env.ref('website_sale.dynamic_filter_latest_viewed_products')
|
||||
|
||||
# BEFORE VISITING THE PRODUCT
|
||||
res = snippet_filter._prepare_values(16, [])
|
||||
res = snippet_filter._prepare_values(limit=16, search_domain=[])
|
||||
self.assertFalse(res)
|
||||
|
||||
# AFTER VISITING THE PRODUCT
|
||||
with MockRequest(self.website.env, website=self.website):
|
||||
self.cookies = self.WebsiteSaleController.products_recently_viewed_update(product.id)
|
||||
with MockRequest(self.website.env, website=self.website, cookies=self.cookies):
|
||||
res = snippet_filter._prepare_values(16, [])
|
||||
cookies = self.WebsiteSaleController.products_recently_viewed_update(product.id)
|
||||
with MockRequest(self.website.env, website=self.website, cookies=cookies):
|
||||
res = snippet_filter._prepare_values(limit=16, search_domain=[])
|
||||
res_products = [res_product['_record'] for res_product in res]
|
||||
self.assertIn(product, res_products)
|
||||
|
||||
# AFTER CHANGING PRODUCT COMPANY
|
||||
product.product_tmpl_id.company_id = new_company
|
||||
product.product_tmpl_id.flush_recordset(['company_id'])
|
||||
with MockRequest(self.website.env, website=self.website, cookies=self.cookies):
|
||||
res = snippet_filter._prepare_values(16, [])
|
||||
with MockRequest(self.website.env, website=self.website, cookies=cookies):
|
||||
res = snippet_filter._prepare_values(limit=16, search_domain=[])
|
||||
self.assertFalse(res)
|
||||
|
|
|
|||
|
|
@ -1,27 +1,46 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import odoo.tests
|
||||
from datetime import datetime, timedelta
|
||||
from freezegun import freeze_time
|
||||
|
||||
from odoo.api import Environment
|
||||
from odoo.tests import tagged
|
||||
from odoo.tools import SQL
|
||||
|
||||
from odoo.addons.base.tests.common import BaseCommon
|
||||
from odoo.addons.http_routing.tests.common import MockRequest
|
||||
|
||||
|
||||
@odoo.tests.common.tagged('post_install', '-at_install')
|
||||
class TestWebsiteSequence(odoo.tests.TransactionCase):
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestWebsiteSequence(BaseCommon):
|
||||
|
||||
def setUp(self):
|
||||
super(TestWebsiteSequence, self).setUp()
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
ProductTemplate = self.env['product.template']
|
||||
cls.website = cls.env.ref('website.default_website')
|
||||
cls.public_user = cls.env.ref('base.public_user')
|
||||
|
||||
ProductTemplate = cls.env['product.template']
|
||||
product_templates = ProductTemplate.search([])
|
||||
# if stock is installed we can't archive since there is orderpoints
|
||||
if hasattr(self.env['product.product'], 'orderpoint_ids'):
|
||||
if 'orderpoint_ids' in cls.env['product.product']:
|
||||
product_templates.mapped('product_variant_ids.orderpoint_ids').write({'active': False})
|
||||
# if pos loyalty is installed we can't archive since there are loyalty rules and rewards
|
||||
if 'loyalty.program' in self.env:
|
||||
programs = self.env['loyalty.program'].search([])
|
||||
if 'loyalty.program' in cls.env:
|
||||
programs = cls.env['loyalty.program'].search([])
|
||||
programs.active = False
|
||||
programs.coupon_ids.unlink()
|
||||
programs.unlink()
|
||||
# The "Service on Timesheet" product cannot be archived nor deleted via ORM
|
||||
if time_product := cls.env.ref('sale_timesheet.time_product', raise_if_not_found=False):
|
||||
product_templates -= time_product.product_tmpl_id
|
||||
cls.env.cr.execute(SQL(
|
||||
'UPDATE product_template SET active = false WHERE id = %s',
|
||||
time_product.product_tmpl_id.id,
|
||||
))
|
||||
product_templates.write({'active': False})
|
||||
self.p1, self.p2, self.p3, self.p4 = ProductTemplate.create([{
|
||||
cls.product_tmpls = cls.p1, cls.p2, cls.p3, cls.p4 = ProductTemplate.create([{
|
||||
'name': 'First Product',
|
||||
'website_sequence': 100,
|
||||
}, {
|
||||
|
|
@ -35,33 +54,48 @@ class TestWebsiteSequence(odoo.tests.TransactionCase):
|
|||
'website_sequence': 250,
|
||||
}])
|
||||
|
||||
self._check_correct_order(self.p1 + self.p2 + self.p3 + self.p4)
|
||||
def get_product_sort_mapping(self, label):
|
||||
context = dict(self.env.context, website_id=self.website.id, lang='en_US')
|
||||
env = Environment(self.env.cr, self.public_user.id, context)
|
||||
with MockRequest(env, website=self.website.with_env(env)) as req:
|
||||
product_sort_mapping = req.env['website']._get_product_sort_mapping()
|
||||
return next(k for k, v in product_sort_mapping if v == label)
|
||||
|
||||
def _search_website_sequence_order(self, order='ASC'):
|
||||
'''Helper method to limit the search only to the setUp products'''
|
||||
return self.env['product.template'].search([
|
||||
], order='website_sequence %s' % (order))
|
||||
def get_sorted_products(self, order, products=None):
|
||||
products = products or self.product_tmpls
|
||||
return products.search(
|
||||
[('id', 'in', products.ids)],
|
||||
order=order,
|
||||
)
|
||||
|
||||
def _check_correct_order(self, products):
|
||||
product_ids = self._search_website_sequence_order().ids
|
||||
self.assertEqual(product_ids, products.ids, "Wrong sequence order")
|
||||
def assertProductOrdering(self, products, order):
|
||||
"""Assert `products` are sorted by `order`.
|
||||
|
||||
:param records products: The products or product templates to check.
|
||||
:param str order: Expect ordering, in the same format as used by `search`.
|
||||
"""
|
||||
expected = self.get_sorted_products(order, products=products)
|
||||
self.assertSequenceEqual(products, expected, f"Products should be ordered on '{order}'")
|
||||
|
||||
def test_01_website_sequence(self):
|
||||
sequence_order = self.get_product_sort_mapping("Featured")
|
||||
self.assertProductOrdering(self.p1 + self.p2 + self.p3 + self.p4, sequence_order)
|
||||
# 100:1, 180:2, 225:3, 250:4
|
||||
self.p2.set_sequence_down()
|
||||
# 100:1, 180:3, 225:2, 250:4
|
||||
self._check_correct_order(self.p1 + self.p3 + self.p2 + self.p4)
|
||||
self.assertProductOrdering(self.p1 + self.p3 + self.p2 + self.p4, sequence_order)
|
||||
self.p4.set_sequence_up()
|
||||
# 100:1, 180:3, 225:4, 250:2
|
||||
self._check_correct_order(self.p1 + self.p3 + self.p4 + self.p2)
|
||||
self.assertProductOrdering(self.p1 + self.p3 + self.p4 + self.p2, sequence_order)
|
||||
self.p2.set_sequence_top()
|
||||
# 95:2, 100:1, 180:3, 225:4
|
||||
self._check_correct_order(self.p2 + self.p1 + self.p3 + self.p4)
|
||||
self.assertProductOrdering(self.p2 + self.p1 + self.p3 + self.p4, sequence_order)
|
||||
self.p1.set_sequence_bottom()
|
||||
# 95:2, 180:3, 225:4, 230:1
|
||||
self._check_correct_order(self.p2 + self.p3 + self.p4 + self.p1)
|
||||
self.assertProductOrdering(self.p2 + self.p3 + self.p4 + self.p1, sequence_order)
|
||||
|
||||
current_sequences = self._search_website_sequence_order().mapped('website_sequence')
|
||||
current_products = self.get_sorted_products(sequence_order)
|
||||
current_sequences = current_products.mapped('website_sequence')
|
||||
self.assertEqual(current_sequences, [95, 180, 225, 230], "Wrong sequence order (2)")
|
||||
|
||||
self.p2.website_sequence = 1
|
||||
|
|
@ -72,5 +106,44 @@ class TestWebsiteSequence(odoo.tests.TransactionCase):
|
|||
new_product = self.env['product.template'].create({
|
||||
'name': 'Last Newly Created Product',
|
||||
})
|
||||
current_products += new_product
|
||||
|
||||
self.assertEqual(self._search_website_sequence_order()[-1], new_product, "new product should be last")
|
||||
self.assertEqual(
|
||||
self.get_sorted_products(sequence_order, current_products)[-1],
|
||||
new_product,
|
||||
"New product should be last",
|
||||
)
|
||||
|
||||
def test_02_newest_arrivals(self):
|
||||
def toggle_publish(products, delta=timedelta(seconds=5)):
|
||||
publish_date = datetime.now()
|
||||
for product in products:
|
||||
publish_date += delta
|
||||
with freeze_time(publish_date):
|
||||
product.website_publish_button()
|
||||
product.flush_recordset() # force computations
|
||||
|
||||
newest_arrival_order = self.get_product_sort_mapping("Newest Arrivals")
|
||||
|
||||
toggle_publish(self.product_tmpls)
|
||||
# Products were published sequentially,
|
||||
# so first product is "oldest" arrival & last product is "newest" arrival
|
||||
target = self.product_tmpls[::-1]
|
||||
self.assertTrue(all(self.product_tmpls.mapped('is_published')))
|
||||
self.assertProductOrdering(target, newest_arrival_order)
|
||||
|
||||
publish_dates = self.product_tmpls.mapped('publish_date')
|
||||
toggle_publish(self.product_tmpls)
|
||||
self.assertFalse(any(self.product_tmpls.mapped('is_published')))
|
||||
self.assertSequenceEqual(
|
||||
self.product_tmpls.mapped('publish_date'),
|
||||
publish_dates,
|
||||
"Unpublishing should not affect publishing date",
|
||||
)
|
||||
|
||||
toggle_publish(self.p2, delta=timedelta(days=1))
|
||||
self.assertEqual(
|
||||
self.get_sorted_products(newest_arrival_order)[0],
|
||||
self.p2,
|
||||
"Most recently published product should appear first",
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue