mirror of
https://github.com/bringout/oca-ocb-sale.git
synced 2026-04-27 06:52:06 +02:00
Initial commit: Sale packages
This commit is contained in:
commit
14e3d26998
6469 changed files with 2479670 additions and 0 deletions
|
|
@ -0,0 +1,26 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import test_customize
|
||||
from . import test_express_checkout_flows
|
||||
from . import test_sale_process
|
||||
from . import test_sitemap
|
||||
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_mail
|
||||
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_reorder_from_portal
|
||||
from . import test_website_sale_snippets
|
||||
from . import test_website_sale_fiscal_position
|
||||
from . import test_website_sale_invoice
|
||||
|
|
@ -0,0 +1,459 @@
|
|||
# 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.tests import tagged
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestUi(HttpCaseWithUserDemo, HttpCaseWithUserPortal):
|
||||
|
||||
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,
|
||||
})
|
||||
|
||||
tax = self.env['account.tax'].create({'name': "Test tax", 'amount': 10})
|
||||
product_template.taxes_id = tax
|
||||
|
||||
product_attribute = self.env['product.attribute'].create({
|
||||
'name': 'Legs',
|
||||
'visibility': 'visible',
|
||||
'sequence': 10,
|
||||
})
|
||||
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,
|
||||
})
|
||||
|
||||
# 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])]
|
||||
}])
|
||||
|
||||
# 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)])
|
||||
|
||||
for ptav in product_template_attribute_values:
|
||||
if ptav.name == "Steel - Test":
|
||||
ptav.price_extra = 0
|
||||
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})
|
||||
|
||||
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_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,
|
||||
})
|
||||
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")
|
||||
|
||||
def test_03_public_tour_shop_dynamic_variants(self):
|
||||
""" 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',
|
||||
})
|
||||
|
||||
# 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,
|
||||
})
|
||||
|
||||
# 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)])
|
||||
|
||||
for ptav in product_template_attribute_values:
|
||||
if ptav.name == "Dynamic Value 1":
|
||||
ptav.price_extra = 10
|
||||
else:
|
||||
# 0 to not bother with the pricelist of the public user
|
||||
ptav.price_extra = 0
|
||||
|
||||
self.start_tour("/", 'tour_shop_dynamic_variants')
|
||||
|
||||
def test_04_portal_tour_deleted_archived_variants(self):
|
||||
"""The goal of this test is to make sure deleted and archived variants
|
||||
are shown as impossible combinations.
|
||||
|
||||
Using "portal" to have various users in the tests.
|
||||
"""
|
||||
|
||||
# create the attribute
|
||||
product_attribute = self.env['product.attribute'].create({
|
||||
'name': "My Attribute",
|
||||
'create_variant': 'always',
|
||||
})
|
||||
|
||||
# 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,
|
||||
})
|
||||
|
||||
# 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[0].price_extra = 10
|
||||
product_template_attribute_values[1].price_extra = 20
|
||||
product_template_attribute_values[2].price_extra = 30
|
||||
|
||||
# archive first combination (first variant)
|
||||
product_template.product_variant_ids[0].active = False
|
||||
# delete second combination (which is now first variant since cache has been cleared)
|
||||
product_template.product_variant_ids[0].unlink()
|
||||
|
||||
self.start_tour("/", 'tour_shop_deleted_archived_variants', login="portal")
|
||||
|
||||
def test_05_demo_tour_no_variant_attribute(self):
|
||||
"""The goal of this test is to make sure attributes no_variant are
|
||||
correctly added to cart.
|
||||
|
||||
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,
|
||||
})
|
||||
|
||||
# create the template
|
||||
product_template = self.env['product.template'].create({
|
||||
'name': 'Test Product 3',
|
||||
'website_published': True,
|
||||
})
|
||||
|
||||
# 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
|
||||
|
||||
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")
|
||||
|
||||
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")
|
||||
|
||||
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
|
||||
attributes only disabled other options if only one is missing or all are selected.
|
||||
|
||||
Using "portal" to have various users in the tests.
|
||||
"""
|
||||
|
||||
attribute_1, attribute_2, attribute_3 = self.env['product.attribute'].create([
|
||||
{
|
||||
'name': 'Size',
|
||||
'create_variant': 'always',
|
||||
},
|
||||
{
|
||||
'name': 'Color',
|
||||
'create_variant': 'always',
|
||||
},
|
||||
{
|
||||
'name': 'Brand',
|
||||
'create_variant': 'always',
|
||||
},
|
||||
])
|
||||
|
||||
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,
|
||||
})
|
||||
|
||||
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
|
||||
|
||||
self.start_tour("/", 'tour_shop_archived_variant_multi', login="portal")
|
||||
|
||||
def test_09_pills_variant(self):
|
||||
"""The goal of this test is to make sure that you can click anywhere on a pill
|
||||
and still trigger a variant change. The radio input be visually hidden.
|
||||
|
||||
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,
|
||||
})
|
||||
|
||||
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.start_tour("/", 'test_09_pills_variant', login="portal")
|
||||
|
||||
def test_10_shop_editor_set_product_ribbon(self):
|
||||
self.start_tour("/", 'shop_editor_set_product_ribbon', login="admin")
|
||||
|
|
@ -0,0 +1,212 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import json
|
||||
from uuid import uuid4
|
||||
from werkzeug import urls
|
||||
|
||||
from odoo import Command
|
||||
from odoo.http import root
|
||||
from odoo.tests import tagged
|
||||
|
||||
from odoo.addons.base.tests.common import HttpCaseWithUserDemo
|
||||
from odoo.addons.website_sale.controllers.main import WebsiteSale as WebsiteSaleController
|
||||
|
||||
|
||||
@tagged('at_install')
|
||||
class TestWebsiteSaleExpressCheckoutFlows(HttpCaseWithUserDemo):
|
||||
""" The goal of this method class is to test the address management on
|
||||
express checkout.
|
||||
"""
|
||||
|
||||
@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.express_checkout_billing_values = {
|
||||
'name': 'Express Checkout Partner',
|
||||
'email': 'express@check.out',
|
||||
'phone': '0000000000',
|
||||
'street': 'ooo',
|
||||
'street2': 'ppp',
|
||||
'city': 'ooo',
|
||||
'zip': '1200',
|
||||
'country': 'US',
|
||||
'state': 'WA',
|
||||
}
|
||||
# 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():
|
||||
if key in ('state', 'country'):
|
||||
value = partner[f'{key}_id'].code
|
||||
else:
|
||||
value = partner[key]
|
||||
self.assertEqual(value, expected, "Shipping value should match")
|
||||
if partner.state_id:
|
||||
self.assertEqual(
|
||||
partner.state_id.country_id,
|
||||
partner.country_id,
|
||||
"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,
|
||||
)
|
||||
|
||||
if not result.ok:
|
||||
return {}
|
||||
|
||||
return result.json().get("result", {})
|
||||
|
||||
def test_express_checkout_public_user(self):
|
||||
""" Test that when using express checkout as a public user, a new partner is created. """
|
||||
session = self.authenticate(None, None)
|
||||
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={
|
||||
'billing_address': dict(self.express_checkout_billing_values)
|
||||
}
|
||||
)
|
||||
|
||||
new_partner = self.sale_order.partner_id
|
||||
self.assertNotEqual(new_partner, self.website.user_id.partner_id)
|
||||
self.assertPartnerShippingValues(
|
||||
new_partner,
|
||||
self.express_checkout_billing_values,
|
||||
)
|
||||
|
||||
def test_express_checkout_registered_user(self):
|
||||
""" Test that when you use express checkout as a registered user and the address sent by the
|
||||
express checkout form exactly matches the one registered 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)
|
||||
|
||||
self._make_json_rpc_request(
|
||||
urls.url_join(
|
||||
self.base_url(), WebsiteSaleController._express_checkout_route
|
||||
), data={
|
||||
'billing_address': {
|
||||
'name': self.user_demo.partner_id.name,
|
||||
'email': self.user_demo.partner_id.email,
|
||||
'phone': self.user_demo.partner_id.phone,
|
||||
'street': self.user_demo.partner_id.street,
|
||||
'street2': self.user_demo.partner_id.street2,
|
||||
'city': self.user_demo.partner_id.city,
|
||||
'zip': self.user_demo.partner_id.zip,
|
||||
'country': self.user_demo.partner_id.country_id.code,
|
||||
'state': self.user_demo.partner_id.state_id.code,
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
self.assertEqual(self.sale_order.partner_id.id, self.user_demo.partner_id.id)
|
||||
self.assertEqual(self.sale_order.partner_invoice_id.id, self.user_demo.partner_id.id)
|
||||
|
||||
def test_express_checkout_registered_user_existing_address(self):
|
||||
""" Test that when you use the express checkout as a registered user and the address sent by
|
||||
the express checkout form exactly matches to one of the addresses linked to this user in
|
||||
odoo, we do not create a new partner and reuse the existing one.
|
||||
"""
|
||||
# Create a child partner for the demo partner
|
||||
child_partner_address = dict(self.express_checkout_billing_values)
|
||||
child_partner_country = self.env['res.country'].search([
|
||||
('code', '=', child_partner_address.pop('country')),
|
||||
], limit=1)
|
||||
child_partner_state = self.env['res.country.state'].search([
|
||||
('code', '=', child_partner_address.pop('state')),
|
||||
('country_id', '=', child_partner_country.id),
|
||||
], limit=1)
|
||||
child_partner = self.env['res.partner'].create(dict(
|
||||
**child_partner_address,
|
||||
parent_id=self.user_demo.partner_id.id,
|
||||
type='invoice',
|
||||
country_id=child_partner_country.id,
|
||||
state_id=child_partner_state.id,
|
||||
))
|
||||
|
||||
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)
|
||||
|
||||
self._make_json_rpc_request(
|
||||
urls.url_join(
|
||||
self.base_url(), WebsiteSaleController._express_checkout_route
|
||||
), data={
|
||||
'billing_address': dict(self.express_checkout_billing_values)
|
||||
}
|
||||
)
|
||||
|
||||
self.assertEqual(self.sale_order.partner_id.id, self.user_demo.partner_id.id)
|
||||
self.assertEqual(self.sale_order.partner_invoice_id.id, child_partner.id)
|
||||
|
||||
def test_express_checkout_registered_user_new_address(self):
|
||||
""" Test that when you use the express checkout as a registered user and the address sent by
|
||||
the express checkout form doesn't match to one of the addresses linked to this user 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)
|
||||
|
||||
self._make_json_rpc_request(
|
||||
urls.url_join(
|
||||
self.base_url(), WebsiteSaleController._express_checkout_route
|
||||
), data={
|
||||
'billing_address': dict(self.express_checkout_billing_values)
|
||||
}
|
||||
)
|
||||
|
||||
self.assertEqual(self.sale_order.partner_id.id, self.user_demo.partner_id.id)
|
||||
new_partner = self.sale_order.partner_invoice_id
|
||||
self.assertNotEqual(new_partner, self.website.user_id.partner_id)
|
||||
self.assertPartnerShippingValues(
|
||||
new_partner,
|
||||
self.express_checkout_billing_values,
|
||||
)
|
||||
|
|
@ -0,0 +1,508 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import logging
|
||||
|
||||
import odoo.tests
|
||||
|
||||
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
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@odoo.tests.tagged('post_install', '-at_install')
|
||||
class TestUi(HttpCaseWithUserDemo):
|
||||
|
||||
def setUp(self):
|
||||
super(TestUi, self).setUp()
|
||||
product_product_7 = self.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({
|
||||
'name': 'Legs',
|
||||
'sequence': 10,
|
||||
})
|
||||
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({
|
||||
'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)],
|
||||
})
|
||||
|
||||
self.product_product_1_product_template = self.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)]
|
||||
|
||||
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',
|
||||
})
|
||||
|
||||
def test_01_admin_shop_tour(self):
|
||||
self.start_tour(self.env['website'].get_client_action_url('/shop'), 'shop', 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):
|
||||
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')
|
||||
tax_group = self.env['account.tax.group'].create({'name': 'Tax 15%'})
|
||||
tax = self.env['account.tax'].create({
|
||||
'name': 'Tax 15%',
|
||||
'amount': 15,
|
||||
'type_tax_use': 'sale',
|
||||
'tax_group_id': tax_group.id
|
||||
})
|
||||
# storage box
|
||||
self.product_product_7 = self.env['product.product'].create({
|
||||
'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',
|
||||
})
|
||||
self.product_product_7.taxes_id = [tax.id]
|
||||
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("/", 'website_sale_tour_2', login="admin")
|
||||
|
||||
def test_05_google_analytics_tracking(self):
|
||||
# Data for google_analytics_view_item
|
||||
attribute = self.env['product.attribute'].create({
|
||||
'name': 'Color',
|
||||
'sequence': 10,
|
||||
'display_type': 'color',
|
||||
'value_ids': [
|
||||
Command.create({
|
||||
'name': 'Red',
|
||||
}),
|
||||
Command.create({
|
||||
'name': 'Pink',
|
||||
}),
|
||||
]
|
||||
})
|
||||
self.env['product.template'].create({
|
||||
'name': 'Colored T-Shirt',
|
||||
'standard_price': 500,
|
||||
'list_price': 750,
|
||||
'detailed_type': 'consu',
|
||||
'website_published': True,
|
||||
'attribute_line_ids': [
|
||||
Command.create({
|
||||
'attribute_id': attribute.id,
|
||||
'value_ids': attribute.value_ids,
|
||||
})
|
||||
]
|
||||
})
|
||||
self.env['website'].browse(1).write({'google_analytics_key': 'G-XXXXXXXXXXX'})
|
||||
self.start_tour("/shop", 'google_analytics_view_item')
|
||||
# Data for google_analytics_add_to_cart
|
||||
self.env['product.template'].create({
|
||||
'name': 'Basic Shirt',
|
||||
'standard_price': 500,
|
||||
'detailed_type': 'consu',
|
||||
'website_published': True
|
||||
})
|
||||
self.start_tour("/shop", 'google_analytics_add_to_cart')
|
||||
|
||||
|
||||
@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,
|
||||
'sale_ok': True,
|
||||
'taxes_id': [tax_10_incl.id]
|
||||
})
|
||||
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.assertEqual(
|
||||
[so.amount_untaxed, so.amount_tax, so.amount_total],
|
||||
[90.91, 9.09, 100.0]
|
||||
)
|
||||
|
||||
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")
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.tests import HttpCase, tagged
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestSitemap(HttpCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestSitemap, self).setUp()
|
||||
|
||||
self.cats = self.env['product.public.category'].create([{
|
||||
'name': 'Level 0',
|
||||
}, {
|
||||
'name': 'Level 1',
|
||||
}, {
|
||||
'name': 'Level 2',
|
||||
}])
|
||||
self.cats[2].parent_id = self.cats[1].id
|
||||
self.cats[1].parent_id = self.cats[0].id
|
||||
|
||||
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.")
|
||||
|
|
@ -0,0 +1,266 @@
|
|||
# -*- 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.exceptions import ValidationError
|
||||
from odoo.tests import HttpCase, tagged
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
ATTACHMENT_DATA = [
|
||||
b"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAEElEQVR4nGKqf3geEAAA//8EGgIyYKYzzgAAAABJRU5ErkJggg==",
|
||||
b"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAEElEQVR4nGKqvvQEEAAA//8EBQI0GMlQsAAAAABJRU5ErkJggg==",
|
||||
b"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAEElEQVR4nGKKLakBBAAA//8ChwFQsvFlAwAAAABJRU5ErkJggg==",
|
||||
b"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAEElEQVR4nGJqkdoACAAA//8CfAFRzSyOUAAAAABJRU5ErkJggg==",
|
||||
b"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAEElEQVR4nGLSfxgICAAA//8CrAFkoLBhpQAAAABJRU5ErkJggg==",
|
||||
]
|
||||
ATTACHMENT_COUNT = 5
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestProductPictureController(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': 'Storage Test Box',
|
||||
'standard_price': 70.0,
|
||||
'list_price': 79.0,
|
||||
'website_published': True,
|
||||
})
|
||||
|
||||
cls.attachments = cls.env['ir.attachment'].create([
|
||||
{
|
||||
'datas': ATTACHMENT_DATA[i],
|
||||
'name': f'image0{i}.gif',
|
||||
'public': True
|
||||
}
|
||||
for i in range(ATTACHMENT_COUNT)])
|
||||
|
||||
def _create_product_images(self):
|
||||
with MockRequest(self.product.env, website=self.website):
|
||||
self.WebsiteSaleController.add_product_images(
|
||||
[{'id': attachment.id} for attachment in self.attachments],
|
||||
self.product.id,
|
||||
self.product.product_tmpl_id.id,
|
||||
)
|
||||
|
||||
def _get_product_image_data(self):
|
||||
return [
|
||||
hasattr(image, 'video_url') and image.video_url or image.image_1920
|
||||
for image in self.product._get_images()
|
||||
]
|
||||
|
||||
def test_bulk_image_upload(self):
|
||||
# Turns attachments to product_images
|
||||
self._create_product_images()
|
||||
|
||||
# Check if the media now exists on the product :
|
||||
for i, image in enumerate(self.product.product_template_image_ids):
|
||||
# Check if all names are now in the product
|
||||
self.assertIn(image.name, self.attachments.mapped('name'))
|
||||
# Check if image datas are the same
|
||||
self.assertEqual(image.image_1920, ATTACHMENT_DATA[i])
|
||||
# Check if exactly ATTACHMENT_COUNT images were saved (no dupes/misses?)
|
||||
self.assertEqual(ATTACHMENT_COUNT, len(self.product.product_template_image_ids))
|
||||
|
||||
def test_image_clear(self):
|
||||
# First create some images
|
||||
self._create_product_images()
|
||||
self.assertEqual(ATTACHMENT_COUNT, len(self.product.product_template_image_ids))
|
||||
|
||||
# Remove all images
|
||||
# (Exception raised if error)
|
||||
with MockRequest(self.product.env, website=self.website):
|
||||
self.WebsiteSaleController.clear_product_images(
|
||||
self.product.id,
|
||||
self.product.product_tmpl_id.id,
|
||||
)
|
||||
# According to the product, there are no variants images.
|
||||
self.assertEqual(0, len(self.product.product_template_image_ids))
|
||||
|
||||
def test_extra_images_with_new_variant(self):
|
||||
# Test that adding images for a variant that is not yet created works
|
||||
product_attribute = self.env['product.attribute'].create({
|
||||
"name": "Test attribute",
|
||||
"create_variant": "dynamic",
|
||||
})
|
||||
product_attribute_values = self.env['product.attribute.value'].create([
|
||||
{
|
||||
"name" : "Test Dynamic 1",
|
||||
"attribute_id": product_attribute.id,
|
||||
"sequence": 1,
|
||||
},
|
||||
{
|
||||
"name" : "Test Dynamic 2",
|
||||
"attribute_id": product_attribute.id,
|
||||
"sequence": 2,
|
||||
}
|
||||
])
|
||||
product_template = self.env['product.template'].create({
|
||||
"name": "test product",
|
||||
"website_published": True,
|
||||
})
|
||||
product_template_attribute_line = self.env['product.template.attribute.line'].create({
|
||||
"attribute_id": product_attribute.id,
|
||||
"product_tmpl_id": product_template.id,
|
||||
"value_ids": product_attribute_values,
|
||||
})
|
||||
self.assertEqual(0, len(product_template.product_variant_ids))
|
||||
with MockRequest(product_template.env, website=self.website):
|
||||
self.WebsiteSaleController.add_product_images(
|
||||
[{'id': self.attachments[0].id}],
|
||||
False,
|
||||
product_template.id,
|
||||
[product_template_attribute_line.product_template_value_ids[0].id],
|
||||
)
|
||||
self.assertEqual(1, len(product_template.product_variant_ids))
|
||||
|
||||
def test_resequence_image_first(self):
|
||||
self._create_product_images()
|
||||
with MockRequest(self.product.env, website=self.website):
|
||||
images = self.product._get_images()
|
||||
i1, i2, i3, i4, i5, i6 = self._get_product_image_data()
|
||||
self.WebsiteSaleController.resequence_product_image(
|
||||
images[2]._name, images[2].id, 'first',
|
||||
)
|
||||
# Trigger the reordering of product.image records based on their sequence.
|
||||
self.env['product.image'].invalidate_model()
|
||||
self.assertListEqual(self._get_product_image_data(), [i3, i1, i2, i4, i5, i6])
|
||||
self.assertEqual(self.product.image_1920, i3)
|
||||
|
||||
def test_resequence_image_left(self):
|
||||
self._create_product_images()
|
||||
with MockRequest(self.product.env, website=self.website):
|
||||
images = self.product._get_images()
|
||||
i1, i2, i3, i4, i5, i6 = self._get_product_image_data()
|
||||
self.WebsiteSaleController.resequence_product_image(
|
||||
images[2]._name, images[2].id, 'left',
|
||||
)
|
||||
self.env['product.image'].invalidate_model()
|
||||
self.assertListEqual(self._get_product_image_data(), [i1, i3, i2, i4, i5, i6])
|
||||
|
||||
def test_resequence_image_right(self):
|
||||
self._create_product_images()
|
||||
with MockRequest(self.product.env, website=self.website):
|
||||
images = self.product._get_images()
|
||||
i1, i2, i3, i4, i5, i6 = self._get_product_image_data()
|
||||
self.WebsiteSaleController.resequence_product_image(
|
||||
images[2]._name, images[2].id, 'right',
|
||||
)
|
||||
self.env['product.image'].invalidate_model()
|
||||
self.assertListEqual(self._get_product_image_data(), [i1, i2, i4, i3, i5, i6])
|
||||
|
||||
def test_resequence_image_last(self):
|
||||
self._create_product_images()
|
||||
with MockRequest(self.product.env, website=self.website):
|
||||
images = self.product._get_images()
|
||||
i1, i2, i3, i4, i5, i6 = self._get_product_image_data()
|
||||
self.WebsiteSaleController.resequence_product_image(
|
||||
images[2]._name, images[2].id, 'last',
|
||||
)
|
||||
self.env['product.image'].invalidate_model()
|
||||
self.assertListEqual(self._get_product_image_data(), [i1, i2, i4, i5, i6, i3])
|
||||
|
||||
def test_resequence_image_first_to_last(self):
|
||||
""" Moving an image from first to last position is an edge case in the code. """
|
||||
self._create_product_images()
|
||||
with MockRequest(self.product.env, website=self.website):
|
||||
images = self.product._get_images()
|
||||
i1, i2, i3, i4, i5, i6 = self._get_product_image_data()
|
||||
self.WebsiteSaleController.resequence_product_image(
|
||||
images[0]._name, images[0].id, 'last',
|
||||
)
|
||||
self.env['product.image'].invalidate_model()
|
||||
self.assertListEqual(self._get_product_image_data(), [i2, i3, i4, i5, i6, i1])
|
||||
self.assertEqual(self.product.image_1920, i2)
|
||||
|
||||
def test_resequence_video_left(self):
|
||||
self._create_product_images()
|
||||
with MockRequest(self.product.env, website=self.website):
|
||||
images = self.product._get_images()
|
||||
images[2].video_url = "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
|
||||
i1, i2, i3, i4, i5, i6 = self._get_product_image_data()
|
||||
self.WebsiteSaleController.resequence_product_image(
|
||||
images[2]._name, images[2].id, 'left',
|
||||
)
|
||||
self.env['product.image'].invalidate_model()
|
||||
self.assertListEqual(self._get_product_image_data(), [i1, i3, i2, i4, i5, i6])
|
||||
|
||||
def test_resequence_video_first(self):
|
||||
""" A video can't be resequenced to first position. """
|
||||
self._create_product_images()
|
||||
with MockRequest(self.product.env, website=self.website):
|
||||
images = self.product._get_images()
|
||||
images[2].video_url = "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
|
||||
i1, i2, i3, i4, i5, i6 = self._get_product_image_data()
|
||||
with self.assertRaises(ValidationError):
|
||||
self.WebsiteSaleController.resequence_product_image(
|
||||
images[2]._name, images[2].id, 'first',
|
||||
)
|
||||
self.env['product.image'].invalidate_model()
|
||||
self.assertListEqual(self._get_product_image_data(), [i1, i2, i3, i4, i5, i6])
|
||||
|
||||
def test_resequence_video_replace_first(self):
|
||||
""" A video can't replace an image that was resequenced away from first position. """
|
||||
self._create_product_images()
|
||||
with MockRequest(self.product.env, website=self.website):
|
||||
images = self.product._get_images()
|
||||
images[1].video_url = "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
|
||||
i1, i2, i3, i4, i5, i6 = self._get_product_image_data()
|
||||
with self.assertRaises(ValidationError):
|
||||
self.WebsiteSaleController.resequence_product_image(
|
||||
images[0]._name, images[0].id, 'right',
|
||||
)
|
||||
self.env['product.image'].invalidate_model()
|
||||
self.assertListEqual(self._get_product_image_data(), [i1, i2, i3, i4, i5, i6])
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestWebsiteSaleEditor(HttpCase):
|
||||
@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
|
||||
])]
|
||||
})
|
||||
|
||||
def test_category_page_and_products_snippet(self):
|
||||
category = self.env['product.public.category'].create({
|
||||
'name': 'Test Category',
|
||||
})
|
||||
self.env['product.template'].create({
|
||||
'name': 'Test Product',
|
||||
'website_published': True,
|
||||
'public_categ_ids': [
|
||||
Command.link(category.id)
|
||||
]
|
||||
})
|
||||
self.env['product.template'].create({
|
||||
'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('/shop', 'category_page_and_products_snippet_use', login=None)
|
||||
|
||||
def test_website_sale_restricted_editor_ui(self):
|
||||
self.env['product.template'].create({
|
||||
'name': 'Test Product',
|
||||
'website_sequence': 0,
|
||||
'website_published': True,
|
||||
})
|
||||
self.start_tour(self.env['website'].get_client_action_url('/shop'), 'website_sale_restricted_editor_ui', login='restricted')
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import logging
|
||||
|
||||
from odoo import Command
|
||||
from odoo.tests import HttpCase, tagged
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestAddToCartSnippet(HttpCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
# Create a dummy payment provider to ensure that the tour has at least one available to it.
|
||||
arch = """
|
||||
<form action="dummy" method="post">
|
||||
<input type="hidden" name="view_id" t-att-value="viewid"/>
|
||||
<input type="hidden" name="user_id" t-att-value="user_id.id"/>
|
||||
</form>
|
||||
"""
|
||||
redirect_form = cls.env['ir.ui.view'].create({
|
||||
'name': "Dummy Redirect Form",
|
||||
'type': 'qweb',
|
||||
'arch': arch,
|
||||
})
|
||||
cls.dummy_provider = cls.env['payment.provider'].create({
|
||||
'name': "Dummy Provider",
|
||||
'code': 'none',
|
||||
'state': 'test',
|
||||
'is_published': True,
|
||||
'allow_tokenization': True,
|
||||
'redirect_form_view_id': redirect_form.id,
|
||||
})
|
||||
|
||||
def test_configure_product(self):
|
||||
# Reset the company country id, which ensure that no country dependant fields are blocking the address form.
|
||||
self.env.company.country_id = self.env.ref('base.us')
|
||||
attribute = self.env['product.attribute'].create({
|
||||
'name': 'Color',
|
||||
'value_ids': [
|
||||
Command.create({
|
||||
'name': 'Red'
|
||||
}),
|
||||
Command.create({
|
||||
'name': 'Pink'
|
||||
})
|
||||
]
|
||||
})
|
||||
self.env['product.template'].create([{
|
||||
'name': 'Product No Variant',
|
||||
'website_published': True
|
||||
}, {
|
||||
'name': 'Product Yes Variant 1',
|
||||
'website_published': True,
|
||||
'attribute_line_ids': [
|
||||
Command.create({
|
||||
'attribute_id': attribute.id,
|
||||
'value_ids': attribute.value_ids
|
||||
})
|
||||
]
|
||||
}, {
|
||||
'name': 'Product Yes Variant 2',
|
||||
'website_published': True,
|
||||
'attribute_line_ids': [
|
||||
Command.create({
|
||||
'attribute_id': attribute.id,
|
||||
'value_ids': attribute.value_ids
|
||||
})
|
||||
]
|
||||
}])
|
||||
admin_partner = self.env.ref('base.user_admin').partner_id
|
||||
admin_partner.write({
|
||||
'street': "rue des Bourlottes, 9",
|
||||
'street2': "",
|
||||
'city': "Ramillies",
|
||||
'zip': 1367,
|
||||
'country_id': self.env.ref('base.be').id
|
||||
})
|
||||
self.env.ref('base.user_admin').country_id = self.env.ref('base.be')
|
||||
self.start_tour("/", 'add_to_cart_snippet_tour', login="admin")
|
||||
|
|
@ -0,0 +1,228 @@
|
|||
# coding: utf-8
|
||||
|
||||
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.fields import Command
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class WebsiteSaleCart(TransactionCaseWithUserPortal):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(WebsiteSaleCart, cls).setUpClass()
|
||||
cls.website = cls.env['website'].browse(1)
|
||||
cls.WebsiteSaleController = WebsiteSale()
|
||||
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({
|
||||
'name': 'Test Product',
|
||||
'sale_ok': True,
|
||||
'website_published': True,
|
||||
})
|
||||
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_unpublished_product(self):
|
||||
# Try to add an unpublished product
|
||||
product = self.env['product.product'].create({
|
||||
'name': 'Test Product',
|
||||
'sale_ok': 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)
|
||||
|
||||
# public but remove sale_ok
|
||||
product.sale_ok = False
|
||||
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)
|
||||
|
||||
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
|
||||
|
||||
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_zero_price_product_rule(self):
|
||||
"""
|
||||
With the `prevent_zero_price_sale` that we have on website, we can't add free products
|
||||
to our cart.
|
||||
There is an exception for certain product types specified by the
|
||||
`_get_product_types_allow_zero_price` method, so this test ensures that it works
|
||||
by mocking that function to return the "service" product type.
|
||||
"""
|
||||
website_prevent_zero_price = self.env['website'].create({
|
||||
'name': 'Prevent zero price sale',
|
||||
'prevent_zero_price_sale': True,
|
||||
})
|
||||
product_consu = self.env['product.product'].create({
|
||||
'name': 'Cannot be zero price',
|
||||
'detailed_type': 'consu',
|
||||
'list_price': 0,
|
||||
'website_published': True,
|
||||
})
|
||||
product_service = self.env['product.product'].create({
|
||||
'name': 'Can be zero price',
|
||||
'detailed_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)
|
||||
|
||||
# service types should not raise a UserError
|
||||
with MockRequest(self.env, website=website_prevent_zero_price):
|
||||
self.WebsiteSaleController.cart_update_json(product_id=product_service.id, add_qty=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()
|
||||
sale_order.access_token = 'test_token'
|
||||
old_amount = sale_order.amount_total
|
||||
self.WebsiteSaleController.cart_update_json(product_id=product.id, add_qty=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)
|
||||
|
||||
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):
|
||||
# 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.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.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.assertEqual(sale_order.order_line, SaleOrderLine)
|
||||
|
||||
def test_unpublished_accessory_product_visibility(self):
|
||||
# Check if unpublished product is shown to public user
|
||||
accessory_product = self.env['product.product'].create({
|
||||
'name': 'Access Product',
|
||||
'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)]
|
||||
})
|
||||
|
||||
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)
|
||||
|
||||
def test_remove_archived_product_line(self):
|
||||
"""If an order has a line containing an archived product,
|
||||
it is removed when opening the order in the cart."""
|
||||
# Arrange
|
||||
user = self.public_user
|
||||
website = self.website.with_user(user)
|
||||
product = self.env['product.product'].create({
|
||||
'name': 'Product',
|
||||
'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()
|
||||
|
||||
# pre-condition: the order contains an active product
|
||||
self.assertRecordValues(order.order_line, [{
|
||||
"product_id": product.id,
|
||||
}])
|
||||
self.assertTrue(product.active)
|
||||
|
||||
# Act: archive the product and open the cart
|
||||
product.active = False
|
||||
self.WebsiteSaleController.cart()
|
||||
|
||||
# Assert: the line has been removed
|
||||
self.assertFalse(order.order_line)
|
||||
|
||||
def test_keep_note_line(self):
|
||||
"""If an order has a line containing a note,
|
||||
it is not removed when opening the order in the cart."""
|
||||
# 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)
|
||||
order.order_line = [
|
||||
Command.create({
|
||||
"name": "Note",
|
||||
"display_type": "line_note",
|
||||
})
|
||||
]
|
||||
|
||||
# pre-condition: the order contains only a note line
|
||||
self.assertRecordValues(order.order_line, [{
|
||||
"display_type": "line_note",
|
||||
}])
|
||||
|
||||
# Act: open the cart
|
||||
self.WebsiteSaleController.cart()
|
||||
|
||||
# Assert: the line is still there
|
||||
self.assertRecordValues(order.order_line, [{
|
||||
"display_type": "line_note",
|
||||
}])
|
||||
|
|
@ -0,0 +1,255 @@
|
|||
# -*- 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 odoo.tests import tagged
|
||||
from odoo.addons.base.tests.common import HttpCaseWithUserPortal
|
||||
from odoo.addons.mail.models.mail_template import MailTemplate
|
||||
|
||||
|
||||
class TestWebsiteSaleCartAbandonedCommon(HttpCaseWithUserPortal):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
now = datetime.utcnow()
|
||||
cls.customer = cls.env['res.partner'].create({
|
||||
'name': 'a',
|
||||
'email': 'a@example.com',
|
||||
})
|
||||
cls.public_partner = cls.env['res.partner'].create({
|
||||
'name': 'public',
|
||||
'email': 'public@example.com',
|
||||
})
|
||||
cls.public_user = cls.env['res.users'].create({
|
||||
'name': 'Foo', 'login': 'foo',
|
||||
'partner_id': cls.public_partner.id,
|
||||
})
|
||||
cls.website0 = cls.env['website'].create({
|
||||
'name': 'web0',
|
||||
'cart_abandoned_delay': 1.0, # 1 hour
|
||||
})
|
||||
cls.website1 = cls.env['website'].create({
|
||||
'name': 'web1',
|
||||
'cart_abandoned_delay': 0.5, # 30 minutes
|
||||
})
|
||||
cls.website2 = cls.env['website'].create({
|
||||
'name': 'web2',
|
||||
'cart_abandoned_delay': 24.0, # 1 day
|
||||
'user_id': cls.public_user.id, # specific public user
|
||||
})
|
||||
product = cls.env['product.product'].create({
|
||||
'name': 'The Product'
|
||||
})
|
||||
add_order_line = [[0, 0, {
|
||||
'name': 'The Product',
|
||||
'product_id': product.id,
|
||||
'product_uom_qty': 1,
|
||||
}]]
|
||||
cls.so0before = cls.env['sale.order'].create({
|
||||
'partner_id': cls.customer.id,
|
||||
'website_id': cls.website0.id,
|
||||
'state': 'draft',
|
||||
'date_order': (now - relativedelta(hours=1)) - relativedelta(minutes=1),
|
||||
'order_line': add_order_line,
|
||||
})
|
||||
cls.so0after = cls.env['sale.order'].create({
|
||||
'partner_id': cls.customer.id,
|
||||
'website_id': cls.website0.id,
|
||||
'state': 'draft',
|
||||
'date_order': (now - relativedelta(hours=1)) + relativedelta(minutes=1),
|
||||
'order_line': add_order_line,
|
||||
})
|
||||
cls.so1before = cls.env['sale.order'].create({
|
||||
'partner_id': cls.customer.id,
|
||||
'website_id': cls.website1.id,
|
||||
'state': 'draft',
|
||||
'date_order': (now - relativedelta(minutes=30)) - relativedelta(minutes=1),
|
||||
'order_line': add_order_line,
|
||||
})
|
||||
cls.so1after = cls.env['sale.order'].create({
|
||||
'partner_id': cls.customer.id,
|
||||
'website_id': cls.website1.id,
|
||||
'state': 'draft',
|
||||
'date_order': (now - relativedelta(minutes=30)) + relativedelta(minutes=1),
|
||||
'order_line': add_order_line,
|
||||
})
|
||||
cls.so2before = cls.env['sale.order'].create({
|
||||
'partner_id': cls.customer.id,
|
||||
'website_id': cls.website2.id,
|
||||
'state': 'draft',
|
||||
'date_order': (now - relativedelta(hours=24)) - relativedelta(minutes=1),
|
||||
'order_line': add_order_line,
|
||||
})
|
||||
cls.so2after = cls.env['sale.order'].create({
|
||||
'partner_id': cls.customer.id,
|
||||
'website_id': cls.website2.id,
|
||||
'state': 'draft',
|
||||
'date_order': (now - relativedelta(hours=24)) + relativedelta(minutes=1),
|
||||
'order_line': add_order_line,
|
||||
})
|
||||
cls.so2before_but_public = cls.env['sale.order'].create({
|
||||
'partner_id': cls.public_partner.id,
|
||||
'website_id': cls.website2.id,
|
||||
'state': 'draft',
|
||||
'date_order': (now - relativedelta(hours=24)) - relativedelta(minutes=1),
|
||||
'order_line': add_order_line,
|
||||
})
|
||||
|
||||
# Must behave like so1before because public partner is not the one of website1
|
||||
cls.so1before_but_other_public = cls.env['sale.order'].create({
|
||||
'partner_id': cls.public_partner.id,
|
||||
'website_id': cls.website1.id,
|
||||
'state': 'draft',
|
||||
'date_order': (now - relativedelta(minutes=30)) - relativedelta(minutes=1),
|
||||
'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']
|
||||
abandoned = SaleOrder.search([('is_abandoned_cart', '=', True)]).ids
|
||||
self.assertTrue(self.so0before.id in abandoned)
|
||||
self.assertTrue(self.so1before.id in abandoned)
|
||||
self.assertTrue(self.so1before_but_other_public.id in abandoned)
|
||||
self.assertTrue(self.so2before.id in abandoned)
|
||||
self.assertFalse(self.so0after.id in abandoned)
|
||||
self.assertFalse(self.so1after.id in abandoned)
|
||||
self.assertFalse(self.so2after.id in abandoned)
|
||||
self.assertFalse(self.so2before_but_public.id in abandoned)
|
||||
|
||||
non_abandoned = SaleOrder.search([('is_abandoned_cart', '=', False)]).ids
|
||||
self.assertFalse(self.so0before.id in non_abandoned)
|
||||
self.assertFalse(self.so1before.id in non_abandoned)
|
||||
self.assertFalse(self.so1before_but_other_public.id in non_abandoned)
|
||||
self.assertFalse(self.so2before.id in non_abandoned)
|
||||
self.assertTrue(self.so0after.id in non_abandoned)
|
||||
self.assertTrue(self.so1after.id in non_abandoned)
|
||||
self.assertTrue(self.so2after.id in non_abandoned)
|
||||
self.assertFalse(self.so2before_but_public.id in abandoned)
|
||||
|
||||
def test_website_sale_abandoned_cart_email(self):
|
||||
"""Make sure the send_abandoned_cart_email method sends the correct emails."""
|
||||
|
||||
website = self.env['website'].get_current_website()
|
||||
website.send_abandoned_cart_email = True
|
||||
|
||||
product = self.env['product.product'].create({
|
||||
'name': 'The Product'
|
||||
})
|
||||
order_line = [[0, 0, {
|
||||
'name': 'The Product',
|
||||
'product_id': product.id,
|
||||
'product_uom_qty': 1,
|
||||
}]]
|
||||
abandoned_sale_order = self.env['sale.order'].create({
|
||||
'partner_id': self.customer.id,
|
||||
'website_id': website.id,
|
||||
'state': 'draft',
|
||||
'date_order': (datetime.utcnow() - relativedelta(hours=website.cart_abandoned_delay)) - relativedelta(minutes=1),
|
||||
'order_line': order_line
|
||||
})
|
||||
self.assertTrue(abandoned_sale_order.is_abandoned_cart)
|
||||
|
||||
self.assertTrue(self.send_mail_patched(abandoned_sale_order.id))
|
||||
|
||||
# Test that no mail is sent if the partner has no email address.
|
||||
self.customer.email = False
|
||||
self.env['sale.order'].create({
|
||||
'partner_id': self.customer.id,
|
||||
'website_id': website.id,
|
||||
'state': 'draft',
|
||||
'date_order': (datetime.utcnow() - relativedelta(hours=website.cart_abandoned_delay)) - relativedelta(
|
||||
minutes=1),
|
||||
'order_line': order_line
|
||||
})
|
||||
self.assertFalse(self.send_mail_patched(abandoned_sale_order.id))
|
||||
|
||||
# Test that no mail is sent if the recovery email of the sale order has already been sent.
|
||||
self.env['sale.order'].create({
|
||||
'partner_id': self.customer.id,
|
||||
'website_id': website.id,
|
||||
'state': 'draft',
|
||||
'date_order': (datetime.utcnow() - relativedelta(hours=website.cart_abandoned_delay)) - relativedelta(
|
||||
minutes=1),
|
||||
'order_line': order_line,
|
||||
'cart_recovery_email_sent': True
|
||||
})
|
||||
self.assertFalse(self.send_mail_patched(abandoned_sale_order.id))
|
||||
|
||||
# Test that no email is sent if the sale order contains product that are free.
|
||||
free_product_template = self.env['product.template'].create({
|
||||
'list_price': 0.0,
|
||||
'name': 'free_product'
|
||||
})
|
||||
free_product_product = free_product_template.product_variant_id
|
||||
order_line = [[0, 0, {
|
||||
'name': 'The Product',
|
||||
'product_id': free_product_product.id,
|
||||
'product_uom_qty': 1,
|
||||
}]]
|
||||
self.env['sale.order'].create({
|
||||
'partner_id': self.customer.id,
|
||||
'website_id': website.id,
|
||||
'state': 'draft',
|
||||
'date_order': (datetime.utcnow() - relativedelta(hours=website.cart_abandoned_delay)) - relativedelta(
|
||||
minutes=1),
|
||||
'order_line': order_line
|
||||
})
|
||||
self.assertFalse(self.send_mail_patched(abandoned_sale_order.id))
|
||||
|
||||
# Test that no email is sent if the sale order has no error in its transaction.
|
||||
abandoned_sale_order = self.env['sale.order'].create({
|
||||
'partner_id': self.customer.id,
|
||||
'website_id': website.id,
|
||||
'state': 'draft',
|
||||
'date_order': (datetime.utcnow() - relativedelta(hours=website.cart_abandoned_delay)) - relativedelta(
|
||||
minutes=1),
|
||||
'order_line': order_line,
|
||||
})
|
||||
transaction = self.env['payment.transaction'].create({
|
||||
'provider_id': 15,
|
||||
'partner_id': self.customer.id,
|
||||
'reference': abandoned_sale_order.name,
|
||||
'amount': abandoned_sale_order.amount_total,
|
||||
'state': 'error',
|
||||
'currency_id': self.env.ref('base.EUR').id,
|
||||
|
||||
})
|
||||
abandoned_sale_order.transaction_ids += transaction
|
||||
self.assertFalse(self.send_mail_patched(abandoned_sale_order.id))
|
||||
|
||||
# Test that if the partner of the abandoned cart made an order ulterior to the abandoned cart create date,
|
||||
# no email is sent.
|
||||
self.env['sale.order'].create({
|
||||
'partner_id': self.customer.id,
|
||||
'website_id': website.id,
|
||||
'state': 'draft',
|
||||
'date_order': (datetime.utcnow() - relativedelta(hours=website.cart_abandoned_delay)) - relativedelta(
|
||||
minutes=1),
|
||||
'order_line': order_line,
|
||||
})
|
||||
self.env['sale.order'].create({
|
||||
'partner_id': self.customer.id,
|
||||
'website_id': website.id,
|
||||
'state': 'draft',
|
||||
'date_order': datetime.utcnow(),
|
||||
'order_line': order_line,
|
||||
})
|
||||
self.assertFalse(self.send_mail_patched(abandoned_sale_order.id))
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.models import Command
|
||||
from odoo.tests.common import tagged
|
||||
|
||||
from odoo.addons.payment.tests.common import PaymentCommon
|
||||
from odoo.addons.website.tools import MockRequest
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class WebsiteSaleCartPayment(PaymentCommon):
|
||||
|
||||
@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({
|
||||
'amount': cls.amount,
|
||||
'currency_id': cls.currency.id,
|
||||
'provider_id': cls.provider.id,
|
||||
'reference': cls.reference,
|
||||
'operation': 'online_redirect',
|
||||
'partner_id': cls.partner.id,
|
||||
})
|
||||
cls.order.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):
|
||||
self.assertEqual(
|
||||
self.website.sale_get_order(),
|
||||
self.order,
|
||||
msg=f"The transaction state '{unpaid_order_tx_state}' should not prevent "
|
||||
f"retrieving the linked order.",
|
||||
)
|
||||
|
||||
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
|
||||
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):
|
||||
self.assertFalse(
|
||||
self.website.sale_get_order(),
|
||||
msg=f"The transaction state '{paid_order_tx_state}' should prevent retrieving "
|
||||
f"the linked order.",
|
||||
)
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
# -*- 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")
|
||||
|
|
@ -0,0 +1,126 @@
|
|||
# -*- 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.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['product.product'].create({
|
||||
'name': 'Acoustic Bloc Screens',
|
||||
'list_price': 2950.0,
|
||||
'website_published': True,
|
||||
})
|
||||
|
||||
self.start_tour("/", 'shop_cart_recovery', login="portal")
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestWebsiteSaleCartRecoveryServer(TransactionCase):
|
||||
|
||||
def setUp(self):
|
||||
res = super(TestWebsiteSaleCartRecoveryServer, self).setUp()
|
||||
|
||||
self.customer = self.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()
|
||||
|
||||
self.website0 = self.env['website'].create({
|
||||
'name': 'web0',
|
||||
'cart_recovery_mail_template_id': self.recovery_template_default.id,
|
||||
})
|
||||
self.website1 = self.env['website'].create({
|
||||
'name': 'web1',
|
||||
'cart_recovery_mail_template_id': self.recovery_template_custom1.id,
|
||||
})
|
||||
self.website2 = self.env['website'].create({
|
||||
'name': 'web2',
|
||||
'cart_recovery_mail_template_id': self.recovery_template_custom2.id,
|
||||
})
|
||||
self.so0 = self.env['sale.order'].create({
|
||||
'partner_id': self.customer.id,
|
||||
'website_id': self.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,
|
||||
'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,
|
||||
'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(
|
||||
self.so1._get_cart_recovery_template(),
|
||||
self.recovery_template_custom1,
|
||||
"We do not return the correct mail template"
|
||||
)
|
||||
self.assertEqual(
|
||||
self.so2._get_cart_recovery_template(),
|
||||
self.recovery_template_custom2,
|
||||
"We do not return the correct mail template"
|
||||
)
|
||||
# Orders that belong to different websites; we should get the default template
|
||||
self.assertEqual(
|
||||
(self.so1 + self.so2)._get_cart_recovery_template(),
|
||||
self.recovery_template_default,
|
||||
"We do not return the correct mail template"
|
||||
)
|
||||
|
||||
def test_cart_recovery_mail_template_send(self):
|
||||
"""The goal of this test is to make sure cart recovery works."""
|
||||
orders = self.so0 + self.so1 + self.so2
|
||||
|
||||
self.assertFalse(
|
||||
any(orders.mapped('cart_recovery_email_sent')),
|
||||
"The recovery mail should not have been sent yet."
|
||||
)
|
||||
self.assertFalse(
|
||||
any(orders.mapped('access_token')),
|
||||
"There should not be an access token yet."
|
||||
)
|
||||
|
||||
orders._cart_recovery_email_send()
|
||||
|
||||
self.assertTrue(
|
||||
all(orders.mapped('cart_recovery_email_sent')),
|
||||
"The recovery mail should have been sent."
|
||||
)
|
||||
self.assertTrue(
|
||||
all(orders.mapped('access_token')),
|
||||
"All tokens should have been generated."
|
||||
)
|
||||
|
||||
sent_mail = {}
|
||||
for order in orders:
|
||||
mail = self.env["mail.mail"].search([
|
||||
('record_name', '=', order['name'])
|
||||
])
|
||||
sent_mail.update({order: mail})
|
||||
|
||||
self.assertTrue(
|
||||
all(len(sent_mail[order]) == 1 for order in orders),
|
||||
"Each cart recovery mail has been sent exactly once."
|
||||
)
|
||||
self.assertTrue(
|
||||
all(order.access_token in sent_mail[order].body for order in orders),
|
||||
"Each mail should contain the access token of the corresponding SO."
|
||||
)
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
# -*- 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,429 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import base64
|
||||
import io
|
||||
|
||||
from PIL import Image
|
||||
from odoo.tests.common import HOST
|
||||
from odoo.tools import config
|
||||
|
||||
import odoo.tests
|
||||
|
||||
|
||||
@odoo.tests.common.tagged('post_install', '-at_install')
|
||||
class TestWebsiteSaleImage(odoo.tests.HttpCase):
|
||||
|
||||
# registry_test_mode = False # uncomment to save the product to test in browser
|
||||
|
||||
def test_01_admin_shop_zoom_tour(self):
|
||||
color_red = '#CD5C5C'
|
||||
name_red = 'Indian Red'
|
||||
|
||||
color_green = '#228B22'
|
||||
name_green = 'Forest Green'
|
||||
|
||||
color_blue = '#4169E1'
|
||||
name_blue = 'Royal Blue'
|
||||
|
||||
# create the color attribute
|
||||
product_attribute = self.env['product.attribute'].create({
|
||||
'name': 'Beautiful Color',
|
||||
'display_type': 'color',
|
||||
})
|
||||
|
||||
# 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())
|
||||
|
||||
# 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())
|
||||
|
||||
# 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())
|
||||
|
||||
# Template Extra Image 1
|
||||
f = io.BytesIO()
|
||||
Image.new('RGB', (124, 147)).save(f, 'GIF')
|
||||
f.seek(0)
|
||||
image_gif = base64.b64encode(f.read())
|
||||
|
||||
# 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())
|
||||
|
||||
# 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())
|
||||
|
||||
# create the template, without creating the variants
|
||||
template = self.env['product.template'].with_context(create_product_product=True).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})],
|
||||
})
|
||||
|
||||
# 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)]
|
||||
}])
|
||||
value_red = line.product_template_value_ids[0]
|
||||
value_green = line.product_template_value_ids[1]
|
||||
|
||||
# set a different price on the variants to differentiate them
|
||||
product_template_attribute_values = self.env['product.template.attribute.value'].search([('product_tmpl_id', '=', template.id)])
|
||||
|
||||
for val in product_template_attribute_values:
|
||||
if val.name == name_red:
|
||||
val.price_extra = 10
|
||||
else:
|
||||
val.price_extra = 20
|
||||
|
||||
# Get RED variant, and set image to blue (will be set on the template
|
||||
# because the template image is empty and there is only one variant)
|
||||
product_red = template._get_variant_for_combination(value_red)
|
||||
product_red.write({
|
||||
'image_1920': blue_image,
|
||||
'product_variant_image_ids': [(0, 0, {'name': 'image 2', 'image_1920': image_bmp})],
|
||||
})
|
||||
|
||||
self.assertEqual(template.image_1920, blue_image)
|
||||
|
||||
# Get the green variant
|
||||
product_green = template._get_variant_for_combination(value_green)
|
||||
product_green.write({
|
||||
'image_1920': green_image,
|
||||
'product_variant_image_ids': [(0, 0, {'name': 'image 3', 'image_1920': image_png})],
|
||||
})
|
||||
|
||||
# now set the red image on the first variant, that works because
|
||||
# template image is not empty anymore and we have a second variant
|
||||
product_red.image_1920 = red_image
|
||||
|
||||
# Verify image_1920 size > 1024 can be zoomed
|
||||
self.assertTrue(template.can_image_1024_be_zoomed)
|
||||
self.assertFalse(template.product_template_image_ids[0].can_image_1024_be_zoomed)
|
||||
self.assertFalse(template.product_template_image_ids[1].can_image_1024_be_zoomed)
|
||||
self.assertFalse(product_red.can_image_1024_be_zoomed)
|
||||
self.assertFalse(product_red.product_variant_image_ids[0].can_image_1024_be_zoomed)
|
||||
self.assertTrue(product_green.can_image_1024_be_zoomed)
|
||||
self.assertTrue(product_green.product_variant_image_ids[0].can_image_1024_be_zoomed)
|
||||
|
||||
# jpeg encoding is changing the color a bit
|
||||
jpeg_blue = (65, 105, 227)
|
||||
jpeg_red = (205, 93, 92)
|
||||
jpeg_green = (34, 139, 34)
|
||||
|
||||
# Verify original size: keep original
|
||||
image = Image.open(io.BytesIO(base64.b64decode(template.image_1920)))
|
||||
self.assertEqual(image.size, (1920, 1080))
|
||||
self.assertEqual(image.getpixel((image.size[0] / 2, image.size[1] / 2)), jpeg_blue, "blue")
|
||||
image = Image.open(io.BytesIO(base64.b64decode(product_red.image_1920)))
|
||||
self.assertEqual(image.size, (800, 500))
|
||||
self.assertEqual(image.getpixel((image.size[0] / 2, image.size[1] / 2)), jpeg_red, "red")
|
||||
image = Image.open(io.BytesIO(base64.b64decode(product_green.image_1920)))
|
||||
self.assertEqual(image.size, (1920, 1080))
|
||||
self.assertEqual(image.getpixel((image.size[0] / 2, image.size[1] / 2)), jpeg_green, "green")
|
||||
|
||||
# Verify 1024 size: keep aspect ratio
|
||||
image = Image.open(io.BytesIO(base64.b64decode(template.image_1024)))
|
||||
self.assertEqual(image.size, (1024, 576))
|
||||
self.assertEqual(image.getpixel((image.size[0] / 2, image.size[1] / 2)), jpeg_blue, "blue")
|
||||
image = Image.open(io.BytesIO(base64.b64decode(product_red.image_1024)))
|
||||
self.assertEqual(image.size, (800, 500))
|
||||
self.assertEqual(image.getpixel((image.size[0] / 2, image.size[1] / 2)), jpeg_red, "red")
|
||||
image = Image.open(io.BytesIO(base64.b64decode(product_green.image_1024)))
|
||||
self.assertEqual(image.size, (1024, 576))
|
||||
self.assertEqual(image.getpixel((image.size[0] / 2, image.size[1] / 2)), jpeg_green, "green")
|
||||
|
||||
# Verify 512 size: keep aspect ratio
|
||||
image = Image.open(io.BytesIO(base64.b64decode(template.image_512)))
|
||||
self.assertEqual(image.size, (512, 288))
|
||||
self.assertEqual(image.getpixel((image.size[0] / 2, image.size[1] / 2)), jpeg_blue, "blue")
|
||||
image = Image.open(io.BytesIO(base64.b64decode(product_red.image_512)))
|
||||
self.assertEqual(image.size, (512, 320))
|
||||
self.assertEqual(image.getpixel((image.size[0] / 2, image.size[1] / 2)), jpeg_red, "red")
|
||||
image = Image.open(io.BytesIO(base64.b64decode(product_green.image_512)))
|
||||
self.assertEqual(image.size, (512, 288))
|
||||
self.assertEqual(image.getpixel((image.size[0] / 2, image.size[1] / 2)), jpeg_green, "green")
|
||||
|
||||
# Verify 256 size: keep aspect ratio
|
||||
image = Image.open(io.BytesIO(base64.b64decode(template.image_256)))
|
||||
self.assertEqual(image.size, (256, 144))
|
||||
self.assertEqual(image.getpixel((image.size[0] / 2, image.size[1] / 2)), jpeg_blue, "blue")
|
||||
image = Image.open(io.BytesIO(base64.b64decode(product_red.image_256)))
|
||||
self.assertEqual(image.size, (256, 160))
|
||||
self.assertEqual(image.getpixel((image.size[0] / 2, image.size[1] / 2)), jpeg_red, "red")
|
||||
image = Image.open(io.BytesIO(base64.b64decode(product_green.image_256)))
|
||||
self.assertEqual(image.size, (256, 144))
|
||||
self.assertEqual(image.getpixel((image.size[0] / 2, image.size[1] / 2)), jpeg_green, "green")
|
||||
|
||||
# Verify 128 size: keep aspect ratio
|
||||
image = Image.open(io.BytesIO(base64.b64decode(template.image_128)))
|
||||
self.assertEqual(image.size, (128, 72))
|
||||
self.assertEqual(image.getpixel((image.size[0] / 2, image.size[1] / 2)), jpeg_blue, "blue")
|
||||
image = Image.open(io.BytesIO(base64.b64decode(product_red.image_128)))
|
||||
self.assertEqual(image.size, (128, 80))
|
||||
self.assertEqual(image.getpixel((image.size[0] / 2, image.size[1] / 2)), jpeg_red, "red")
|
||||
image = Image.open(io.BytesIO(base64.b64decode(product_green.image_128)))
|
||||
self.assertEqual(image.size, (128, 72))
|
||||
self.assertEqual(image.getpixel((image.size[0] / 2, image.size[1] / 2)), jpeg_green, "green")
|
||||
|
||||
# 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.
|
||||
# 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.start_tour("/", 'shop_zoom', login="admin")
|
||||
|
||||
# CASE: unlink move image to fallback if fallback image empty
|
||||
template.image_1920 = False
|
||||
product_red.unlink()
|
||||
self.assertEqual(template.image_1920, red_image)
|
||||
|
||||
# CASE: unlink does nothing special if fallback image already set
|
||||
self.env['product.product'].create({
|
||||
'product_tmpl_id': template.id,
|
||||
'image_1920': green_image,
|
||||
}).unlink()
|
||||
self.assertEqual(template.image_1920, red_image)
|
||||
|
||||
# CASE: display variant image first if set
|
||||
self.assertEqual(product_green._get_images()[0].image_1920, green_image)
|
||||
|
||||
# CASE: display variant fallback after variant o2m, correct fallback
|
||||
# write on the variant field, otherwise it will write on the fallback
|
||||
product_green.image_variant_1920 = False
|
||||
images = product_green._get_images()
|
||||
# images on fields are resized to max 1920
|
||||
image_png = Image.open(io.BytesIO(base64.b64decode(images[1].image_1920)))
|
||||
self.assertEqual(images[0].image_1920, red_image)
|
||||
self.assertEqual(image_png.size, (1268, 1920))
|
||||
self.assertEqual(images[2].image_1920, image_gif)
|
||||
self.assertEqual(images[3].image_1920, image_svg)
|
||||
|
||||
# CASE: When uploading a product variant image
|
||||
# we don't want the default_product_tmpl_id from the context to be applied if we have a product_variant_id set
|
||||
# we want the default_product_tmpl_id from the context to be applied if we don't have a product_variant_id set
|
||||
|
||||
additionnal_context = {'default_product_tmpl_id': template.id}
|
||||
|
||||
product = self.env['product.product'].create({
|
||||
'product_tmpl_id': template.id,
|
||||
})
|
||||
|
||||
product_image = self.env['product.image'].with_context(**additionnal_context).create([{
|
||||
'name': 'Template image',
|
||||
'image_1920': red_image,
|
||||
}, {
|
||||
'name': 'Variant image',
|
||||
'image_1920': blue_image,
|
||||
'product_variant_id': product.id,
|
||||
}])
|
||||
|
||||
template_image = product_image.filtered(lambda i: i.name == 'Template image')
|
||||
variant_image = product_image.filtered(lambda i: i.name == 'Variant image')
|
||||
|
||||
self.assertEqual(template_image.product_tmpl_id.id, template.id)
|
||||
self.assertFalse(template_image.product_variant_id.id)
|
||||
self.assertFalse(variant_image.product_tmpl_id.id)
|
||||
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())
|
||||
|
||||
# create the color attribute
|
||||
product_attribute = self.env['product.attribute'].create({
|
||||
'name': 'Beautiful Color',
|
||||
'display_type': 'color',
|
||||
})
|
||||
|
||||
# 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({
|
||||
'name': 'Test subject',
|
||||
})
|
||||
|
||||
# when there are no variants, the image must be obtained from the template
|
||||
self.assertEqual(template, template._get_image_holder())
|
||||
|
||||
# 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)]
|
||||
}])
|
||||
value_red = line.product_template_value_ids[0]
|
||||
product_red = template._get_variant_for_combination(value_red)
|
||||
product_red.image_variant_1920 = image
|
||||
|
||||
value_green = line.product_template_value_ids[1]
|
||||
product_green = template._get_variant_for_combination(value_green)
|
||||
product_green.image_variant_1920 = image
|
||||
|
||||
# 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()
|
||||
|
||||
# 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())
|
||||
|
||||
template.image_1920 = image
|
||||
|
||||
# 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',
|
||||
})
|
||||
|
||||
# First image (blue) for the template.
|
||||
color_blue = '#4169E1'
|
||||
name_blue = 'Royal Blue'
|
||||
# Red for the variant.
|
||||
color_red = '#CD5C5C'
|
||||
name_red = 'Indian Red'
|
||||
|
||||
# Create the color attribute.
|
||||
self.product_attribute = self.env['product.attribute'].create({
|
||||
'name': 'Beautiful Color',
|
||||
'display_type': 'color',
|
||||
})
|
||||
|
||||
# create the color attribute values
|
||||
self.attr_values = self.env['product.attribute.value'].create([{
|
||||
'name': name_blue,
|
||||
'attribute_id': self.product_attribute.id,
|
||||
'html_color': color_blue,
|
||||
'sequence': 1,
|
||||
}, {
|
||||
'name': name_red,
|
||||
'attribute_id': self.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({
|
||||
'name': 'Test Remove Image',
|
||||
'image_1920': blue_image,
|
||||
})
|
||||
|
||||
@odoo.tests.common.tagged('post_install', '-at_install')
|
||||
class TestRemoveWebsiteSaleImageNoVariant(TestEnvironmentWebsiteSaleImage):
|
||||
def setUp(self):
|
||||
super(TestRemoveWebsiteSaleImageNoVariant, self).setUp()
|
||||
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()
|
||||
# Set the color attribute and values on the template.
|
||||
self.env['product.template.attribute.line'].create([{
|
||||
'attribute_id': self.product_attribute.id,
|
||||
'product_tmpl_id': self.template.id,
|
||||
'value_ids': [(6, 0, self.attr_values.ids)]
|
||||
}])
|
||||
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)
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.tests import tagged
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
from odoo.addons.account_payment.tests.common import AccountPaymentCommon
|
||||
from odoo.addons.sale.tests.common import SaleCommon
|
||||
|
||||
|
||||
@tagged('-at_install', 'post_install')
|
||||
class TestWebsiteSaleInvoice(AccountPaymentCommon, SaleCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
cls.website = cls.env['website'].create({
|
||||
'name': 'Test Website'
|
||||
})
|
||||
|
||||
def test_automatic_invoice_website_id(self):
|
||||
# Set automatic invoice
|
||||
self.env['ir.config_parameter'].sudo().set_param('sale.automatic_invoice', 'True')
|
||||
|
||||
# Create SO on Test Website
|
||||
self.sale_order.website_id = self.website.id
|
||||
|
||||
# Create the payment
|
||||
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()
|
||||
|
||||
self.assertEqual(self.sale_order.website_id.id, self.website.id)
|
||||
self.assertEqual(self.sale_order.invoice_ids.website_id.id, self.website.id)
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
# -*- 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.tests import tagged
|
||||
from odoo.tests.common import HttpCase
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestWebsiteSaleMail(HttpCase):
|
||||
|
||||
def test_01_shop_mail_tour(self):
|
||||
"""The goal of this test is to make sure sending SO by email works."""
|
||||
self.env['product.product'].create({
|
||||
'name': 'Acoustic Bloc Screens',
|
||||
'list_price': 2950.0,
|
||||
'website_published': True,
|
||||
})
|
||||
self.env['res.partner'].create({
|
||||
'name': 'Azure Interior',
|
||||
'email': 'azure.Interior24@example.com',
|
||||
})
|
||||
|
||||
# we override unlink because we don't want the email to be auto deleted
|
||||
MailMail = odoo.addons.mail.models.mail_mail.MailMail
|
||||
# as we check some link content, avoid mobile doing its link management
|
||||
self.env['ir.config_parameter'].sudo().set_param('mail_mobile.disable_redirect_firebase_dynamic_link', True)
|
||||
|
||||
main_website = self.env.ref('website.default_website')
|
||||
other_websites = self.env['website'].search([]) - main_website
|
||||
|
||||
# We change the domain of the website to test that the email that
|
||||
# will be sent uses the correct domain for its links.
|
||||
main_website.domain = "my-test-domain.com"
|
||||
for w in other_websites:
|
||||
w.domain = f'domain-not-used-{w.id}.fr'
|
||||
with patch.object(MailMail, 'unlink', lambda self: None):
|
||||
start_time = fields.Datetime.now()
|
||||
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)
|
||||
self.assertTrue(new_mail)
|
||||
self.assertIn('Your', new_mail.body_html)
|
||||
self.assertIn('order', new_mail.body_html)
|
||||
|
|
@ -0,0 +1,726 @@
|
|||
# -*- 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 odoo.fields import Command
|
||||
from odoo.tests import tagged, TransactionCase
|
||||
|
||||
from odoo.addons.base.tests.common import TransactionCaseWithUserDemo, HttpCaseWithUserPortal
|
||||
from odoo.addons.website.tools import MockRequest
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
r''' /!\/!\
|
||||
Calling `get_pricelist_available` after setting `property_product_pricelist` on
|
||||
a partner will not work as expected. That field will change the output of
|
||||
`get_pricelist_available` but modifying it will not invalidate the cache.
|
||||
Thus, tests should not do:
|
||||
|
||||
self.env.user.partner_id.property_product_pricelist = my_pricelist
|
||||
pls = self.get_pricelist_available()
|
||||
self.assertEqual(...)
|
||||
self.env.user.partner_id.property_product_pricelist = another_pricelist
|
||||
pls = self.get_pricelist_available()
|
||||
self.assertEqual(...)
|
||||
|
||||
as `_get_pl_partner_order` cache won't be invalidate between the calls, output
|
||||
won't be the one expected and tests will actually not test anything.
|
||||
Try to keep one call to `get_pricelist_available` by test method.
|
||||
'''
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestWebsitePriceList(TransactionCase):
|
||||
|
||||
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
|
||||
|
||||
(self.env['product.pricelist'].search([]) - self.env.ref('product.list0')).write({'website_id': False, 'active': False})
|
||||
self.benelux = self.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)]
|
||||
})
|
||||
self.list_benelux = self.env['product.pricelist'].create({
|
||||
'name': 'Benelux',
|
||||
'selectable': True,
|
||||
'website_id': self.website.id,
|
||||
'country_group_ids': [(4, self.benelux.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,
|
||||
})
|
||||
|
||||
|
||||
self.list_christmas = self.env['product.pricelist'].create({
|
||||
'name': 'Christmas',
|
||||
'selectable': False,
|
||||
'website_id': self.website.id,
|
||||
'country_group_ids': [(4, self.env.ref('base.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,
|
||||
})
|
||||
|
||||
list_europe = self.env['product.pricelist'].create({
|
||||
'name': 'EUR',
|
||||
'selectable': True,
|
||||
'website_id': self.website.id,
|
||||
'country_group_ids': [(4, self.env.ref('base.europe').id)],
|
||||
'sequence': 3,
|
||||
'currency_id': self.env.ref('base.EUR').id,
|
||||
})
|
||||
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({
|
||||
'name': 'Canada',
|
||||
'country_ids': [(6, 0, [self.ref('base.ca')])]
|
||||
})
|
||||
self.env['product.pricelist'].create({
|
||||
'name': 'Canada',
|
||||
'selectable': True,
|
||||
'website_id': self.website.id,
|
||||
'country_group_ids': [(6, 0, [ca_group.id])],
|
||||
'sequence': 10
|
||||
})
|
||||
self.args = {
|
||||
'show': False,
|
||||
'current_pl': False,
|
||||
}
|
||||
patcher = patch('odoo.addons.website_sale.models.website.Website.get_pricelist_available', wraps=self._get_pricelist_available)
|
||||
self.startPatcher(patcher)
|
||||
|
||||
# Mock nedded because request.session doesn't exist during test
|
||||
def _get_pricelist_available(self, show_visible=False):
|
||||
return self.get_pl(self.args.get('show'), self.args.get('current_pl'), self.args.get('country'))
|
||||
|
||||
def get_pl(self, show_visible, current_pl_id, country_code):
|
||||
self.website.invalidate_recordset(['pricelist_ids'])
|
||||
pl_ids = self.website._get_pl_partner_order(
|
||||
country_code,
|
||||
show_visible,
|
||||
current_pl_id=current_pl_id,
|
||||
website_pricelist_ids=tuple(self.website.pricelist_ids.ids),
|
||||
)
|
||||
return self.env['product.pricelist'].browse(pl_ids)
|
||||
|
||||
def test_get_pricelist_available_show(self):
|
||||
show = True
|
||||
current_pl = False
|
||||
|
||||
country_list = {
|
||||
False: ['Public Pricelist', 'EUR', 'Benelux', 'Canada'],
|
||||
'BE': ['EUR', 'Benelux'],
|
||||
'IT': ['EUR'],
|
||||
'CA': ['Canada'],
|
||||
'US': ['Public Pricelist', 'EUR', 'Benelux', 'Canada']
|
||||
}
|
||||
for country, result in country_list.items():
|
||||
pls = self.get_pl(show, current_pl, country)
|
||||
self.assertEqual(len(set(pls.mapped('name')) & set(result)), len(pls), 'Test failed for %s (%s %s vs %s %s)'
|
||||
% (country, len(pls), pls.mapped('name'), len(result), result))
|
||||
|
||||
def test_get_pricelist_available_not_show(self):
|
||||
show = False
|
||||
current_pl = False
|
||||
|
||||
country_list = {
|
||||
False: ['Public Pricelist', 'EUR', 'Benelux', 'Christmas', 'Canada'],
|
||||
'BE': ['EUR', 'Benelux', 'Christmas'],
|
||||
'IT': ['EUR', 'Christmas'],
|
||||
'US': ['Public Pricelist', 'EUR', 'Benelux', 'Christmas', 'Canada'],
|
||||
'CA': ['Canada']
|
||||
}
|
||||
|
||||
for country, result in country_list.items():
|
||||
pls = self.get_pl(show, current_pl, country)
|
||||
self.assertEqual(len(set(pls.mapped('name')) & set(result)), len(pls), 'Test failed for %s (%s %s vs %s %s)'
|
||||
% (country, len(pls), pls.mapped('name'), len(result), result))
|
||||
|
||||
def test_get_pricelist_available_promocode(self):
|
||||
christmas_pl = self.list_christmas.id
|
||||
|
||||
country_list = {
|
||||
False: True,
|
||||
'BE': True,
|
||||
'IT': True,
|
||||
'US': True,
|
||||
'CA': False
|
||||
}
|
||||
|
||||
for country, result in country_list.items():
|
||||
self.args['country'] = country
|
||||
# mock patch method could not pass env context
|
||||
available = self.website.is_pricelist_available(christmas_pl)
|
||||
if result:
|
||||
self.assertTrue(available, 'AssertTrue failed for %s' % country)
|
||||
else:
|
||||
self.assertFalse(available, 'AssertFalse failed for %s' % country)
|
||||
|
||||
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
|
||||
current_pl = False
|
||||
|
||||
country_list = {
|
||||
False: ['Public Pricelist', 'EUR', 'Benelux', 'Canada'],
|
||||
'BE': ['EUR', 'Benelux'],
|
||||
'IT': ['EUR'],
|
||||
'CA': ['EUR', 'Canada'],
|
||||
'US': ['Public Pricelist', 'EUR', 'Benelux', 'Canada']
|
||||
}
|
||||
for country, result in country_list.items():
|
||||
pls = self.get_pl(show, current_pl, country)
|
||||
self.assertEqual(len(set(pls.mapped('name')) & set(result)), len(pls), 'Test failed for %s (%s %s vs %s %s)'
|
||||
% (country, len(pls), pls.mapped('name'), len(result), result))
|
||||
|
||||
def test_pricelist_combination(self):
|
||||
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, {
|
||||
'applied_on': '1_product',
|
||||
'product_tmpl_id': product.product_tmpl_id.id,
|
||||
'min_quantity': 500,
|
||||
'compute_price': 'percentage',
|
||||
'percent_price': 63,
|
||||
})]
|
||||
})
|
||||
promo_pricelist = self.env['product.pricelist'].create({
|
||||
'name': 'Super Pricelist',
|
||||
'discount_policy': 'without_discount',
|
||||
'item_ids': [(0, 0, {
|
||||
'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
|
||||
})]
|
||||
})
|
||||
so = self.env['sale.order'].create({
|
||||
'partner_id': self.env.user.partner_id.id,
|
||||
'order_line': [(0, 0, {
|
||||
'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,
|
||||
})]
|
||||
})
|
||||
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')
|
||||
self.assertEqual(sol.price_total, 13875)
|
||||
|
||||
def test_pricelist_with_no_list_price(self):
|
||||
product = self.env['product.product'].create({
|
||||
'name': 'Super Product',
|
||||
'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,
|
||||
})]
|
||||
})
|
||||
so = self.env['sale.order'].create({
|
||||
'partner_id': self.env.user.partner_id.id,
|
||||
'order_line': [(0, 0, {
|
||||
'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,
|
||||
})]
|
||||
})
|
||||
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)
|
||||
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.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.
|
||||
"""
|
||||
pricelist = self.env['product.pricelist'].create({
|
||||
'name': 'Pricelist base on cost',
|
||||
'item_ids': [Command.create({
|
||||
'base': 'standard_price',
|
||||
'compute_price': 'percentage',
|
||||
'percent_price': 10,
|
||||
})]
|
||||
})
|
||||
|
||||
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})
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
def test_pricelist_item_validity_period(self):
|
||||
""" Test that if a cart was created before a validity period,
|
||||
the correct prices will still apply.
|
||||
"""
|
||||
today = datetime.today()
|
||||
tomorrow = today + timedelta(days=1)
|
||||
pricelist = self.env['product.pricelist'].create({
|
||||
'name': 'Pricelist with validity period',
|
||||
'item_ids': [Command.create({
|
||||
'compute_price': 'formula',
|
||||
'base': 'list_price',
|
||||
'price_discount': 20,
|
||||
'date_start': tomorrow,
|
||||
})]
|
||||
})
|
||||
product = self.env['product.product'].create({
|
||||
'name': 'Super Product',
|
||||
'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,
|
||||
'pricelist_id': pricelist.id,
|
||||
'order_line': [(0, 0, {
|
||||
'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,
|
||||
})],
|
||||
'website_id': current_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)
|
||||
self.assertEqual(sol.price_unit, 80.0, 'Reduction should be applied')
|
||||
self.assertEqual(sol.price_total, 160)
|
||||
|
||||
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():
|
||||
return self.env['website'].browse(website_id)
|
||||
patcher = patch('odoo.addons.website.models.ir_http.get_request_website', wraps=get_request_website)
|
||||
self.startPatcher(patcher)
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestWebsitePriceListAvailable(TransactionCase):
|
||||
def setUp(self):
|
||||
super(TestWebsitePriceListAvailable, self).setUp()
|
||||
Pricelist = self.env['product.pricelist']
|
||||
Website = self.env['website']
|
||||
|
||||
# Set up 2 websites
|
||||
self.website = Website.browse(1)
|
||||
self.website2 = Website.create({'name': 'Website 2'})
|
||||
|
||||
# Remove existing pricelists and create new ones
|
||||
existing_pricelists = Pricelist.search([])
|
||||
self.backend_pl = Pricelist.create({
|
||||
'name': 'Backend Pricelist',
|
||||
'website_id': False,
|
||||
})
|
||||
self.generic_pl_select = Pricelist.create({
|
||||
'name': 'Generic Selectable Pricelist',
|
||||
'selectable': True,
|
||||
'website_id': False,
|
||||
})
|
||||
self.generic_pl_code = Pricelist.create({
|
||||
'name': 'Generic Code Pricelist',
|
||||
'code': 'GENERICCODE',
|
||||
'website_id': False,
|
||||
})
|
||||
self.generic_pl_code_select = Pricelist.create({
|
||||
'name': 'Generic Code Selectable Pricelist',
|
||||
'code': 'GENERICCODESELECT',
|
||||
'selectable': True,
|
||||
'website_id': False,
|
||||
})
|
||||
self.w1_pl = Pricelist.create({
|
||||
'name': 'Website 1 Pricelist',
|
||||
'website_id': self.website.id,
|
||||
})
|
||||
self.w1_pl_select = Pricelist.create({
|
||||
'name': 'Website 1 Pricelist Selectable',
|
||||
'website_id': self.website.id,
|
||||
'selectable': True,
|
||||
})
|
||||
self.w1_pl_code_select = Pricelist.create({
|
||||
'name': 'Website 1 Pricelist Code Selectable',
|
||||
'website_id': self.website.id,
|
||||
'code': 'W1CODESELECT',
|
||||
'selectable': True,
|
||||
})
|
||||
self.w1_pl_code = Pricelist.create({
|
||||
'name': 'Website 1 Pricelist Code',
|
||||
'website_id': self.website.id,
|
||||
'code': 'W1CODE',
|
||||
})
|
||||
self.w2_pl = Pricelist.create({
|
||||
'name': 'Website 2 Pricelist',
|
||||
'website_id': self.website2.id,
|
||||
})
|
||||
existing_pricelists.write({'active': False})
|
||||
|
||||
self.website = self.env['website'].browse(1)
|
||||
|
||||
simulate_frontend_context(self)
|
||||
|
||||
def test_get_pricelist_available(self):
|
||||
# all_pl = self.backend_pl + self.generic_pl_select + self.generic_pl_code + self.generic_pl_code_select + self.w1_pl + self.w1_pl_select + self.w1_pl_code + self.w1_pl_code_select + self.w2_pl
|
||||
|
||||
# Test get all available pricelists
|
||||
pls_to_return = self.generic_pl_select + self.generic_pl_code + self.generic_pl_code_select + self.w1_pl + self.w1_pl_select + self.w1_pl_code + self.w1_pl_code_select
|
||||
pls = self.website.get_pricelist_available()
|
||||
self.assertEqual(pls, pls_to_return, "Every pricelist having the correct website_id set or (no website_id but a code or selectable) should be returned")
|
||||
|
||||
# Test get all available and visible pricelists
|
||||
pls_to_return = self.generic_pl_select + self.generic_pl_code_select + self.w1_pl_select + self.w1_pl_code_select
|
||||
pls = self.website.get_pricelist_available(show_visible=True)
|
||||
self.assertEqual(pls, pls_to_return, "Only selectable pricelists website compliant (website_id False or current website) should be returned")
|
||||
|
||||
def test_property_product_pricelist_for_inactive_partner(self):
|
||||
# `_get_partner_pricelist_multi` should consider inactive users when searching for pricelists.
|
||||
# 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')
|
||||
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`")
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestWebsitePriceListAvailableGeoIP(TestWebsitePriceListAvailable):
|
||||
def setUp(self):
|
||||
super(TestWebsitePriceListAvailableGeoIP, self).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()
|
||||
|
||||
# set different country groups on pricelists
|
||||
c_EUR = self.env.ref('base.europe')
|
||||
c_BENELUX = self.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)]
|
||||
})
|
||||
|
||||
self.BE = self.env.ref('base.be')
|
||||
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])]})
|
||||
|
||||
(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])]})
|
||||
|
||||
# pricelist | selectable | website | code | country group |
|
||||
# ----------------------------------------------------------------------|
|
||||
# backend_pl | | | | BE |
|
||||
# generic_pl_select | V | | | BE |
|
||||
# generic_pl_code | | | V | BE |
|
||||
# generic_pl_code_select | V | | V | BENELUX |
|
||||
# w1_pl | | 1 | | BENELUX |
|
||||
# w1_pl_select | V | 1 | | BE |
|
||||
# w1_pl_code_select | V | 1 | V | NL |
|
||||
# w1_pl_code | | 1 | V | EUR |
|
||||
# w2_pl | | 2 | | BENELUX |
|
||||
|
||||
# available pl for website 1 for GeoIP BE (anything except website 2, backend and NL)
|
||||
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)
|
||||
|
||||
# property_product_pricelist will also be returned in the available pricelists
|
||||
self.website1_be_pl += self.env.user.partner_id.property_product_pricelist
|
||||
|
||||
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, 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
|
||||
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)
|
||||
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()
|
||||
self.assertEqual(pls, self.website1_be_pl, "Only pricelists for BE and accessible on website should be returned, but not the partner pricelist as it is website compliant but not GeoIP compliant.")
|
||||
|
||||
def test_get_pricelist_available_geoip4(self):
|
||||
# Test get all available with geoip and visible pricelists + promo pl
|
||||
pls_to_return = self.generic_pl_select + self.w1_pl_select + self.generic_pl_code_select
|
||||
# property_product_pricelist will also be returned in the available pricelists
|
||||
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):
|
||||
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")
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestWebsitePriceListHttp(HttpCaseWithUserPortal):
|
||||
def test_get_pricelist_available_multi_company(self):
|
||||
''' Test that the `property_product_pricelist` of `res.partner` is not
|
||||
computed as SUPERUSER_ID.
|
||||
Indeed, `property_product_pricelist` is a _compute that ends up
|
||||
doing a search on `product.pricelist` that woule bypass the
|
||||
pricelist multi-company `ir.rule`. Then it would return pricelists
|
||||
from another company and the code would raise an access error when
|
||||
reading that `property_product_pricelist`.
|
||||
'''
|
||||
test_company = self.env['res.company'].create({'name': 'Test Company'})
|
||||
test_company.flush_recordset()
|
||||
self.env['product.pricelist'].create({
|
||||
'name': 'Backend Pricelist For "Test Company"',
|
||||
'website_id': False,
|
||||
'company_id': test_company.id,
|
||||
'sequence': 1,
|
||||
})
|
||||
|
||||
self.authenticate('portal', 'portal')
|
||||
r = self.url_open('/shop')
|
||||
self.assertEqual(r.status_code, 200, "The page should not raise an access error because of reading pricelists from other companies")
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestWebsitePriceListMultiCompany(TransactionCaseWithUserDemo):
|
||||
def setUp(self):
|
||||
''' Create a basic multi-company pricelist environment:
|
||||
- Set up 2 companies with their own company-restricted pricelist each.
|
||||
- Add demo user in those 2 companies
|
||||
- For each company, add that company pricelist to the demo user partner.
|
||||
- Set website's company to company 2
|
||||
- Demo user will still be in company 1
|
||||
'''
|
||||
super(TestWebsitePriceListMultiCompany, self).setUp()
|
||||
|
||||
self.demo_user = self.user_demo
|
||||
|
||||
# Create and add demo user to 2 companies
|
||||
self.company1 = self.demo_user.company_id
|
||||
self.company2 = self.env['res.company'].create({'name': 'Test Company'})
|
||||
self.demo_user.company_ids += self.company2
|
||||
# Set company2 as current company for demo user
|
||||
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,
|
||||
})
|
||||
|
||||
# Create a company pricelist for each company and set it to demo user
|
||||
self.c1_pl = self.env['product.pricelist'].create({
|
||||
'name': 'Company 1 Pricelist',
|
||||
'company_id': self.company1.id,
|
||||
# The `website_id` field will default to the company's website,
|
||||
# in this case `self.website2`.
|
||||
})
|
||||
self.c2_pl = self.env['product.pricelist'].create({
|
||||
'name': 'Company 2 Pricelist',
|
||||
'company_id': self.company2.id,
|
||||
'website_id': False,
|
||||
})
|
||||
self.demo_user.partner_id.with_company(self.company1.id).property_product_pricelist = self.c1_pl
|
||||
self.demo_user.partner_id.with_company(self.company2.id).property_product_pricelist = self.c2_pl
|
||||
|
||||
# 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
|
||||
|
||||
def test_property_product_pricelist_multi_company(self):
|
||||
''' Test that the `property_product_pricelist` of `res.partner` is read
|
||||
for the company of the website and not the current user company.
|
||||
This is the case if the user visit a website for which the company
|
||||
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
|
||||
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
|
||||
# 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)
|
||||
demo_pl = partner.property_product_pricelist
|
||||
self.assertEqual(demo_pl, self.c2_pl)
|
||||
|
||||
# Second thing to check: It should not error in read right access error
|
||||
# Indeed, the ir.rule for pricelists rights about company should allow to
|
||||
# also read a pricelist from another company if that company is the one
|
||||
# from the currently visited website.
|
||||
self.env(user=self.user_demo)['product.pricelist'].browse(demo_pl.id).name
|
||||
|
||||
def test_archive_pricelist_1(self):
|
||||
''' Test that when a pricelist is archived, the check that verify that
|
||||
all website have at least one pricelist have access to all
|
||||
pricelists (considering all companies).
|
||||
'''
|
||||
|
||||
self.c2_pl.website_id = self.website
|
||||
c2_pl2 = self.c2_pl.copy({'name': 'Copy of c2_pl'})
|
||||
self.env['product.pricelist'].search([
|
||||
('id', 'not in', (self.c2_pl + self.c1_pl + c2_pl2).ids)
|
||||
]).write({'active': False})
|
||||
|
||||
# ---------------- PRICELISTS ----------------
|
||||
# name | website_id | company_id |
|
||||
# --------------------------------------------
|
||||
# self.c1_pl | self.website2 | self.company1 |
|
||||
# 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')
|
||||
|
||||
# The test is here: while having access only to self.company2 records,
|
||||
# archive should not raise an error
|
||||
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):
|
||||
|
||||
def test_update_pricelist_user_session(self):
|
||||
"""
|
||||
The objective is to verify that the pricelist
|
||||
changes correctly according to the user.
|
||||
"""
|
||||
website = self.env.ref('website.default_website')
|
||||
test_user = self.env['res.users'].create({
|
||||
'name': 'Toto',
|
||||
'login': 'toto',
|
||||
'password': 'long_enough_password',
|
||||
})
|
||||
# We need at least two selectable pricelists to display the dropdown
|
||||
self.env['product.pricelist'].create([{
|
||||
'name': 'Public Pricelist 1',
|
||||
'selectable': True
|
||||
}, {
|
||||
'name': 'Public Pricelist 2',
|
||||
'selectable': True
|
||||
}])
|
||||
user_pricelist = self.env['product.pricelist'].create({
|
||||
'name': 'User Pricelist',
|
||||
'website_id': website.id,
|
||||
'code': 'User_pricelist',
|
||||
})
|
||||
test_user.partner_id.property_product_pricelist = user_pricelist
|
||||
self.start_tour("/shop", 'website_sale_shop_pricelist_tour', login="")
|
||||
|
|
@ -0,0 +1,144 @@
|
|||
# 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)
|
||||
|
|
@ -0,0 +1,317 @@
|
|||
# -*- 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.tests import tagged
|
||||
from odoo.addons.website.tools import MockRequest
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install', 'product_attribute')
|
||||
class TestWebsiteSaleProductAttributeValueConfig(TestSaleProductAttributeValueCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls, chart_template_ref=None):
|
||||
super().setUpClass(chart_template_ref=chart_template_ref)
|
||||
# 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)
|
||||
|
||||
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 product with 15% tax.
|
||||
product_template = self.computer.with_context(website_id=website.id)
|
||||
product_template.write({
|
||||
'taxes_id': [Command.set(self.company_data['default_tax_sale'].ids)],
|
||||
'company_id': self.env.company.id,
|
||||
})
|
||||
|
||||
tax_ratio = 1.15
|
||||
discount_rate = 0.9
|
||||
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)
|
||||
|
||||
# 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
|
||||
|
||||
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)
|
||||
|
||||
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,
|
||||
'taxes_id': [Command.set(self.company_data['default_tax_sale'].ids)],
|
||||
'company_id': self.env.company.id,
|
||||
})
|
||||
|
||||
# Setup pricelist: make sure the pricelist has a 10% discount
|
||||
pricelist = self.env['product.pricelist'].create({
|
||||
'name': "test_get_combination_info",
|
||||
'company_id': self.env.company.id,
|
||||
'item_ids': [Command.create({
|
||||
'applied_on': "1_product",
|
||||
'base': "list_price",
|
||||
'compute_price': "fixed",
|
||||
'fixed_price': 500,
|
||||
'product_tmpl_id': product.id,
|
||||
})],
|
||||
})
|
||||
|
||||
# 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.
|
||||
computer_ssd_attribute_lines = self.env['product.template.attribute.line'].create({
|
||||
'product_tmpl_id': product.id,
|
||||
'attribute_id': self.ssd_attribute.id,
|
||||
'value_ids': [(6, 0, [self.ssd_256.id])],
|
||||
})
|
||||
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
|
||||
|
||||
combination_info = product._get_combination_info(pricelist=pricelist)
|
||||
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({
|
||||
'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,
|
||||
})],
|
||||
})
|
||||
|
||||
# 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.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
|
||||
|
||||
# Reset / Safety check
|
||||
self.env.user.partner_id.country_id = None
|
||||
combination_info = product._get_combination_info(pricelist=pricelist)
|
||||
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.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)
|
||||
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)")
|
||||
|
||||
|
||||
@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,
|
||||
})
|
||||
|
||||
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,
|
||||
})
|
||||
|
||||
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%)")
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
# -*- 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
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestWebsiteSaleReorderFromPortal(HttpCase):
|
||||
@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([
|
||||
{
|
||||
'name': 'Reorder Product 1',
|
||||
'sale_ok': True,
|
||||
'website_published': True,
|
||||
},
|
||||
{
|
||||
'name': 'Reorder Product 2',
|
||||
'sale_ok': True,
|
||||
'website_published': True,
|
||||
},
|
||||
])
|
||||
no_variant_attribute = self.env['product.attribute'].create({
|
||||
'name': 'Size',
|
||||
'create_variant': 'no_variant',
|
||||
'value_ids': [
|
||||
Command.create({'name': 'S'}),
|
||||
Command.create({'name': 'M'}),
|
||||
Command.create({'name': 'L', 'is_custom': True}),
|
||||
]
|
||||
})
|
||||
s, _m, l = no_variant_attribute.value_ids
|
||||
no_variant_template = self.env['product.template'].create({
|
||||
'name': 'Sofa',
|
||||
'attribute_line_ids': [Command.create({
|
||||
'attribute_id': no_variant_attribute.id,
|
||||
'value_ids': [Command.set(no_variant_attribute.value_ids.ids)],
|
||||
})]
|
||||
})
|
||||
ptavs = no_variant_template.attribute_line_ids.product_template_value_ids
|
||||
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({
|
||||
'partner_id': user_admin.partner_id.id,
|
||||
'state': 'sale',
|
||||
'order_line': [
|
||||
Command.create({
|
||||
'product_id': product_1.id,
|
||||
}),
|
||||
Command.create({
|
||||
'product_id': product_2.id,
|
||||
}),
|
||||
Command.create({
|
||||
'product_id': no_variant_template.product_variant_id.id,
|
||||
'product_no_variant_attribute_value_ids': [Command.set(ptav_s.ids)],
|
||||
}),
|
||||
Command.create({
|
||||
'product_id': no_variant_template.product_variant_id.id,
|
||||
'product_no_variant_attribute_value_ids': [Command.set(ptav_l.ids)],
|
||||
'product_custom_attribute_value_ids': [Command.create({
|
||||
'custom_product_template_attribute_value_id': ptav_l.id,
|
||||
'custom_value': 'Whatever',
|
||||
})]
|
||||
})
|
||||
],
|
||||
})
|
||||
order.message_subscribe(user_admin.partner_id.ids)
|
||||
|
||||
self.start_tour("/", 'website_sale_reorder_from_portal', login='admin')
|
||||
|
||||
reorder_cart = self.env['sale.order'].search([('website_id', '!=', False)], limit=1)
|
||||
previous_lines = order.order_line
|
||||
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_no_variant_attribute_value_ids,
|
||||
order_lines.product_no_variant_attribute_value_ids,
|
||||
)
|
||||
|
|
@ -0,0 +1,145 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.fields import Command
|
||||
from odoo.tests import tagged
|
||||
|
||||
from odoo.addons.account.tests.common import AccountTestInvoicingHttpCommon
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class WebsiteSaleShopPriceListCompareListPriceDispayTests(AccountTestInvoicingHttpCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls, chart_template_ref=None):
|
||||
super().setUpClass(chart_template_ref=chart_template_ref)
|
||||
|
||||
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",
|
||||
'company_id': cls.env.company.id,
|
||||
'sequence': 1,
|
||||
})
|
||||
|
||||
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,
|
||||
'company_id': cls.env.company.id,
|
||||
})
|
||||
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({
|
||||
'name': 'pricelist_default',
|
||||
'website_id': website.id,
|
||||
'company_id': cls.env.company.id,
|
||||
'selectable': True,
|
||||
'sequence': 1,
|
||||
})
|
||||
cls.pricelist_with_discount = Pricelist.create({
|
||||
'name': 'pricelist_with_discount',
|
||||
'website_id': website.id,
|
||||
'company_id': cls.env.company.id,
|
||||
'selectable': True,
|
||||
'sequence': 2,
|
||||
'discount_policy': 'with_discount',
|
||||
})
|
||||
cls.pricelist_without_discount = Pricelist.create({
|
||||
'name': 'pricelist_without_discount',
|
||||
'website_id': website.id,
|
||||
'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,
|
||||
})
|
||||
|
||||
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.start_tour("/", 'compare_list_price_price_list_display', login=self.env.user.login)
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
# -*- 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
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install', 'website_snippets')
|
||||
class TestSnippets(HttpCase):
|
||||
|
||||
def test_01_snippet_products_edition(self):
|
||||
self.env['product.product'].create({
|
||||
'name': 'Test Product',
|
||||
'website_published': True,
|
||||
'sale_ok': True,
|
||||
'list_price': 500,
|
||||
})
|
||||
self.env['product.product'].create({
|
||||
'name': 'Test Product 2',
|
||||
'website_published': True,
|
||||
'sale_ok': True,
|
||||
'list_price': 500,
|
||||
})
|
||||
self.env['product.product'].create({
|
||||
'name': 'Test Product 3',
|
||||
'website_published': True,
|
||||
'sale_ok': True,
|
||||
'list_price': 500,
|
||||
})
|
||||
self.env['product.product'].create({
|
||||
'name': 'Test Product 4',
|
||||
'website_published': True,
|
||||
'sale_ok': True,
|
||||
'list_price': 500,
|
||||
})
|
||||
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")
|
||||
|
|
@ -0,0 +1,119 @@
|
|||
# coding: utf-8
|
||||
from odoo.addons.website_sale.controllers.main import WebsiteSale
|
||||
from odoo.addons.website.tools import MockRequest
|
||||
from odoo.tests import TransactionCase, tagged
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class WebsiteSaleVisitorTests(TransactionCase):
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
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, "No visitor should be created after visiting another tracked product")
|
||||
self.assertEqual(len(new_tracks), 1, "No track should be created after visiting the same tracked product before 30 min")
|
||||
|
||||
product = self.env['product.product'].create({
|
||||
'name': 'Large Cabinet',
|
||||
'website_published': True,
|
||||
'list_price': 320.0,
|
||||
})
|
||||
|
||||
with MockRequest(self.env, website=self.website, cookies=self.cookies):
|
||||
self.WebsiteSaleController.products_recently_viewed_update(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, "No visitor should be created after visiting another tracked product")
|
||||
self.assertEqual(len(new_tracks), 2, "A track should be created after visiting another tracked product")
|
||||
|
||||
def test_dynamic_filter_newest_products(self):
|
||||
"""Test that a product is not displayed anymore after
|
||||
changing it company."""
|
||||
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',
|
||||
'website_published': True,
|
||||
'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')
|
||||
|
||||
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, [])
|
||||
res_products = [res_product['_record'] for res_product in res]
|
||||
self.assertNotIn(product, res_products)
|
||||
|
||||
def test_recently_viewed_company_changed(self):
|
||||
"""Test that a product is :
|
||||
- displayed after visiting it
|
||||
- not displayed after changing it company."""
|
||||
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',
|
||||
'website_published': True,
|
||||
'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_latest_viewed_products')
|
||||
|
||||
# BEFORE VISITING THE PRODUCT
|
||||
res = snippet_filter._prepare_values(16, [])
|
||||
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, [])
|
||||
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, [])
|
||||
self.assertFalse(res)
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import odoo.tests
|
||||
|
||||
|
||||
@odoo.tests.common.tagged('post_install', '-at_install')
|
||||
class TestWebsiteSequence(odoo.tests.TransactionCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestWebsiteSequence, self).setUp()
|
||||
|
||||
ProductTemplate = self.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'):
|
||||
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([])
|
||||
programs.active = False
|
||||
programs.coupon_ids.unlink()
|
||||
programs.unlink()
|
||||
product_templates.write({'active': False})
|
||||
self.p1, self.p2, self.p3, self.p4 = ProductTemplate.create([{
|
||||
'name': 'First Product',
|
||||
'website_sequence': 100,
|
||||
}, {
|
||||
'name': 'Second Product',
|
||||
'website_sequence': 180,
|
||||
}, {
|
||||
'name': 'Third Product',
|
||||
'website_sequence': 225,
|
||||
}, {
|
||||
'name': 'Last Product',
|
||||
'website_sequence': 250,
|
||||
}])
|
||||
|
||||
self._check_correct_order(self.p1 + self.p2 + self.p3 + self.p4)
|
||||
|
||||
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 _check_correct_order(self, products):
|
||||
product_ids = self._search_website_sequence_order().ids
|
||||
self.assertEqual(product_ids, products.ids, "Wrong sequence order")
|
||||
|
||||
def test_01_website_sequence(self):
|
||||
# 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.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.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.p1.set_sequence_bottom()
|
||||
# 95:2, 180:3, 225:4, 230:1
|
||||
self._check_correct_order(self.p2 + self.p3 + self.p4 + self.p1)
|
||||
|
||||
current_sequences = self._search_website_sequence_order().mapped('website_sequence')
|
||||
self.assertEqual(current_sequences, [95, 180, 225, 230], "Wrong sequence order (2)")
|
||||
|
||||
self.p2.website_sequence = 1
|
||||
self.p3.set_sequence_top()
|
||||
# -4:3, 1:2, 225:4, 230:1
|
||||
self.assertEqual(self.p3.website_sequence, -4, "`website_sequence` should go below 0")
|
||||
|
||||
new_product = self.env['product.template'].create({
|
||||
'name': 'Last Newly Created Product',
|
||||
})
|
||||
|
||||
self.assertEqual(self._search_website_sequence_order()[-1], new_product, "new product should be last")
|
||||
Loading…
Add table
Add a link
Reference in a new issue