diff options
| author | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
|---|---|---|
| committer | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
| commit | 3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch) | |
| tree | a44932296ef4a9b71d5f010906253d8c53727726 /addons/website_sale/tests | |
| parent | 0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff) | |
initial commit 2
Diffstat (limited to 'addons/website_sale/tests')
| -rw-r--r-- | addons/website_sale/tests/__init__.py | 13 | ||||
| -rw-r--r-- | addons/website_sale/tests/test_customize.py | 313 | ||||
| -rw-r--r-- | addons/website_sale/tests/test_sale_process.py | 230 | ||||
| -rw-r--r-- | addons/website_sale/tests/test_sitemap.py | 25 | ||||
| -rw-r--r-- | addons/website_sale/tests/test_website_sale_cart_recovery.py | 126 | ||||
| -rw-r--r-- | addons/website_sale/tests/test_website_sale_image.py | 320 | ||||
| -rw-r--r-- | addons/website_sale/tests/test_website_sale_mail.py | 31 | ||||
| -rw-r--r-- | addons/website_sale/tests/test_website_sale_pricelist.py | 469 | ||||
| -rw-r--r-- | addons/website_sale/tests/test_website_sale_product_attribute_value_config.py | 191 | ||||
| -rw-r--r-- | addons/website_sale/tests/test_website_sale_visitor.py | 53 | ||||
| -rw-r--r-- | addons/website_sale/tests/test_website_sequence.py | 77 |
11 files changed, 1848 insertions, 0 deletions
diff --git a/addons/website_sale/tests/__init__.py b/addons/website_sale/tests/__init__.py new file mode 100644 index 00000000..dc94da27 --- /dev/null +++ b/addons/website_sale/tests/__init__.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import test_customize +from . import test_sale_process +from . import test_sitemap +from . import test_website_sale_cart_recovery +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_visitor diff --git a/addons/website_sale/tests/test_customize.py b/addons/website_sale/tests/test_customize.py new file mode 100644 index 00000000..285a70d6 --- /dev/null +++ b/addons/website_sale/tests/test_customize.py @@ -0,0 +1,313 @@ +# 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(TestUi, self).setUp() + # 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', + '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 + + 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("/", 'shop_customize', login="admin") + + def test_02_admin_shop_custom_attribute_value_tour(self): + # Make sure pricelist rule exist + self.product_attribute_1 = self.env['product.attribute'].create({ + 'name': 'Legs', + 'sequence': 10, + }) + 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': 0, + }) + + # 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._onchange_sale_tax() + config.execute() + + self.start_tour("/", 'shop_list_view_b2c', login="admin") diff --git a/addons/website_sale/tests/test_sale_process.py b/addons/website_sale/tests/test_sale_process.py new file mode 100644 index 00000000..558e9965 --- /dev/null +++ b/addons/website_sale/tests/test_sale_process.py @@ -0,0 +1,230 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import odoo.tests + +from odoo import api +from odoo.addons.base.tests.common import HttpCaseWithUserDemo, TransactionCaseWithUserDemo +from odoo.addons.website_sale.controllers.main import WebsiteSale +from odoo.addons.website.tools import MockRequest + + +@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 (CONFIG)', + 'list_price': 16.50, + '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, + }) + + cash_journal = self.env['account.journal'].create({'name': 'Cash - Test', 'type': 'cash', 'code': 'CASH - Test'}) + self.env.ref('payment.payment_acquirer_transfer').journal_id = cash_journal + + # 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("/", 'shop', login="admin") + + def test_02_admin_checkout(self): + self.start_tour("/", 'shop_buy_product', login="admin") + + def test_03_demo_checkout(self): + self.start_tour("/", 'shop_buy_product', login="demo") + + def test_04_admin_website_sale_tour(self): + 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') + + +@odoo.tests.tagged('post_install', '-at_install') +class TestWebsiteSaleCheckoutAddress(TransactionCaseWithUserDemo): + ''' 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.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): + return self.env['sale.order'].create({ + '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}).id, + 'name': 'Product A', + })] + }) + + 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): + 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 + + 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): + # 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, edit 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.") + + 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): + # 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") diff --git a/addons/website_sale/tests/test_sitemap.py b/addons/website_sale/tests/test_sitemap.py new file mode 100644 index 00000000..2baf167e --- /dev/null +++ b/addons/website_sale/tests/test_sitemap.py @@ -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.") diff --git a/addons/website_sale/tests/test_website_sale_cart_recovery.py b/addons/website_sale/tests/test_website_sale_cart_recovery.py new file mode 100644 index 00000000..eae65eac --- /dev/null +++ b/addons/website_sale/tests/test_website_sale_cart_recovery.py @@ -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." + ) diff --git a/addons/website_sale/tests/test_website_sale_image.py b/addons/website_sale/tests/test_website_sale_image.py new file mode 100644 index 00000000..b50f7ae6 --- /dev/null +++ b/addons/website_sale/tests/test_website_sale_image.py @@ -0,0 +1,320 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import base64 +import io + +from PIL import Image + +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 + + 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 = Image.open(io.BytesIO(base64.b64decode(images[0].image_1920))) + self.assertEqual(image.size, (1268, 1920)) + self.assertEqual(images[1].image_1920, red_image) + 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()) diff --git a/addons/website_sale/tests/test_website_sale_mail.py b/addons/website_sale/tests/test_website_sale_mail.py new file mode 100644 index 00000000..4962dd0d --- /dev/null +++ b/addons/website_sale/tests/test_website_sale_mail.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from unittest.mock import patch + +import odoo +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 + + with patch.object(MailMail, 'unlink', lambda self: None): + self.start_tour("/", 'shop_mail', login="admin") diff --git a/addons/website_sale/tests/test_website_sale_pricelist.py b/addons/website_sale/tests/test_website_sale_pricelist.py new file mode 100644 index 00000000..01d5e580 --- /dev/null +++ b/addons/website_sale/tests/test_website_sale_pricelist.py @@ -0,0 +1,469 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. +from unittest.mock import patch + +from odoo.addons.base.tests.common import TransactionCaseWithUserDemo, HttpCaseWithUserPortal +from odoo.tests import tagged +from odoo.tests.common import HttpCase, TransactionCase +from odoo.tools import DotDict + +''' /!\/!\ +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): + + # 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 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) + patcher.start() + self.addCleanup(patcher.stop) + + def get_pl(self, show, current_pl, country): + self.website.invalidate_cache(['pricelist_ids'], [self.website.id]) + pl_ids = self.website._get_pl_partner_order( + country, + show, + self.website.pricelist_id.id, + current_pl, + self.website.pricelist_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 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) + patcher.start() + self.addCleanup(patcher.stop) + + +@tagged('post_install', '-at_install') +class TestWebsitePriceListAvailable(TransactionCase): + # This is enough to avoid a mock (request.session/website do not exist during test) + def get_pricelist_available(self, show_visible=False, website_id=1, country_code=None, website_sale_current_pl=None): + request = DotDict({ + 'website': self.env['website'].browse(website_id), + 'session': { + 'geoip': { + 'country_code': country_code, + }, + 'website_sale_current_pl': website_sale_current_pl, + }, + }) + return self.env['website']._get_pricelist_available(request, show_visible) + + 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}) + + 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.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.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 + + pls = self.get_pricelist_available(country_code=self.BE.code) + 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 + pls = self.get_pricelist_available(country_code=self.BE.code) + 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 + pls = self.get_pricelist_available(country_code=self.BE.code) + 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 + pls = self.get_pricelist_available(country_code=self.BE.code, show_visible=True, website_sale_current_pl=current_pl.id) + 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() + 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 + self.website = self.env.ref('website.default_website') + self.website.company_id = self.company2 + + # 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, + }) + 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.") + simulate_frontend_context(self) + # ---------------------------------- 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. + ''' + # 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 diff --git a/addons/website_sale/tests/test_website_sale_product_attribute_value_config.py b/addons/website_sale/tests/test_website_sale_product_attribute_value_config.py new file mode 100644 index 00000000..fa0de777 --- /dev/null +++ b/addons/website_sale/tests/test_website_sale_product_attribute_value_config.py @@ -0,0 +1,191 @@ +# -*- 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.tests import tagged +from odoo.addons.website.tools import MockRequest + + +@tagged('post_install', '-at_install') +class TestWebsiteSaleProductAttributeValueConfig(TestSaleProductAttributeValueCommon): + + def test_get_combination_info(self): + current_website = self.env['website'].get_current_website() + pricelist = current_website.get_current_pricelist() + + self.computer = self.computer.with_context(website_id=current_website.id) + + # 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 + + # make sure there is a 15% tax on the product + tax = self.env['account.tax'].create({'name': "Test tax", 'amount': 15}) + self.computer.taxes_id = tax + tax_ratio = (100 + tax.amount) / 100 + + currency_ratio = 2 + pricelist.currency_id = self._setup_currency(currency_ratio) + + # ensure pricelist is set to with_discount + pricelist.discount_policy = 'with_discount' + + # CASE: B2B 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_included.users -= self.env.user + group_tax_excluded.users |= self.env.user + + combination_info = self.computer._get_combination_info() + 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['has_discounted_price'], False) + + # CASE: B2C setting + group_tax_excluded.users -= self.env.user + group_tax_included.users |= self.env.user + + combination_info = self.computer._get_combination_info() + 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['has_discounted_price'], False) + + # CASE: pricelist 'without_discount' + pricelist.discount_policy = 'without_discount' + + # ideally we would need to use compare_amounts everywhere, but this is + # the only rounding where it fails without it + combination_info = self.computer._get_combination_info() + self.assertEqual(pricelist.currency_id.compare_amounts(combination_info['price'], 2222 * discount_rate * currency_ratio * tax_ratio), 0) + self.assertEqual(pricelist.currency_id.compare_amounts(combination_info['list_price'], 2222 * currency_ratio * tax_ratio), 0) + self.assertEqual(combination_info['has_discounted_price'], True) + + def test_get_combination_info_with_fpos(self): + self.env.user.partner_id.country_id = False + current_website = self.env['website'].get_current_website() + pricelist = current_website.get_current_pricelist() + (self.env['product.pricelist'].search([]) - pricelist).write({'active': False}) + + test_product = self.env['product.template'].create({ + 'name': 'Test Product', + 'price': 2000, + }).with_context(website_id=current_website.id) + + # Add fixed price for pricelist + pricelist.item_ids = self.env['product.pricelist.item'].create({ + 'applied_on': "1_product", + 'base': "list_price", + 'compute_price': "fixed", + 'fixed_price': 500, + 'product_tmpl_id': test_product.id, + }) + # Add 15% tax on product + tax15 = self.env['account.tax'].create({'name': "Test tax 15", 'amount': 15}) + tax0 = self.env['account.tax'].create({'name': "Test tax 0", 'amount': 0}) + test_product.taxes_id = tax15 + + # 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 + + # Create fiscal position for belgium mapping taxes 15% -> 0% + fpos = self.env['account.fiscal.position'].create({ + 'name': 'test', + 'auto_apply': True, + 'country_id': self.env.ref('base.be').id, + }) + self.env['account.fiscal.position.tax'].create({ + 'position_id': fpos.id, + 'tax_src_id': tax15.id, + 'tax_dest_id': tax0.id, + }) + + combination_info = test_product._get_combination_info() + self.assertEqual(combination_info['price'], 575, "500$ + 15% tax") + self.assertEqual(combination_info['list_price'], 575, "500$ + 15% tax (2)") + + # Now with fiscal position, taxes should be mapped + self.env.user.partner_id.country_id = self.env.ref('base.be').id + combination_info = test_product._get_combination_info() + self.assertEqual(combination_info['price'], 500, "500% + 0% tax (mapped from fp 15% -> 0% for BE)") + self.assertEqual(combination_info['list_price'], 500, "500% + 0% tax (mapped from fp 15% -> 0% for BE) (2)") + + # Try same flow with tax included + tax15.write({'price_include': True}) + + # Reset / Safety check + self.env.user.partner_id.country_id = None + combination_info = test_product._get_combination_info() + self.assertEqual(combination_info['price'], 500, "434.78$ + 15% tax") + self.assertEqual(combination_info['list_price'], 500, "434.78$ + 15% tax (2)") + + # Now with fiscal position, taxes should be mapped + self.env.user.partner_id.country_id = self.env.ref('base.be').id + combination_info = test_product._get_combination_info() + self.assertEqual(round(combination_info['price'], 2), 434.78, "434.78$ + 0% tax (mapped from fp 15% -> 0% for BE)") + self.assertEqual(round(combination_info['list_price'], 2), 434.78, "434.78$ + 0% tax (mapped from fp 15% -> 0% for BE)") + +@tagged('post_install', '-at_install') +class TestWebsiteSaleProductPricelist(TestSaleProductAttributeValueCommon): + def test_cart_update_with_fpos(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 + self.env.user.partner_id.country_id = False + 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'}) + tax0 = self.env['account.tax'].create({'name': "Test tax 0", 'amount': 0, 'price_include': True, 'amount_type': 'percent'}) + + test_product = self.env['product.template'].create({ + 'name': 'Test Product', + '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% -> 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, + }) + so = self.env['sale.order'].create({ + 'partner_id': self.env.user.partner_id.id, + }) + sol = self.env['sale.order.line'].create({ + 'name': test_product.name, + 'product_id': test_product.product_variant_id.id, + 'product_uom_qty': 1, + 'product_uom': test_product.uom_id.id, + 'price_unit': test_product.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.product_id_change() + 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=1) + self.assertEqual(round(sol.price_total), 50, "100$ with 50% discount + 0% tax (mapped from fp 10% -> 0%)") diff --git a/addons/website_sale/tests/test_website_sale_visitor.py b/addons/website_sale/tests/test_website_sale_visitor.py new file mode 100644 index 00000000..7f24f33d --- /dev/null +++ b/addons/website_sale/tests/test_website_sale_visitor.py @@ -0,0 +1,53 @@ +# 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") diff --git a/addons/website_sale/tests/test_website_sequence.py b/addons/website_sale/tests/test_website_sequence.py new file mode 100644 index 00000000..b09193b3 --- /dev/null +++ b/addons/website_sale/tests/test_website_sequence.py @@ -0,0 +1,77 @@ +# 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.rule' in self.env: + rules = self.env['loyalty.rule'].search([]) + rules.unlink() + if 'loyalty.reward' in self.env: + rewards = self.env['loyalty.reward'].search([]) + rewards.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") |
