mirror of
https://github.com/bringout/oca-ocb-sale.git
synced 2026-04-25 05:32:04 +02:00
19.0 vanilla
This commit is contained in:
parent
79f83631d5
commit
73afc09215
6267 changed files with 1534193 additions and 1130106 deletions
|
|
@ -1,11 +1,15 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import test_barcode
|
||||
from . import test_common
|
||||
from . import test_import_files
|
||||
from . import test_name
|
||||
from . import test_pricelist
|
||||
from . import test_pricelist_auto_creation
|
||||
from . import test_product_attribute_value_config
|
||||
from . import test_product_combo
|
||||
from . import test_product_pricelist
|
||||
from . import test_seller
|
||||
from . import test_update_pav_wizard
|
||||
from . import test_variants
|
||||
from . import test_product_rounding
|
||||
|
|
|
|||
|
|
@ -1,66 +1,75 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from contextlib import nullcontext
|
||||
from unittest.mock import patch
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.fields import Command
|
||||
|
||||
from odoo.addons.base.tests.common import BaseCommon
|
||||
from odoo.addons.uom.tests.common import UomCommon
|
||||
|
||||
|
||||
class ProductCommon(
|
||||
BaseCommon, # enforce constant test currency (USD)
|
||||
UomCommon,
|
||||
):
|
||||
class ProductCommon(UomCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
# Ideally, this logic should be moved into sthg like a NoAccountCommon in account :D
|
||||
# Since tax fields are specified in account module, cannot be given as create values
|
||||
NO_TAXES_CONTEXT = {
|
||||
'default_taxes_id': False
|
||||
}
|
||||
cls.group_product_pricelist = cls.quick_ref('product.group_product_pricelist')
|
||||
cls.group_product_variant = cls.quick_ref('product.group_product_variant')
|
||||
|
||||
cls.product_category = cls.env['product.category'].create({
|
||||
'name': 'Test Category',
|
||||
})
|
||||
cls.product = cls.env['product.product'].with_context(**NO_TAXES_CONTEXT).create({
|
||||
cls.product, cls.service_product = cls.env['product.product'].create([{
|
||||
'name': 'Test Product',
|
||||
'detailed_type': 'consu',
|
||||
'type': 'consu',
|
||||
'list_price': 20.0,
|
||||
'categ_id': cls.product_category.id,
|
||||
})
|
||||
cls.service_product = cls.env['product.product'].with_context(**NO_TAXES_CONTEXT).create({
|
||||
}, {
|
||||
'name': 'Test Service Product',
|
||||
'detailed_type': 'service',
|
||||
'type': 'service',
|
||||
'list_price': 50.0,
|
||||
'categ_id': cls.product_category.id,
|
||||
})
|
||||
cls.consumable_product = cls.product
|
||||
}])
|
||||
cls.pricelist = cls.env['product.pricelist'].create({
|
||||
'name': 'Test Pricelist',
|
||||
})
|
||||
cls._archive_other_pricelists()
|
||||
# Archive all existing pricelists
|
||||
cls.env['product.pricelist'].search([
|
||||
('id', '!=', cls.pricelist.id),
|
||||
]).action_archive()
|
||||
|
||||
@classmethod
|
||||
def _archive_other_pricelists(cls):
|
||||
"""Do not raise if there is no pricelist(s) for a given website"""
|
||||
website_sale = cls.env['ir.module.module']._get('website_sale')
|
||||
if website_sale.state == 'installed':
|
||||
archive_context = patch('odoo.addons.website_sale.models.product_pricelist.ProductPricelist._check_website_pricelist')
|
||||
else:
|
||||
archive_context = nullcontext()
|
||||
def get_default_groups(cls):
|
||||
groups = super().get_default_groups()
|
||||
return groups | cls.quick_ref('product.group_product_manager')
|
||||
|
||||
with archive_context:
|
||||
cls.env['product.pricelist'].search([
|
||||
('id', '!=', cls.pricelist.id),
|
||||
]).action_archive()
|
||||
@classmethod
|
||||
def _enable_pricelists(cls):
|
||||
cls.env.user.group_ids += cls.group_product_pricelist
|
||||
|
||||
@classmethod
|
||||
def _enable_variants(cls):
|
||||
cls.env.user.group_ids += cls.group_product_variant
|
||||
|
||||
@classmethod
|
||||
def _create_pricelist(cls, **create_vals):
|
||||
return cls.env['product.pricelist'].create({
|
||||
'name': "Test Pricelist",
|
||||
**create_vals,
|
||||
})
|
||||
|
||||
@classmethod
|
||||
def _create_product(cls, **create_vals):
|
||||
return cls.env['product.product'].create({
|
||||
'name': "Test Product",
|
||||
'type': 'consu',
|
||||
'list_price': 100.0,
|
||||
'standard_price': 50.0,
|
||||
'uom_id': cls.uom_unit.id,
|
||||
'categ_id': cls.product_category.id,
|
||||
**create_vals,
|
||||
})
|
||||
|
||||
|
||||
class ProductAttributesCommon(ProductCommon):
|
||||
class ProductVariantsCommon(ProductCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
|
|
@ -94,17 +103,31 @@ class ProductAttributesCommon(ProductCommon):
|
|||
cls.color_attribute_green,
|
||||
) = cls.color_attribute.value_ids
|
||||
|
||||
cls.no_variant_attribute = cls.env['product.attribute'].create({
|
||||
'name': 'No variant',
|
||||
'create_variant': 'no_variant',
|
||||
'value_ids': [
|
||||
Command.create({'name': 'extra'}),
|
||||
Command.create({'name': 'second'}),
|
||||
]
|
||||
})
|
||||
(
|
||||
cls.no_variant_attribute_extra,
|
||||
cls.no_variant_attribute_second,
|
||||
) = cls.no_variant_attribute.value_ids
|
||||
|
||||
class ProductVariantsCommon(ProductAttributesCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.dynamic_attribute = cls.env['product.attribute'].create({
|
||||
'name': 'Dynamic',
|
||||
'create_variant': 'dynamic',
|
||||
'value_ids': [
|
||||
Command.create({'name': 'dyn1'}),
|
||||
Command.create({'name': 'dyn2'}),
|
||||
]
|
||||
})
|
||||
|
||||
cls.product_template_sofa = cls.env['product.template'].create({
|
||||
'name': 'Sofa',
|
||||
'uom_id': cls.uom_unit.id,
|
||||
'uom_po_id': cls.uom_unit.id,
|
||||
'categ_id': cls.product_category.id,
|
||||
'attribute_line_ids': [Command.create({
|
||||
'attribute_id': cls.color_attribute.id,
|
||||
|
|
@ -116,62 +139,21 @@ class ProductVariantsCommon(ProductAttributesCommon):
|
|||
})]
|
||||
})
|
||||
|
||||
cls.product_template_shirt = cls.env['product.template'].create({
|
||||
'name': 'Shirt',
|
||||
'categ_id': cls.product_category.id,
|
||||
'attribute_line_ids': [
|
||||
Command.create({
|
||||
'attribute_id': cls.size_attribute.id,
|
||||
'value_ids': [Command.set([cls.size_attribute_l.id])],
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
|
||||
class TestProductCommon(ProductVariantsCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
# Product environment related data
|
||||
cls.uom_dunit = cls.env['uom.uom'].create({
|
||||
'name': 'DeciUnit',
|
||||
'category_id': cls.uom_unit.category_id.id,
|
||||
'factor_inv': 0.1,
|
||||
'factor': 10.0,
|
||||
'uom_type': 'smaller',
|
||||
'rounding': 0.001,
|
||||
})
|
||||
|
||||
cls.product_1, cls.product_2 = cls.env['product.product'].create([{
|
||||
'name': 'Courage', # product_1
|
||||
'type': 'consu',
|
||||
'default_code': 'PROD-1',
|
||||
'uom_id': cls.uom_dunit.id,
|
||||
'uom_po_id': cls.uom_dunit.id,
|
||||
}, {
|
||||
'name': 'Wood', # product_2
|
||||
}])
|
||||
|
||||
# Kept for reduced diff in other modules (mainly stock & mrp)
|
||||
cls.prod_att_1 = cls.color_attribute
|
||||
cls.prod_attr1_v1 = cls.color_attribute_red
|
||||
cls.prod_attr1_v2 = cls.color_attribute_blue
|
||||
cls.prod_attr1_v3 = cls.color_attribute_green
|
||||
|
||||
cls.product_7_template = cls.product_template_sofa
|
||||
|
||||
cls.product_7_attr1_v1 = cls.product_7_template.attribute_line_ids[
|
||||
0].product_template_value_ids[0]
|
||||
cls.product_7_attr1_v2 = cls.product_7_template.attribute_line_ids[
|
||||
0].product_template_value_ids[1]
|
||||
cls.product_7_attr1_v3 = cls.product_7_template.attribute_line_ids[
|
||||
0].product_template_value_ids[2]
|
||||
|
||||
cls.product_7_1 = cls.product_7_template._get_variant_for_combination(
|
||||
cls.product_7_attr1_v1)
|
||||
cls.product_7_2 = cls.product_7_template._get_variant_for_combination(
|
||||
cls.product_7_attr1_v2)
|
||||
cls.product_7_3 = cls.product_7_template._get_variant_for_combination(
|
||||
cls.product_7_attr1_v3)
|
||||
cls.product_sofa_red = cls.product_template_sofa.product_variant_ids.filtered(
|
||||
lambda pp:
|
||||
pp.product_template_attribute_value_ids.product_attribute_value_id
|
||||
==
|
||||
cls.color_attribute_red
|
||||
)
|
||||
cls.product_sofa_blue = cls.product_template_sofa.product_variant_ids.filtered(
|
||||
lambda pp:
|
||||
pp.product_template_attribute_value_ids.product_attribute_value_id
|
||||
==
|
||||
cls.color_attribute_blue
|
||||
)
|
||||
cls.product_sofa_green = cls.product_template_sofa.product_variant_ids.filtered(
|
||||
lambda pp:
|
||||
pp.product_template_attribute_value_ids.product_attribute_value_id
|
||||
==
|
||||
cls.color_attribute_green
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.tests import tagged, TransactionCase
|
||||
from odoo.fields import Command
|
||||
from odoo.tests import TransactionCase, tagged
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
|
|
@ -16,6 +17,26 @@ class TestProductBarcode(TransactionCase):
|
|||
{'name': 'BC2', 'barcode': '2'},
|
||||
])
|
||||
|
||||
cls.size_attribute = cls.env['product.attribute'].create({
|
||||
'name': 'Size',
|
||||
'value_ids': [
|
||||
Command.create({'name': 'SMALL'}),
|
||||
Command.create({'name': 'LARGE'}),
|
||||
]
|
||||
})
|
||||
cls.size_attribute_s, cls.size_attribute_l = cls.size_attribute.value_ids
|
||||
|
||||
cls.template = cls.env['product.template'].create({'name': 'template'})
|
||||
cls.template.write({
|
||||
'attribute_line_ids': [Command.create({
|
||||
'attribute_id': cls.size_attribute.id,
|
||||
'value_ids': [
|
||||
Command.link(cls.size_attribute_s.id),
|
||||
Command.link(cls.size_attribute_l.id),
|
||||
],
|
||||
})]
|
||||
})
|
||||
|
||||
def test_blank_barcodes_allowed(self):
|
||||
"""Makes sure duplicated blank barcodes are allowed."""
|
||||
for i in range(2):
|
||||
|
|
@ -54,40 +75,100 @@ class TestProductBarcode(TransactionCase):
|
|||
try:
|
||||
self.env['product.product'].create(batch)
|
||||
except ValidationError as exc:
|
||||
assert 'Barcode "3" already assigned to product(s): BC3, BC4' in exc.args[0]
|
||||
assert 'Barcode "4" already assigned to product(s): BC5, BC6' in exc.args[0]
|
||||
assert 'Barcode "3" already assigned to product(s): BC3 and BC4' in exc.args[0]
|
||||
assert 'Barcode "4" already assigned to product(s): BC5 and BC6' in exc.args[0]
|
||||
assert 'Barcode "1" already assigned to product(s): BC1' in exc.args[0]
|
||||
|
||||
def test_delete_package_and_use_its_barcode_in_product(self):
|
||||
""" Test that the barcode of the package can be used when the package is removed from the product."""
|
||||
def test_delete_packaging_and_use_its_barcode_in_product(self):
|
||||
""" Test that the barcode of the packaging can be used when the packaging is removed from the product."""
|
||||
pack_uom = self.env['uom.uom'].create({
|
||||
'name': 'Pack of 10',
|
||||
'relative_factor': 10,
|
||||
'relative_uom_id': self.env.ref('uom.product_uom_unit').id,
|
||||
})
|
||||
product = self.env['product.product'].create({
|
||||
'name': 'product',
|
||||
'packaging_ids': [(0, 0, {
|
||||
'name': 'packing',
|
||||
'barcode': '1234',
|
||||
})]
|
||||
'uom_ids': [Command.link(pack_uom.id)],
|
||||
})
|
||||
package = product.packaging_ids
|
||||
self.assertTrue(package.exists())
|
||||
self.assertEqual(package.barcode, '1234')
|
||||
product.packaging_ids = False
|
||||
packaging_barcode = self.env['product.uom'].create({
|
||||
'barcode': '1234',
|
||||
'product_id': product.id,
|
||||
'uom_id': pack_uom.id,
|
||||
})
|
||||
packaging = product.product_uom_ids
|
||||
self.assertTrue(packaging.exists())
|
||||
self.assertEqual(packaging.barcode, '1234')
|
||||
packaging_barcode.unlink()
|
||||
self.assertFalse(packaging.exists())
|
||||
product.barcode = '1234'
|
||||
|
||||
def test_delete_product_and_reuse_barcode(self):
|
||||
""" Test that the barcode of the package can be used when the package is removed from the product."""
|
||||
product = self.env['product.product'].create({
|
||||
'name': 'product',
|
||||
'packaging_ids': [(0, 0, {
|
||||
'name': 'packing',
|
||||
'barcode': '1234',
|
||||
})]
|
||||
})
|
||||
product.unlink()
|
||||
def test_duplicated_barcodes_are_allowed_for_different_companies(self):
|
||||
"""Barcode needs to be unique only withing the same company"""
|
||||
company_a = self.env.company
|
||||
company_b = self.env['res.company'].create({'name': 'CB'})
|
||||
|
||||
self.env['product.product'].create({
|
||||
'name': 'product2',
|
||||
'packaging_ids': [(0, 0, {
|
||||
'name': 'packing2',
|
||||
'barcode': '1234',
|
||||
})]
|
||||
allowed_products = [
|
||||
# Allowed, barcode doesn't exist yet
|
||||
{'name': 'A1', 'barcode': '3', 'company_id': company_a.id},
|
||||
# Allowed, barcode exists (A1), but for a different company
|
||||
{'name': 'A2', 'barcode': '3', 'company_id': company_b.id},
|
||||
]
|
||||
|
||||
forbidden_products = [
|
||||
# Forbidden, collides with BC1
|
||||
{'name': 'F1', 'barcode': '1', 'company_id': False},
|
||||
# Forbidden, collides with BC1
|
||||
{'name': 'F2', 'barcode': '1', 'company_id': company_a.id},
|
||||
# Forbidden, collides with BC2
|
||||
{'name': 'F3', 'barcode': '2', 'company_id': company_b.id},
|
||||
# Forbidden, collides with A1
|
||||
{'name': 'F4', 'barcode': '3', 'company_id': company_a.id},
|
||||
# Forbidden, collides with A2
|
||||
{'name': 'F5', 'barcode': '3', 'company_id': company_b.id},
|
||||
# Forbidden, collides with A1 and A2
|
||||
{'name': 'F6', 'barcode': '3', 'company_id': False},
|
||||
]
|
||||
|
||||
for product in allowed_products:
|
||||
self.env['product.product'].create(product)
|
||||
|
||||
for product in forbidden_products:
|
||||
with self.assertRaises(ValidationError):
|
||||
self.env['product.product'].create(product)
|
||||
|
||||
def test_duplicated_barcodes_in_product_variants(self):
|
||||
"""
|
||||
Create 2 variants with different barcodes and same company.
|
||||
Assign a duplicated barcode to one of them while changing the company.
|
||||
Barcode validation should be triggered and a duplicated barcode should be detected.
|
||||
"""
|
||||
company_a = self.env.company
|
||||
company_b = self.env['res.company'].create({'name': 'CB'})
|
||||
|
||||
variant_1 = self.template.product_variant_ids[0]
|
||||
variant_2 = self.template.product_variant_ids[1]
|
||||
|
||||
variant_1.barcode = 'barcode_1'
|
||||
variant_1.company_id = company_a
|
||||
variant_2.barcode = 'barcode_2'
|
||||
variant_2.company_id = company_a
|
||||
|
||||
with self.assertRaises(ValidationError):
|
||||
variant_2.write({
|
||||
'barcode': 'barcode_1',
|
||||
'company_id': company_b
|
||||
})
|
||||
|
||||
# Variant 1 was not updated
|
||||
self.assertEqual(variant_2.barcode, 'barcode_2')
|
||||
self.assertEqual(variant_2.company_id, company_a)
|
||||
|
||||
variant_2.write({
|
||||
'barcode': 'barcode_3',
|
||||
'company_id': company_b
|
||||
})
|
||||
|
||||
self.assertEqual(variant_1.barcode, 'barcode_1')
|
||||
self.assertEqual(variant_1.company_id, company_b)
|
||||
self.assertEqual(variant_2.barcode, 'barcode_3')
|
||||
self.assertEqual(variant_2.company_id, company_b)
|
||||
|
|
|
|||
|
|
@ -10,14 +10,10 @@ from odoo.addons.product.tests.common import ProductCommon
|
|||
class TestProduct(ProductCommon):
|
||||
|
||||
def test_common(self):
|
||||
self.assertEqual(self.consumable_product.type, 'consu')
|
||||
self._enable_pricelists()
|
||||
self.assertEqual(self.product.type, 'consu')
|
||||
self.assertEqual(self.service_product.type, 'service')
|
||||
|
||||
account_module = self.env['ir.module.module']._get('account')
|
||||
if account_module.state == 'installed':
|
||||
self.assertFalse(self.consumable_product.taxes_id)
|
||||
self.assertFalse(self.service_product.taxes_id)
|
||||
|
||||
self.assertFalse(self.pricelist.item_ids)
|
||||
self.assertEqual(
|
||||
self.env['product.pricelist'].search([]),
|
||||
|
|
@ -27,5 +23,4 @@ class TestProduct(ProductCommon):
|
|||
self.env['res.partner'].search([]).property_product_pricelist,
|
||||
self.pricelist,
|
||||
)
|
||||
self.assertEqual(self.pricelist.currency_id.name, 'USD')
|
||||
self.assertEqual(self.pricelist.discount_policy, 'with_discount')
|
||||
self.assertEqual(self.pricelist.currency_id.name, self.currency.name)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,63 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import unittest
|
||||
|
||||
from odoo.tests import TransactionCase, can_import, loaded_demo_data, tagged
|
||||
from odoo.tools.misc import file_open
|
||||
|
||||
|
||||
@tagged("post_install", "-at_install")
|
||||
class TestImportFiles(TransactionCase):
|
||||
|
||||
@unittest.skipUnless(
|
||||
can_import("xlrd.xlsx") or can_import("openpyxl"), "XLRD/XLSX not available"
|
||||
)
|
||||
def test_import_product_demo_xls(self):
|
||||
if not loaded_demo_data(self.env):
|
||||
self.skipTest('Needs demo data to be able to import those files')
|
||||
for model in ("product.pricelist", "product.supplierinfo", "product.template"):
|
||||
with self.subTest(model):
|
||||
filename = f'{model.replace(".", "_")}.xls'
|
||||
file_content = file_open(f"product/static/xls/{filename}", "rb").read()
|
||||
import_wizard = self.env["base_import.import"].create(
|
||||
{
|
||||
"res_model": model,
|
||||
"file": file_content,
|
||||
"file_type": "application/vnd.ms-excel",
|
||||
}
|
||||
)
|
||||
|
||||
result = import_wizard.parse_preview(
|
||||
{
|
||||
"has_headers": True,
|
||||
}
|
||||
)
|
||||
self.assertIsNone(result.get("error"))
|
||||
field_names = ['/'.join(v) for v in result["matches"].values()]
|
||||
results = import_wizard.execute_import(
|
||||
field_names,
|
||||
[r.lower() for r in result["headers"]],
|
||||
{
|
||||
"import_skip_records": [],
|
||||
"import_set_empty_fields": [],
|
||||
"fallback_values": {},
|
||||
"name_create_enabled_fields": {},
|
||||
"encoding": "",
|
||||
"separator": "",
|
||||
"quoting": '"',
|
||||
"date_format": "",
|
||||
"datetime_format": "",
|
||||
"float_thousand_separator": ",",
|
||||
"float_decimal_separator": ".",
|
||||
"advanced": True,
|
||||
"has_headers": True,
|
||||
"keep_matches": False,
|
||||
"limit": 2000,
|
||||
"skip": 0,
|
||||
"tracking_disable": True,
|
||||
},
|
||||
)
|
||||
self.assertFalse(
|
||||
results["messages"],
|
||||
"results should be empty on successful import of ",
|
||||
)
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.tests import tagged, TransactionCase
|
||||
from odoo.fields import Command
|
||||
from odoo.tests import TransactionCase, tagged
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
|
|
@ -32,7 +33,7 @@ class TestName(TransactionCase):
|
|||
|
||||
def test_product_template_search_name_no_product_product(self):
|
||||
# To be able to test dynamic variant "variants" feature must be set up
|
||||
self.env.user.write({'groups_id': [(4, self.env.ref('product.group_product_variant').id)]})
|
||||
self.env.user.write({'group_ids': [(4, self.env.ref('product.group_product_variant').id)]})
|
||||
color_attr = self.env['product.attribute'].create({'name': 'Color', 'create_variant': 'dynamic'})
|
||||
color_attr_value_r = self.env['product.attribute.value'].create({'name': 'Red', 'attribute_id': color_attr.id})
|
||||
color_attr_value_b = self.env['product.attribute.value'].create({'name': 'Blue', 'attribute_id': color_attr.id})
|
||||
|
|
@ -56,3 +57,27 @@ class TestName(TransactionCase):
|
|||
res_ids = [r[0] for r in res]
|
||||
self.assertIn(template_dyn.id, res_ids)
|
||||
self.assertIn(product.product_tmpl_id.id, res_ids)
|
||||
|
||||
def test_product_product_name_search(self):
|
||||
attribute = self.env['product.attribute'].create({
|
||||
'name': 'Attribute',
|
||||
'value_ids': [
|
||||
Command.create({'name': f'value {i}'})
|
||||
for i in range(3)
|
||||
]
|
||||
})
|
||||
template = self.env['product.template'].create({
|
||||
'name': 'Whatever',
|
||||
'attribute_line_ids': [
|
||||
Command.create({
|
||||
'attribute_id': attribute.id,
|
||||
'value_ids': [Command.set(attribute.value_ids.ids)]
|
||||
})
|
||||
]
|
||||
})
|
||||
variant1, _variant2, _variant3 = template.product_variant_ids
|
||||
variant1.default_code = 'HOHO'
|
||||
product_search = self.env['product.product'].with_context(partner_id=33).search([
|
||||
('display_name', '=', 'HOHO'),
|
||||
])
|
||||
self.assertEqual(variant1, product_search)
|
||||
|
|
|
|||
|
|
@ -1,14 +1,17 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.fields import Command
|
||||
from odoo.tests import tagged
|
||||
from itertools import pairwise
|
||||
from unittest.mock import patch
|
||||
|
||||
from odoo.addons.product.tests.common import ProductCommon
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
from odoo.fields import Command, Domain
|
||||
from odoo.tests import Form, tagged
|
||||
|
||||
from odoo.addons.product.tests.common import ProductVariantsCommon
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestPricelist(ProductCommon):
|
||||
class TestPricelist(ProductVariantsCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
|
|
@ -17,7 +20,7 @@ class TestPricelist(ProductCommon):
|
|||
cls.datacard = cls.env['product.product'].create({'name': 'Office Lamp'})
|
||||
cls.usb_adapter = cls.env['product.product'].create({'name': 'Office Chair'})
|
||||
|
||||
cls.sale_pricelist_id = cls.env['product.pricelist'].create({
|
||||
cls.sale_pricelist_id, cls.pricelist_eu = cls.env['product.pricelist'].create([{
|
||||
'name': 'Sale pricelist',
|
||||
'item_ids': [
|
||||
Command.create({
|
||||
|
|
@ -34,8 +37,21 @@ class TestPricelist(ProductCommon):
|
|||
'product_id': cls.datacard.id,
|
||||
'applied_on': '0_product_variant',
|
||||
}),
|
||||
Command.create({
|
||||
'compute_price': 'formula',
|
||||
'base': 'standard_price', # based on cost
|
||||
'price_markup': 99.99,
|
||||
'applied_on': '3_global',
|
||||
}),
|
||||
],
|
||||
})
|
||||
}, {
|
||||
'name': "EU Pricelist",
|
||||
'country_group_ids': cls.env.ref('base.europe').ids,
|
||||
}])
|
||||
|
||||
# Enable pricelist feature
|
||||
cls.env.user.group_ids += cls.env.ref('product.group_product_pricelist')
|
||||
cls.uom_ton = cls.env.ref('uom.product_uom_ton')
|
||||
|
||||
def test_10_discount(self):
|
||||
# Make sure the price using a pricelist is the same than without after
|
||||
|
|
@ -58,19 +74,29 @@ class TestPricelist(ProductCommon):
|
|||
self.sale_pricelist_id._get_product_price(self.datacard, 1.0, uom=self.uom_unit)*12,
|
||||
self.sale_pricelist_id._get_product_price(self.datacard, 1.0, uom=self.uom_dozen))
|
||||
|
||||
def test_11_markup(self):
|
||||
"""Ensure `price_markup` always equals negative `price_discount`."""
|
||||
# Check create values
|
||||
for item in self.sale_pricelist_id.item_ids:
|
||||
self.assertEqual(item.price_markup, -item.price_discount)
|
||||
|
||||
# Overwrite create values, and check again
|
||||
self.sale_pricelist_id.item_ids[0].price_discount = 0
|
||||
self.sale_pricelist_id.item_ids[1].price_discount = -20.02
|
||||
self.sale_pricelist_id.item_ids[2].price_markup = -0.5
|
||||
for item in self.sale_pricelist_id.item_ids:
|
||||
self.assertEqual(item.price_markup, -item.price_discount)
|
||||
|
||||
def test_20_pricelist_uom(self):
|
||||
# Verify that the pricelist rules are correctly using the product's default UoM
|
||||
# as reference, and return a result according to the target UoM (as specific in the context)
|
||||
|
||||
tonne_price = 100
|
||||
|
||||
# make sure 'tonne' resolves down to 1 'kg'.
|
||||
self.uom_ton.write({'rounding': 0.001})
|
||||
# setup product stored in 'tonnes', with a discounted pricelist for qty > 3 tonnes
|
||||
spam = self.env['product.product'].create({
|
||||
'name': '1 tonne of spam',
|
||||
'uom_id': self.uom_ton.id,
|
||||
'uom_po_id': self.uom_ton.id,
|
||||
'list_price': tonne_price,
|
||||
'type': 'consu'
|
||||
})
|
||||
|
|
@ -96,3 +122,345 @@ class TestPricelist(ProductCommon):
|
|||
test_unit_price(3500, self.uom_kgm.id, (tonne_price - 10) / 1000.0)
|
||||
test_unit_price(2, self.uom_ton.id, tonne_price)
|
||||
test_unit_price(3, self.uom_ton.id, tonne_price - 10)
|
||||
|
||||
def test_30_pricelists_order(self):
|
||||
# Verify the order of pricelists after creation
|
||||
|
||||
ProductPricelist = self.env['product.pricelist']
|
||||
res_partner = self.env['res.partner'].create({'name': 'Ready Corner'})
|
||||
|
||||
ProductPricelist.search([]).active = False
|
||||
|
||||
pl_first = ProductPricelist.create({'name': 'First Pricelist'})
|
||||
res_partner.invalidate_recordset(['property_product_pricelist'])
|
||||
|
||||
self.assertEqual(res_partner.property_product_pricelist, pl_first)
|
||||
|
||||
ProductPricelist.create({'name': 'Second Pricelist'})
|
||||
res_partner.invalidate_recordset(['property_product_pricelist'])
|
||||
|
||||
self.assertEqual(res_partner.property_product_pricelist, pl_first)
|
||||
|
||||
def test_40_specific_property_product_pricelist(self):
|
||||
"""Ensure that that ``specific_property_product_pricelist`` value only gets set
|
||||
when changing ``property_product_pricelist`` to a non-default value for the partner.
|
||||
"""
|
||||
pricelist_1, pricelist_2 = self.pricelist, self.sale_pricelist_id
|
||||
self.env['product.pricelist'].search([
|
||||
('id', 'not in', [pricelist_1.id, pricelist_2.id, self.pricelist_eu.id]),
|
||||
]).active = False
|
||||
|
||||
# Set country to BE -> property defaults to EU pricelist
|
||||
with Form(self.partner) as partner_form:
|
||||
partner_form.country_id = self.env.ref('base.be')
|
||||
self.assertEqual(self.partner.property_product_pricelist, self.pricelist_eu)
|
||||
self.assertFalse(self.partner.specific_property_product_pricelist)
|
||||
|
||||
# Set country to KI -> property defaults to highest sequence pricelist
|
||||
with Form(self.partner) as partner_form:
|
||||
partner_form.country_id = self.env.ref('base.ki')
|
||||
self.assertEqual(self.partner.property_product_pricelist, pricelist_1)
|
||||
self.assertFalse(self.partner.specific_property_product_pricelist)
|
||||
|
||||
# Setting non-default pricelist as property should update specific property
|
||||
with Form(self.partner) as partner_form:
|
||||
partner_form.property_product_pricelist = pricelist_2
|
||||
self.assertEqual(self.partner.property_product_pricelist, pricelist_2)
|
||||
self.assertEqual(self.partner.specific_property_product_pricelist, pricelist_2)
|
||||
|
||||
# Changing partner country shouldn't update (specific) pricelist property
|
||||
with Form(self.partner) as partner_form:
|
||||
partner_form.country_id = self.env.ref('base.be')
|
||||
self.assertEqual(self.partner.property_product_pricelist, pricelist_2)
|
||||
self.assertEqual(self.partner.specific_property_product_pricelist, pricelist_2)
|
||||
|
||||
def test_45_property_product_pricelist_config_parameter(self):
|
||||
"""Check that the ``ir.config_parameter`` gets utilized as fallback to both
|
||||
``property_product_pricelist`` & ``specific_property_product_pricelist``.
|
||||
"""
|
||||
pricelist_1, pricelist_2 = self.pricelist, self.sale_pricelist_id
|
||||
self.env['product.pricelist'].search([
|
||||
('id', 'not in', [pricelist_1.id, pricelist_2.id]),
|
||||
]).active = False
|
||||
self.assertEqual(self.partner.property_product_pricelist, pricelist_1)
|
||||
|
||||
self.partner.invalidate_recordset(['property_product_pricelist'])
|
||||
ICP = self.env['ir.config_parameter'].sudo()
|
||||
ICP.set_param('res.partner.property_product_pricelist', pricelist_2.id)
|
||||
with patch.object(
|
||||
self.pricelist.__class__,
|
||||
'_get_partner_pricelist_multi_search_domain_hook',
|
||||
return_value=Domain.FALSE, # ensures pricelist falls back on ICP
|
||||
):
|
||||
with Form(self.partner) as partner_form:
|
||||
self.assertEqual(partner_form.property_product_pricelist, pricelist_2)
|
||||
partner_form.property_product_pricelist = pricelist_1
|
||||
self.assertEqual(self.partner.property_product_pricelist, pricelist_1)
|
||||
self.assertEqual(self.partner.specific_property_product_pricelist, pricelist_1)
|
||||
|
||||
def test_pricelists_multi_comp_checks(self):
|
||||
first_company = self.env.company
|
||||
second_company = self.env['res.company'].create({'name': 'Test Company'})
|
||||
|
||||
shared_pricelist = self.env['product.pricelist'].create({
|
||||
'name': 'Test Multi-comp pricelist',
|
||||
'company_id': False,
|
||||
})
|
||||
second_pricelist = self.env['product.pricelist'].create({
|
||||
'name': f'Second test pricelist{first_company.name}',
|
||||
})
|
||||
|
||||
self.assertEqual(self.pricelist.company_id, first_company)
|
||||
self.assertFalse(shared_pricelist.company_id)
|
||||
self.assertEqual(second_pricelist.company_id, first_company)
|
||||
|
||||
with self.assertRaises(UserError):
|
||||
shared_pricelist.item_ids = [
|
||||
Command.create({
|
||||
'compute_price': 'formula',
|
||||
'base': 'pricelist',
|
||||
'base_pricelist_id': self.pricelist.id,
|
||||
})
|
||||
]
|
||||
|
||||
self.pricelist.item_ids = [
|
||||
Command.create({
|
||||
'compute_price': 'formula',
|
||||
'base': 'pricelist',
|
||||
'base_pricelist_id': shared_pricelist.id,
|
||||
}),
|
||||
Command.create({
|
||||
'compute_price': 'formula',
|
||||
'base': 'pricelist',
|
||||
'base_pricelist_id': second_pricelist.id,
|
||||
})
|
||||
]
|
||||
|
||||
with self.assertRaises(UserError):
|
||||
# Should raise because the pricelist would have a rule based on a pricelist
|
||||
# from another company
|
||||
self.pricelist.company_id = second_company
|
||||
|
||||
def test_pricelists_res_partner_form(self):
|
||||
pricelist_europe = self.pricelist_eu
|
||||
default_pricelist = self.env['product.pricelist'].search([('name', 'ilike', ' ')], limit=1)
|
||||
|
||||
with Form(self.env['res.partner']) as partner_form:
|
||||
partner_form.name = "test"
|
||||
self.assertEqual(partner_form.property_product_pricelist, default_pricelist)
|
||||
|
||||
partner_form.country_id = self.env.ref('base.be')
|
||||
self.assertEqual(partner_form.property_product_pricelist, pricelist_europe)
|
||||
|
||||
partner_form.property_product_pricelist = self.sale_pricelist_id
|
||||
self.assertEqual(partner_form.property_product_pricelist, self.sale_pricelist_id)
|
||||
|
||||
partner = partner_form.save()
|
||||
|
||||
with Form(partner) as partner_form:
|
||||
self.assertEqual(partner_form.property_product_pricelist, self.sale_pricelist_id)
|
||||
|
||||
def test_pricelist_change_to_formula_and_back(self):
|
||||
pricelist_2 = self.env['product.pricelist'].create({
|
||||
'name': 'Sale pricelist 2',
|
||||
'item_ids': [
|
||||
Command.create({
|
||||
'compute_price': 'percentage',
|
||||
'percent_price': 20,
|
||||
'base': 'pricelist',
|
||||
'base_pricelist_id': self.sale_pricelist_id.id,
|
||||
'applied_on': '3_global',
|
||||
}),
|
||||
],
|
||||
})
|
||||
with Form(pricelist_2.item_ids) as item_form:
|
||||
item_form.compute_price = 'formula'
|
||||
item_form.compute_price = 'percentage'
|
||||
item_form.percent_price = 20
|
||||
self.assertFalse(pricelist_2.item_ids.base_pricelist_id.id)
|
||||
|
||||
def test_sync_parent_pricelist(self):
|
||||
"""Check that adding a parent to a partner updates the partner's pricelist."""
|
||||
self.partner.update({
|
||||
'parent_id': False,
|
||||
'specific_property_product_pricelist': self.sale_pricelist_id.id,
|
||||
})
|
||||
self.assertEqual(self.partner.property_product_pricelist, self.sale_pricelist_id)
|
||||
|
||||
company_2 = self.env.company.create({'name': "Company Two"})
|
||||
company_1_b2b_pl, company_2_b2b_pl = self.sale_pricelist_id.create([{
|
||||
'name': f"B2B ({company.name})",
|
||||
'company_id': company.id,
|
||||
} for company in self.env.company + company_2])
|
||||
parent = self.partner.create({
|
||||
'name': f"{self.partner.name}'s Company",
|
||||
'is_company': True,
|
||||
'specific_property_product_pricelist': company_1_b2b_pl.id,
|
||||
})
|
||||
parent.with_company(company_2).specific_property_product_pricelist = company_2_b2b_pl
|
||||
|
||||
self.partner.parent_id = parent
|
||||
self.assertEqual(
|
||||
self.partner.specific_property_product_pricelist,
|
||||
company_1_b2b_pl,
|
||||
"Assigning a parent with a specific pricelist should sync the parent's pricelist",
|
||||
)
|
||||
self.assertEqual(
|
||||
self.partner.with_company(company_2).specific_property_product_pricelist,
|
||||
company_2_b2b_pl,
|
||||
"Company-specific pricelists should get synced on parent assignment",
|
||||
)
|
||||
|
||||
parent.specific_property_product_pricelist = self.sale_pricelist_id
|
||||
self.assertEqual(
|
||||
self.partner.specific_property_product_pricelist,
|
||||
self.sale_pricelist_id,
|
||||
"Setting a specific parent pricelist should update the partner's pricelist",
|
||||
)
|
||||
self.assertEqual(
|
||||
self.partner.with_company(company_2).specific_property_product_pricelist,
|
||||
company_2_b2b_pl,
|
||||
"Assigning pricelists in one company shouldn't impact pricelists in other companies",
|
||||
)
|
||||
|
||||
def test_prevent_pricelist_recursion(self):
|
||||
"""Ensure recursive pricelist rules raise an error on creation."""
|
||||
def create_item_vals(pl_from, pl_to):
|
||||
return {
|
||||
'pricelist_id': pl_from.id,
|
||||
'compute_price': 'formula',
|
||||
'base': 'pricelist',
|
||||
'base_pricelist_id': pl_to.id,
|
||||
'applied_on': '3_global',
|
||||
}
|
||||
Pricelist = self.env['product.pricelist']
|
||||
pl_a, pl_b, pl_c, pl_d = pricelists = Pricelist.create([{
|
||||
'name': f"Pricelist {c}",
|
||||
} for c in 'ABCD'])
|
||||
|
||||
# A -> B -> C -> D
|
||||
Pricelist.item_ids.create([
|
||||
create_item_vals(pl_from, pl_to)
|
||||
for (pl_from, pl_to) in pairwise(pricelists)
|
||||
])
|
||||
|
||||
with self.assertRaises(ValidationError):
|
||||
# A -> B -> C -> D -> D -> _ (recurs)
|
||||
Pricelist.item_ids.create(create_item_vals(pl_d, pl_d))
|
||||
with self.assertRaises(ValidationError):
|
||||
# A -> B -> C -> D -> A -> _ (recurs)
|
||||
Pricelist.item_ids.create(create_item_vals(pl_d, pl_a))
|
||||
with self.assertRaises(ValidationError):
|
||||
# A -> B -> C -> [B -> _, D] (recurs)
|
||||
Pricelist.item_ids.create(create_item_vals(pl_c, pl_b))
|
||||
|
||||
# A -> B, C -> D
|
||||
pl_b.item_ids.unlink()
|
||||
# C -> D -> A -> B
|
||||
Pricelist.item_ids.create(create_item_vals(pl_d, pl_a))
|
||||
# C -> [B, D -> A -> B]
|
||||
Pricelist.item_ids.create(create_item_vals(pl_c, pl_b))
|
||||
|
||||
with self.assertRaises(ValidationError):
|
||||
# C -> [B, D -> A -> [B, C -> _]] (recurs)
|
||||
Pricelist.item_ids.create(create_item_vals(pl_a, pl_c))
|
||||
with self.assertRaises(ValidationError):
|
||||
# C -> [B -> D -> A -> B -> _, D -> _] (recurs)
|
||||
Pricelist.item_ids.create(create_item_vals(pl_b, pl_d))
|
||||
|
||||
def test_pricelist_rule_linked_to_product_variant(self):
|
||||
"""Verify that pricelist rules assigned to a variant remain linked after write."""
|
||||
self.product_sofa_red.pricelist_rule_ids = [
|
||||
Command.create({
|
||||
'applied_on': '0_product_variant',
|
||||
'product_id': self.product_sofa_red.id,
|
||||
'compute_price': 'fixed',
|
||||
'fixed_price': 99.9,
|
||||
'pricelist_id': self.pricelist.id,
|
||||
}),
|
||||
Command.create({
|
||||
'applied_on': '0_product_variant',
|
||||
'product_id': self.product_sofa_red.id,
|
||||
'compute_price': 'fixed',
|
||||
'fixed_price': 89.9,
|
||||
'pricelist_id': self.pricelist.id,
|
||||
}),
|
||||
]
|
||||
self.assertEqual(len(self.product_sofa_red.pricelist_rule_ids), 2)
|
||||
first_rule, second_rule = self.product_sofa_red.pricelist_rule_ids
|
||||
self.product_sofa_red.pricelist_rule_ids = [
|
||||
Command.update(first_rule.id, {'fixed_price': 79.9}),
|
||||
Command.unlink(second_rule.id),
|
||||
]
|
||||
self.assertEqual(len(self.product_sofa_red.pricelist_rule_ids), 1)
|
||||
self.assertEqual(self.pricelist.item_ids.fixed_price, 79.9)
|
||||
self.assertIn(self.product_sofa_red, self.pricelist.item_ids.product_id)
|
||||
|
||||
# Update of template-based rules through variant form
|
||||
self.product_template_sofa.pricelist_rule_ids = [
|
||||
# Template-based rule (can be edited through the variants)
|
||||
Command.create({
|
||||
'applied_on': '1_product',
|
||||
'product_tmpl_id': self.product_template_sofa.id,
|
||||
'pricelist_id': self.pricelist.id,
|
||||
}),
|
||||
# Rule on another variant than the one being edited. It cannot be edited through the
|
||||
# current variant and therefore shouldn't change when another variant rules are edited.
|
||||
Command.create({
|
||||
'applied_on': '0_product_variant',
|
||||
'product_id': self.product_sofa_blue.id,
|
||||
'compute_price': 'fixed',
|
||||
'fixed_price': 89.9,
|
||||
'pricelist_id': self.pricelist.id,
|
||||
})
|
||||
]
|
||||
self.assertEqual(len(self.product_template_sofa.pricelist_rule_ids), 3)
|
||||
template_rule = self.product_template_sofa.pricelist_rule_ids.filtered(
|
||||
lambda item: not item.product_id
|
||||
)
|
||||
self.assertEqual(len(self.product_sofa_red.pricelist_rule_ids), 2)
|
||||
self.product_sofa_red.pricelist_rule_ids = [
|
||||
Command.update(template_rule.id, {'fixed_price': 133}),
|
||||
]
|
||||
self.assertEqual(template_rule.fixed_price, 133)
|
||||
|
||||
self.product_sofa_red.pricelist_rule_ids = [
|
||||
Command.unlink(template_rule.id),
|
||||
]
|
||||
self.assertFalse(template_rule.exists())
|
||||
|
||||
self.assertTrue(self.product_sofa_blue.pricelist_rule_ids)
|
||||
self.assertEqual(len(self.product_template_sofa.pricelist_rule_ids), 2)
|
||||
|
||||
def test_pricelist_applied_on_product_variant(self):
|
||||
# product template with variants
|
||||
sofa_1 = self.product_template_sofa.product_variant_ids[0]
|
||||
# create pricelist with rule on template
|
||||
pricelist = self.env["product.pricelist"].create(
|
||||
{
|
||||
"name": "Pricelist for Acoustic Bloc Screens",
|
||||
"item_ids": [
|
||||
Command.create(
|
||||
{
|
||||
"compute_price": "fixed",
|
||||
"fixed_price": 123,
|
||||
"base": "list_price",
|
||||
"applied_on": "1_product",
|
||||
"product_tmpl_id": self.product_template_sofa.id,
|
||||
}
|
||||
),
|
||||
],
|
||||
}
|
||||
)
|
||||
# open rule form and change rule to apply on variant instead of template
|
||||
with Form(pricelist.item_ids) as item_form:
|
||||
item_form.product_id = sofa_1
|
||||
# check that `applied_on` changed to variant
|
||||
self.assertEqual(pricelist.item_ids.applied_on, "0_product_variant")
|
||||
# re-edit rule to apply on template again by clearing `product_id`
|
||||
with Form(pricelist.item_ids) as item_form:
|
||||
item_form.product_id = self.env["product.product"]
|
||||
# check that `applied_on` changed to template
|
||||
self.assertEqual(pricelist.item_ids.applied_on, "1_product")
|
||||
# check that product_id is cleared
|
||||
self.assertFalse(pricelist.item_ids.product_id)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.product.tests.common import ProductCommon
|
||||
|
||||
|
||||
class TestPricelistAutoCreation(ProductCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
# Only one currency enabled and used on companies (multi-curr disabled)
|
||||
cls.currency_euro = cls._enable_currency('EUR')
|
||||
cls.currency_usd = cls.env['res.currency'].search([('name', '=', "USD")])
|
||||
cls.env['res.company'].search([]).currency_id = cls.currency_euro
|
||||
cls.env['res.currency'].search([('name', '!=', 'EUR')]).action_archive()
|
||||
|
||||
# Disabled pricelists feature
|
||||
cls.group_user = cls.env.ref('base.group_user').sudo()
|
||||
cls.group_user._remove_group(cls.group_product_pricelist)
|
||||
cls.env['product.pricelist'].search([]).unlink()
|
||||
|
||||
def test_inactive_curr_set_on_company(self):
|
||||
"""Make sure that when setting an inactive currency on a company, the activation of the
|
||||
multi-currency group won't
|
||||
"""
|
||||
self.env.company.currency_id = self.currency_usd
|
||||
self.assertFalse(
|
||||
self.env['product.pricelist'].search([
|
||||
('currency_id.name', '=', 'EUR'),
|
||||
('company_id', '=', self.env.company.id),
|
||||
])
|
||||
)
|
||||
self.assertTrue(self.currency_usd.active)
|
||||
self.assertTrue(
|
||||
self.env['product.pricelist'].search([
|
||||
('currency_id.name', '=', 'USD'),
|
||||
('company_id', '=', self.env.company.id),
|
||||
])
|
||||
)
|
||||
# self.env.user.clear_caches()
|
||||
# self.group_user.invalidate_recordset()
|
||||
# self.assertTrue(
|
||||
# self.group_product_pricelist in self.group_user.implied_ids
|
||||
# )
|
||||
|
|
@ -2,25 +2,21 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import time
|
||||
from psycopg2 import IntegrityError
|
||||
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
from odoo.fields import Command
|
||||
from odoo.tests import tagged, TransactionCase
|
||||
from odoo.tests import tagged
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
from odoo.addons.base.tests.common import DISABLED_MAIL_CONTEXT
|
||||
from odoo.addons.base.tests.common import BaseCommon
|
||||
|
||||
|
||||
class TestProductAttributeValueCommon(TransactionCase):
|
||||
class TestProductAttributeValueCommon(BaseCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
cls.env = cls.env['base'].with_context(**DISABLED_MAIL_CONTEXT).env
|
||||
cls.env.company.country_id = cls.env.ref('base.us')
|
||||
|
||||
cls.computer = cls.env['product.template'].create({
|
||||
'name': 'Super Computer',
|
||||
'list_price': 2000,
|
||||
|
|
@ -31,6 +27,8 @@ class TestProductAttributeValueCommon(TransactionCase):
|
|||
cls.ram_attribute,
|
||||
cls.hdd_attribute,
|
||||
cls.size_attribute,
|
||||
cls.extras_attribute,
|
||||
cls.operating_system_attribute,
|
||||
) = cls.env['product.attribute'].create([{
|
||||
'name': 'Memory',
|
||||
'sequence': 1,
|
||||
|
|
@ -95,12 +93,44 @@ class TestProductAttributeValueCommon(TransactionCase):
|
|||
'sequence': 3,
|
||||
}),
|
||||
],
|
||||
}, {
|
||||
'name': "Extras",
|
||||
'sequence': 5,
|
||||
'display_type': 'multi',
|
||||
'create_variant': 'no_variant',
|
||||
'value_ids': [
|
||||
Command.create({
|
||||
'name': "CPU overclock",
|
||||
'sequence': 1,
|
||||
}),
|
||||
Command.create({
|
||||
'name': "RAM overclock",
|
||||
'sequence': 2,
|
||||
}),
|
||||
],
|
||||
}, {
|
||||
'name': "Operating System",
|
||||
'sequence': 6,
|
||||
'display_type': 'multi',
|
||||
'create_variant': 'no_variant',
|
||||
'value_ids': [
|
||||
Command.create({
|
||||
'name': "Linux",
|
||||
'sequence': 1,
|
||||
}),
|
||||
Command.create({
|
||||
'name': "Windows",
|
||||
'sequence': 2,
|
||||
}),
|
||||
],
|
||||
}])
|
||||
|
||||
cls.ssd_256, cls.ssd_512 = cls.ssd_attribute.value_ids
|
||||
cls.ram_8, cls.ram_16, cls.ram_32 = cls.ram_attribute.value_ids
|
||||
cls.hdd_1, cls.hdd_2, cls.hdd_4 = cls.hdd_attribute.value_ids
|
||||
cls.size_m, cls.size_l, cls.size_xl = cls.size_attribute.value_ids
|
||||
cls.extra_cpu, cls.extra_ram = cls.extras_attribute.value_ids
|
||||
cls.linux_operating_system, cls.windows_operating_system = cls.operating_system_attribute.value_ids
|
||||
|
||||
cls.COMPUTER_SSD_PTAL_VALUES = {
|
||||
'product_tmpl_id': cls.computer.id,
|
||||
|
|
@ -117,6 +147,16 @@ class TestProductAttributeValueCommon(TransactionCase):
|
|||
'attribute_id': cls.hdd_attribute.id,
|
||||
'value_ids': [Command.set([cls.hdd_1.id, cls.hdd_2.id, cls.hdd_4.id])],
|
||||
}
|
||||
cls.COMPUTER_EXTRAS_PTAL_VALUES = {
|
||||
'product_tmpl_id': cls.computer.id,
|
||||
'attribute_id': cls.extras_attribute.id,
|
||||
'value_ids': [Command.set([cls.extra_cpu.id, cls.extra_ram.id])],
|
||||
}
|
||||
cls.COMPUTER_OPERATING_SYSTEM_VALUES = {
|
||||
'product_tmpl_id': cls.computer.id,
|
||||
'attribute_id': cls.operating_system_attribute.id,
|
||||
'value_ids': [Command.set([cls.windows_operating_system.id, cls.linux_operating_system.id])],
|
||||
}
|
||||
|
||||
cls._add_computer_attribute_lines()
|
||||
|
||||
|
|
@ -137,10 +177,14 @@ class TestProductAttributeValueCommon(TransactionCase):
|
|||
cls.computer_ssd_attribute_lines,
|
||||
cls.computer_ram_attribute_lines,
|
||||
cls.computer_hdd_attribute_lines,
|
||||
cls.computer_extras_attribute_lines,
|
||||
cls.computer_operating_system_lines,
|
||||
) = cls.env['product.template.attribute.line'].create([
|
||||
cls.COMPUTER_SSD_PTAL_VALUES,
|
||||
cls.COMPUTER_RAM_PTAL_VALUES,
|
||||
cls.COMPUTER_HDD_PTAL_VALUES,
|
||||
cls.COMPUTER_EXTRAS_PTAL_VALUES,
|
||||
cls.COMPUTER_OPERATING_SYSTEM_VALUES,
|
||||
])
|
||||
|
||||
# Setup extra prices
|
||||
|
|
@ -446,7 +490,7 @@ class TestProductAttributeValueConfig(TestProductAttributeValueCommon):
|
|||
self.assertEqual(self.computer._get_first_possible_combination(), computer_ssd_512 + computer_ram_32 + computer_hdd_4)
|
||||
|
||||
# Not possible to add an exclusion when only one variant is left -> it deletes the product template associated to it
|
||||
with self.assertRaises(UserError), self.cr.savepoint():
|
||||
with self.assertRaises(UserError):
|
||||
self._add_exclude(computer_ram_32, computer_hdd_4)
|
||||
|
||||
# If an exclusion rule deletes all variants at once it does not delete the template.
|
||||
|
|
@ -717,12 +761,6 @@ class TestProductAttributeValueConfig(TestProductAttributeValueCommon):
|
|||
with self.assertRaises(UserError, msg="can't change the product of a product template attribute value"):
|
||||
self.computer_ram_attribute_lines.product_template_value_ids[0].product_tmpl_id = self.computer_case.id
|
||||
|
||||
with mute_logger('odoo.sql_db'), self.assertRaises(IntegrityError, msg="can't have two values with the same name for the same attribute"):
|
||||
self.env['product.attribute.value'].create({
|
||||
'name': '32 GB',
|
||||
'attribute_id': self.ram_attribute.id,
|
||||
})
|
||||
|
||||
@mute_logger('odoo.models.unlink')
|
||||
def test_inactive_related_product_update(self):
|
||||
"""
|
||||
|
|
@ -768,3 +806,53 @@ class TestProductAttributeValueConfig(TestProductAttributeValueCommon):
|
|||
'price_extra'
|
||||
)
|
||||
self.assertEqual(extra_prices, copied_extra_prices)
|
||||
|
||||
def test_04_create_product_variant_non_dynamic(self):
|
||||
"""The goal of this test is to make sure the _create_product_variant does
|
||||
not create variant if the type is not dynamic. It can however return a
|
||||
variant if it already exists."""
|
||||
computer_ssd_256 = self._get_product_template_attribute_value(self.ssd_256)
|
||||
computer_ram_8 = self._get_product_template_attribute_value(self.ram_8)
|
||||
computer_ram_16 = self._get_product_template_attribute_value(self.ram_16)
|
||||
computer_hdd_1 = self._get_product_template_attribute_value(self.hdd_1)
|
||||
self._add_exclude(computer_ram_16, computer_hdd_1)
|
||||
|
||||
# CASE: variant is already created, it should return it
|
||||
combination = computer_ssd_256 + computer_ram_8 + computer_hdd_1
|
||||
variant1 = self.computer._get_variant_for_combination(combination)
|
||||
self.assertEqual(self.computer._create_product_variant(combination), variant1)
|
||||
|
||||
# CASE: variant does not exist, but template is non-dynamic, so it
|
||||
# should not create it
|
||||
Product = self.env['product.product']
|
||||
variant1.unlink()
|
||||
self.assertEqual(self.computer._create_product_variant(combination), Product)
|
||||
|
||||
def test_05_create_product_variant_dynamic(self):
|
||||
"""The goal of this test is to make sure the _create_product_variant does
|
||||
work with dynamic. If the combination is possible, it should create it.
|
||||
If it's not possible, it should not create it."""
|
||||
self.computer_hdd_attribute_lines.write({'active': False})
|
||||
self.hdd_attribute.create_variant = 'dynamic'
|
||||
self._add_hdd_attribute_line()
|
||||
|
||||
computer_ssd_256 = self._get_product_template_attribute_value(self.ssd_256)
|
||||
computer_ram_8 = self._get_product_template_attribute_value(self.ram_8)
|
||||
computer_ram_16 = self._get_product_template_attribute_value(self.ram_16)
|
||||
computer_hdd_1 = self._get_product_template_attribute_value(self.hdd_1)
|
||||
self._add_exclude(computer_ram_16, computer_hdd_1)
|
||||
|
||||
# CASE: variant does not exist, but combination is not possible
|
||||
# so it should not create it
|
||||
impossible_combination = computer_ssd_256 + computer_ram_16 + computer_hdd_1
|
||||
Product = self.env['product.product']
|
||||
self.assertEqual(self.computer._create_product_variant(impossible_combination), Product)
|
||||
|
||||
# CASE: the variant does not exist, and the combination is possible, so
|
||||
# it should create it
|
||||
combination = computer_ssd_256 + computer_ram_8 + computer_hdd_1
|
||||
variant = self.computer._create_product_variant(combination)
|
||||
self.assertTrue(variant)
|
||||
|
||||
# CASE: the variant already exists, so it should return it
|
||||
self.assertEqual(variant, self.computer._create_product_variant(combination))
|
||||
|
|
|
|||
|
|
@ -0,0 +1,148 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from freezegun import freeze_time
|
||||
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
from odoo.fields import Command
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
from odoo.addons.product.tests.common import ProductCommon
|
||||
|
||||
|
||||
class TestProductCombo(ProductCommon):
|
||||
|
||||
def test_combo_item_count(self):
|
||||
combo = self.env['product.combo'].create({
|
||||
'name': "Test combo",
|
||||
'combo_item_ids': [
|
||||
Command.create({'product_id': self._create_product().id}),
|
||||
Command.create({'product_id': self._create_product().id}),
|
||||
Command.create({'product_id': self._create_product().id}),
|
||||
],
|
||||
})
|
||||
|
||||
self.assertEqual(combo.combo_item_count, 3)
|
||||
|
||||
def test_currency_without_company_set(self):
|
||||
self.setup_main_company(currency_code='GBP')
|
||||
|
||||
combo = self.env['product.combo'].create({
|
||||
'name': "Test combo",
|
||||
'combo_item_ids': [Command.create({'product_id': self.product.id})],
|
||||
})
|
||||
|
||||
self.assertEqual(combo.currency_id.name, 'GBP')
|
||||
|
||||
def test_currency_with_company_set(self):
|
||||
company_eur = self._create_company(
|
||||
name="Company EUR", currency_id=self._enable_currency('EUR').id
|
||||
)
|
||||
company_isk = self._create_company(
|
||||
name="Company ISK", currency_id=self._enable_currency('ISK').id
|
||||
)
|
||||
|
||||
combo = self.env['product.combo'].create({
|
||||
'name': "Test combo",
|
||||
'company_id': company_eur.id,
|
||||
'combo_item_ids': [Command.create({'product_id': self.product.id})],
|
||||
})
|
||||
|
||||
self.assertEqual(combo.currency_id.name, 'EUR')
|
||||
|
||||
combo.company_id = company_isk
|
||||
|
||||
self.assertEqual(combo.currency_id.name, 'ISK')
|
||||
|
||||
@freeze_time('2000-01-01')
|
||||
def test_base_price_multiple_currencies(self):
|
||||
self.setup_main_company(currency_code='GBP')
|
||||
currency_eur = self._enable_currency('EUR')
|
||||
company = self._create_company(currency_id=currency_eur.id)
|
||||
# For the sake of this test, we consider that 1 EUR is equivalent to 0.5 GBP.
|
||||
currency_eur.rate_ids = [Command.create({
|
||||
'name': '2000-01-01', 'rate': 2, 'company_id': company.id
|
||||
})]
|
||||
product_gbp = self._create_product(list_price=50)
|
||||
product_eur_a = self._create_product(company_id=company.id, list_price=90)
|
||||
product_eur_b = self._create_product(company_id=company.id, list_price=110)
|
||||
|
||||
combo = self.env['product.combo'].create({
|
||||
'name': "Test combo",
|
||||
'company_id': company.id,
|
||||
'combo_item_ids': [
|
||||
Command.create({'product_id': product_gbp.id}),
|
||||
Command.create({'product_id': product_eur_a.id}),
|
||||
Command.create({'product_id': product_eur_b.id}),
|
||||
],
|
||||
})
|
||||
|
||||
self.assertEqual(combo.base_price, 90)
|
||||
|
||||
def test_empty_combo_items_raises(self):
|
||||
with self.assertRaises(ValidationError):
|
||||
self.env['product.combo'].create({
|
||||
'name': "Test combo",
|
||||
'combo_item_ids': [],
|
||||
})
|
||||
|
||||
def test_duplicate_combo_items_raises(self):
|
||||
with self.assertRaises(ValidationError):
|
||||
self.env['product.combo'].create({
|
||||
'name': "Test combo",
|
||||
'combo_item_ids': [
|
||||
Command.create({'product_id': self.product.id}),
|
||||
Command.create({'product_id': self.product.id}),
|
||||
],
|
||||
})
|
||||
|
||||
@mute_logger('odoo.sql_db')
|
||||
def test_nested_combos_raises(self):
|
||||
combo = self.env['product.combo'].create({
|
||||
'name': "Test combo",
|
||||
'combo_item_ids': [Command.create({'product_id': self.product.id})],
|
||||
})
|
||||
combo_product = self._create_product(type='combo', combo_ids=[Command.link(combo.id)])
|
||||
|
||||
with self.assertRaises(ValidationError):
|
||||
self.env['product.combo'].create({
|
||||
'name': "Test combo",
|
||||
'combo_item_ids': [Command.create({'product_id': combo_product.id})],
|
||||
})
|
||||
|
||||
def test_multi_company_consistency(self):
|
||||
company_a = self._create_company(name="Company A")
|
||||
company_b = self._create_company(name="Company B")
|
||||
product_in_company_a = self._create_product(company_id=company_a.id)
|
||||
|
||||
# Raise if we try to create a combo in company B with a product in company A.
|
||||
with self.assertRaises(UserError):
|
||||
self.env['product.combo'].create({
|
||||
'name': "Test combo",
|
||||
'company_id': company_b.id,
|
||||
'combo_item_ids': [Command.create({'product_id': product_in_company_a.id})],
|
||||
})
|
||||
# Don't raise if we try to create a combo in company A with a product in company A.
|
||||
combo_in_company_a = self.env['product.combo'].create({
|
||||
'name': "Test combo",
|
||||
'company_id': company_a.id,
|
||||
'combo_item_ids': [Command.create({'product_id': product_in_company_a.id})],
|
||||
})
|
||||
|
||||
# Raise if we try to create a combo product in company B with a combo in company A.
|
||||
with self.assertRaises(UserError):
|
||||
self._create_product(
|
||||
company_id=company_b.id,
|
||||
type='combo',
|
||||
combo_ids=[Command.link(combo_in_company_a.id)],
|
||||
)
|
||||
# Don't raise if we try to create a combo product in company A with a combo in company A.
|
||||
self._create_product(
|
||||
company_id=company_a.id,
|
||||
type='combo',
|
||||
combo_ids=[Command.link(combo_in_company_a.id)],
|
||||
)
|
||||
# Raise if we try to update a combo product in company A with a combo without company.
|
||||
with self.assertRaises(UserError):
|
||||
combo_in_company_a.write({
|
||||
'company_id': False,
|
||||
})
|
||||
|
|
@ -4,7 +4,9 @@
|
|||
from datetime import datetime
|
||||
import time
|
||||
|
||||
from odoo.fields import Command, first
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.fields import Command
|
||||
from odoo.tests import Form
|
||||
from odoo.tools import float_compare
|
||||
|
||||
from odoo.addons.product.tests.common import ProductCommon
|
||||
|
|
@ -16,13 +18,6 @@ class TestProductPricelist(ProductCommon):
|
|||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
# Required for some of the tests below
|
||||
# Breaks if run after account installation (and with demo data)
|
||||
# as generic chart of accounts changes company currency to USD
|
||||
# therefore the test must stay at_install until adapted to work with USD
|
||||
# or according to current currency.
|
||||
cls._use_currency('EUR')
|
||||
|
||||
cls.category_5_id = cls.env['product.category'].create({
|
||||
'name': 'Office Furniture',
|
||||
'parent_id': cls.product_category.id
|
||||
|
|
@ -207,11 +202,11 @@ class TestProductPricelist(ProductCommon):
|
|||
product = self.product_multi_price
|
||||
price = self.customer_pricelist._get_product_price(product, quantity=1.0)
|
||||
msg = "Wrong price: Multi Product Price. should be 99 instead of %s" % price
|
||||
self.assertEqual(float_compare(price, 99, precision_digits=2), 0)
|
||||
self.assertEqual(float_compare(price, 99, precision_digits=2), 0, msg)
|
||||
|
||||
price = self.business_pricelist._get_product_price(product, quantity=1.0)
|
||||
msg = "Wrong price: Multi Product Price. should be 50 instead of %s" % price
|
||||
self.assertEqual(float_compare(price, 50, precision_digits=2), 0)
|
||||
self.assertEqual(float_compare(price, 50, precision_digits=2), 0, msg)
|
||||
|
||||
def test_20_price_different_currency_pricelist(self):
|
||||
pricelist = self.env['product.pricelist'].create({
|
||||
|
|
@ -257,6 +252,39 @@ class TestProductPricelist(ProductCommon):
|
|||
# product price use the currency of the pricelist
|
||||
self.assertEqual(price, 10090)
|
||||
|
||||
def test_price_without_pricelist_fallback_product_price(self):
|
||||
ProductPricelist = self.env['product.pricelist']
|
||||
spam = self.env['product.product'].create({
|
||||
'name': '1 tonne of spam',
|
||||
'uom_id': self.uom_ton.id,
|
||||
'list_price': 100,
|
||||
'type': 'consu'
|
||||
})
|
||||
self.assertEqual(
|
||||
ProductPricelist._get_product_price(self.monitor, quantity=1.0),
|
||||
self.monitor.list_price,
|
||||
msg="without pricelist, the price should be the same as the list price",
|
||||
)
|
||||
self.assertEqual(
|
||||
ProductPricelist._get_product_price(self.monitor, quantity=1.0, currency=self.new_currency),
|
||||
self.monitor.list_price*10,
|
||||
msg="without pricelist but with a currency different than the product one, the price "
|
||||
"should be the same as the list price converted with the currency rate",
|
||||
)
|
||||
self.assertEqual(
|
||||
ProductPricelist._get_product_price(spam, quantity=1.0, uom=self.uom_kgm),
|
||||
spam.list_price / 1000,
|
||||
msg="the product price should be converted using the specified uom",
|
||||
)
|
||||
self.assertEqual(
|
||||
ProductPricelist._get_product_price(
|
||||
spam, quantity=1.0, currency=self.new_currency, uom=self.uom_kgm
|
||||
),
|
||||
spam.list_price / 100,
|
||||
msg="the product price should be converted using the specified uom and converted to the"
|
||||
" correct currency",
|
||||
)
|
||||
|
||||
def test_30_pricelist_delete(self):
|
||||
""" Test that `unlink` on many records doesn't raise a RecursionError. """
|
||||
self.customer_pricelist = self.env['product.pricelist'].create({
|
||||
|
|
@ -264,7 +292,6 @@ class TestProductPricelist(ProductCommon):
|
|||
'item_ids': [
|
||||
Command.create({
|
||||
'compute_price': 'formula',
|
||||
'base': 'pricelist',
|
||||
}),
|
||||
] * 101,
|
||||
})
|
||||
|
|
@ -273,9 +300,9 @@ class TestProductPricelist(ProductCommon):
|
|||
def test_40_pricelist_item_min_quantity_precision(self):
|
||||
"""Test that the min_quantity has the precision of Product UoM."""
|
||||
# Arrange: Change precision digits
|
||||
uom_precision = self.env.ref("product.decimal_product_uom")
|
||||
uom_precision = self.env.ref("uom.decimal_product_uom")
|
||||
uom_precision.digits = 3
|
||||
pricelist_item = first(self.customer_pricelist.item_ids[0])
|
||||
pricelist_item = self.customer_pricelist.item_ids[0]
|
||||
precise_value = 1.234
|
||||
|
||||
# Act: Set a value for the increased precision
|
||||
|
|
@ -283,3 +310,64 @@ class TestProductPricelist(ProductCommon):
|
|||
|
||||
# Assert: The set value is kept
|
||||
self.assertEqual(pricelist_item.min_quantity, precise_value)
|
||||
|
||||
def test_remove_product_on_0_product_variant_applied_on_rule(self):
|
||||
"""Test generation of applied on based on rule data"""
|
||||
self.pricelist_item = self.env['product.pricelist.item'].create({
|
||||
'pricelist_id': self.pricelist.id,
|
||||
'applied_on': '0_product_variant',
|
||||
'product_tmpl_id': self.product.product_tmpl_id.id,
|
||||
'product_id': self.product.id,
|
||||
'compute_price': 'fixed',
|
||||
'fixed_price': 50,
|
||||
'base': 'list_price',
|
||||
})
|
||||
|
||||
self.assertEqual(self.pricelist_item.applied_on, '0_product_variant')
|
||||
with Form(self.pricelist_item) as form:
|
||||
form.product_tmpl_id = self.env['product.template']
|
||||
# Test values after on change
|
||||
self.assertFalse(self.pricelist_item.product_tmpl_id)
|
||||
self.assertFalse(self.pricelist_item.product_id)
|
||||
self.assertEqual(self.pricelist_item.applied_on, '3_global')
|
||||
|
||||
def test_pricelist_sync_on_partners(self):
|
||||
ResPartner = self.env['res.partner']
|
||||
|
||||
company_1, company_2 = self.env['res.company'].create([
|
||||
{'name': 'company_1'},
|
||||
{'name': 'company_2'},
|
||||
])
|
||||
|
||||
test_partner_company = ResPartner.create({
|
||||
'name': 'This company',
|
||||
'is_company': True,
|
||||
})
|
||||
test_partner_company.with_company(company_1).property_product_pricelist = self.business_pricelist.id
|
||||
test_partner_company.with_company(company_2).property_product_pricelist = self.customer_pricelist.id
|
||||
|
||||
child_address = ResPartner.create({
|
||||
'name': 'Contact',
|
||||
'parent_id': test_partner_company.id,
|
||||
})
|
||||
self.assertEqual(
|
||||
child_address.property_product_pricelist,
|
||||
test_partner_company.property_product_pricelist,
|
||||
)
|
||||
self.assertEqual(
|
||||
child_address.with_company(company_1).property_product_pricelist,
|
||||
self.business_pricelist,
|
||||
)
|
||||
self.assertEqual(
|
||||
child_address.with_company(company_2).property_product_pricelist,
|
||||
self.customer_pricelist,
|
||||
)
|
||||
|
||||
def test_no_negative_cost(self):
|
||||
form = Form(self.product)
|
||||
with self.assertRaises(ValidationError):
|
||||
form.standard_price = -5
|
||||
|
||||
form = Form(self.product.product_tmpl_id)
|
||||
with self.assertRaises(ValidationError):
|
||||
form.standard_price = -5
|
||||
|
|
|
|||
|
|
@ -0,0 +1,87 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import time
|
||||
|
||||
from odoo.fields import Command
|
||||
from odoo.tests import tagged
|
||||
|
||||
from odoo.addons.product.tests.common import ProductCommon
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestProductRounding(ProductCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
# test-specific currencies
|
||||
cls.currency_jpy = cls.env['res.currency'].create({
|
||||
'name': 'JPX',
|
||||
'symbol': '¥',
|
||||
'rounding': 1.0,
|
||||
'rate_ids': [Command.create({'rate': 133.6200, 'name': time.strftime('%Y-%m-%d')})],
|
||||
})
|
||||
|
||||
cls.currency_cad = cls.env['res.currency'].create({
|
||||
'name': 'CXD',
|
||||
'symbol': '$',
|
||||
'rounding': 0.01,
|
||||
'rate_ids': [Command.create({'rate': 1.338800, 'name': time.strftime('%Y-%m-%d')})],
|
||||
})
|
||||
|
||||
cls.pricelist_usd = cls.pricelist
|
||||
|
||||
cls.pricelist_jpy = cls.env['product.pricelist'].create({
|
||||
'name': 'Pricelist Testing JPY',
|
||||
'currency_id': cls.currency_jpy.id,
|
||||
})
|
||||
|
||||
cls.pricelist_cad = cls.env['product.pricelist'].create({
|
||||
'name': 'Pricelist Testing CAD',
|
||||
'currency_id': cls.currency_cad.id,
|
||||
})
|
||||
|
||||
cls.product_1_dollar = cls.env['product.product'].create({
|
||||
'name': 'Test Product $1',
|
||||
'list_price': 1.00,
|
||||
'categ_id': cls.product_category.id,
|
||||
})
|
||||
|
||||
cls.product_100_dollars = cls.env['product.product'].create({
|
||||
'name': 'Test Product $100',
|
||||
'list_price': 100.00,
|
||||
'categ_id': cls.product_category.id,
|
||||
})
|
||||
|
||||
def test_no_discount_1_dollar_product(self):
|
||||
"""Ensure that no discount is applied when there shouldn't be, even for very small amounts."""
|
||||
product = self.product_1_dollar
|
||||
|
||||
product_in_jpy = product.with_context(pricelist=self.pricelist_jpy.id)
|
||||
discount_jpy = product_in_jpy._get_contextual_discount()
|
||||
self.assertAlmostEqual(discount_jpy, 0.0, places=6, msg="No discount should be applied for $1 product in Testing JPY.")
|
||||
|
||||
product_in_usd = product.with_context(pricelist=self.pricelist_usd.id)
|
||||
discount_usd = product_in_usd._get_contextual_discount()
|
||||
self.assertAlmostEqual(discount_usd, 0.0, places=6, msg="No discount should be applied for $1 product in USD.")
|
||||
|
||||
product_in_cad = product.with_context(pricelist=self.pricelist_cad.id)
|
||||
discount_cad = product_in_cad._get_contextual_discount()
|
||||
self.assertAlmostEqual(discount_cad, 0.0, places=6, msg="No discount should be applied for $1 product in Testing CAD.")
|
||||
|
||||
def test_no_discount_100_dollars_product(self):
|
||||
"""Ensure that no discount is applied when there shouldn't be, even for very small amounts."""
|
||||
product = self.product_100_dollars
|
||||
|
||||
product_in_jpy = product.with_context(pricelist=self.pricelist_jpy.id)
|
||||
discount_jpy = product_in_jpy._get_contextual_discount()
|
||||
self.assertAlmostEqual(discount_jpy, 0.0, places=6, msg="No discount should be applied for $100 product in Testing JPY.")
|
||||
|
||||
product_in_usd = product.with_context(pricelist=self.pricelist_usd.id)
|
||||
discount_usd = product_in_usd._get_contextual_discount()
|
||||
self.assertAlmostEqual(discount_usd, 0.0, places=6, msg="No discount should be applied for $100 product in USD.")
|
||||
|
||||
product_in_cad = product.with_context(pricelist=self.pricelist_cad.id)
|
||||
discount_cad = product_in_cad._get_contextual_discount()
|
||||
self.assertAlmostEqual(discount_cad, 0.0, places=6, msg="No discount should be applied for $100 product in Testing CAD.")
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.fields import first, Command
|
||||
from odoo.fields import Command
|
||||
from odoo.tests import tagged, TransactionCase
|
||||
from odoo.tools import float_compare
|
||||
|
||||
|
|
@ -49,26 +49,20 @@ class TestSeller(TransactionCase):
|
|||
(0, 0, {'partner_id': self.asustec.id, 'product_code': 'NO', 'company_id': False}),
|
||||
]})
|
||||
|
||||
names = self.product_consu.with_context(
|
||||
name = self.product_consu.with_context(
|
||||
partner_id=self.asustec.id,
|
||||
).name_get()
|
||||
ref = set([x[1] for x in names])
|
||||
self.assertEqual(len(names), 3, "3 vendor references should have been found")
|
||||
self.assertEqual(ref, {'[A] Boudin', '[B] Boudin', '[NO] Boudin'}, "Incorrect vendor reference list")
|
||||
names = self.product_consu.with_context(
|
||||
).display_name
|
||||
self.assertEqual(name, '[A] Boudin, [B] Boudin, [NO] Boudin', "Incorrect vendor reference list")
|
||||
name = self.product_consu.with_context(
|
||||
partner_id=self.asustec.id,
|
||||
company_id=company_a.id,
|
||||
).name_get()
|
||||
ref = set([x[1] for x in names])
|
||||
self.assertEqual(len(names), 2, "2 vendor references should have been found")
|
||||
self.assertEqual(ref, {'[A] Boudin', '[NO] Boudin'}, "Incorrect vendor reference list")
|
||||
names = self.product_consu.with_context(
|
||||
).display_name
|
||||
self.assertEqual(name, '[A] Boudin, [NO] Boudin', "Incorrect vendor reference list")
|
||||
name = self.product_consu.with_context(
|
||||
partner_id=self.asustec.id,
|
||||
company_id=company_b.id,
|
||||
).name_get()
|
||||
ref = set([x[1] for x in names])
|
||||
self.assertEqual(len(names), 2, "2 vendor references should have been found")
|
||||
self.assertEqual(ref, {'[B] Boudin', '[NO] Boudin'}, "Incorrect vendor reference list")
|
||||
).display_name
|
||||
self.assertEqual(name, '[B] Boudin, [NO] Boudin', "Incorrect vendor reference list")
|
||||
|
||||
def test_30_select_seller(self):
|
||||
self.res_partner_1 = self.asustec
|
||||
|
|
@ -122,10 +116,25 @@ class TestSeller(TransactionCase):
|
|||
msg = "Wrong cost price: LCD Monitor if more than 3 Unit.should be 785 instead of %s" % price
|
||||
self.assertEqual(float_compare(price, 785, precision_digits=2), 0, msg)
|
||||
|
||||
def test_31_select_seller(self):
|
||||
"""Check that the right seller is selected, even when the decimal precision of
|
||||
Product Price is higher than the precision of the currency.
|
||||
"""
|
||||
self.env.ref('product.decimal_price').digits = 3
|
||||
partner = self.asustec
|
||||
product = self.product_consu
|
||||
self.env['product.supplierinfo'].create([{
|
||||
'partner_id': partner.id,
|
||||
'product_tmpl_id': product.product_tmpl_id.id,
|
||||
'price': price,
|
||||
} for price in (0.025, 0.022, 0.020)])
|
||||
price = product._select_seller(partner_id=partner, quantity=201).price
|
||||
self.assertAlmostEqual(price, 0.02, places=3, msg="Lowest price should be returned")
|
||||
|
||||
def test_40_seller_min_qty_precision(self):
|
||||
"""Test that the min_qty has the precision of Product UoM."""
|
||||
# Arrange: Change precision digits
|
||||
uom_precision = self.env.ref("product.decimal_product_uom")
|
||||
uom_precision = self.env.ref("uom.decimal_product_uom")
|
||||
uom_precision.digits = 3
|
||||
product = self.product_service
|
||||
product.seller_ids = [
|
||||
|
|
@ -133,7 +142,7 @@ class TestSeller(TransactionCase):
|
|||
'partner_id': self.asustec.id,
|
||||
}),
|
||||
]
|
||||
supplier_info = first(product.seller_ids)
|
||||
supplier_info = product.seller_ids[0]
|
||||
precise_value = 1.234
|
||||
|
||||
# Act: Set a value for the increased precision
|
||||
|
|
|
|||
|
|
@ -0,0 +1,61 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.fields import Command
|
||||
from odoo.tests import Form, tagged
|
||||
|
||||
from odoo.addons.product.tests.common import ProductVariantsCommon
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestUpdateProductAttributeValueWizard(ProductVariantsCommon):
|
||||
|
||||
def test_add_to_products(self):
|
||||
product_template_shirt = self.env['product.template'].create({
|
||||
'name': 'Shirt',
|
||||
'categ_id': self.product_category.id,
|
||||
'attribute_line_ids': [
|
||||
Command.create({
|
||||
'attribute_id': self.size_attribute.id,
|
||||
'value_ids': [Command.set([self.size_attribute_l.id])],
|
||||
}),
|
||||
],
|
||||
})
|
||||
self.assertNotIn(
|
||||
self.size_attribute_m,
|
||||
product_template_shirt.attribute_line_ids.value_ids,
|
||||
)
|
||||
|
||||
action = self.size_attribute_m.action_add_to_products()
|
||||
|
||||
with Form(self.env[action['res_model']].with_context(action['context'])) as wizard:
|
||||
wizard_record = wizard.save()
|
||||
|
||||
wizard_record.action_confirm()
|
||||
|
||||
self.assertIn(
|
||||
self.size_attribute_m,
|
||||
product_template_shirt.attribute_line_ids.value_ids,
|
||||
)
|
||||
|
||||
def test_update_extra_prices(self):
|
||||
self.assertEqual(
|
||||
self.color_attribute.value_ids.mapped('default_extra_price'),
|
||||
self.product_template_sofa.attribute_line_ids.product_template_value_ids.mapped('price_extra'),
|
||||
)
|
||||
self.assertEqual(
|
||||
self.color_attribute.value_ids.mapped('default_extra_price'),
|
||||
[0.0, 0.0, 0.0],
|
||||
)
|
||||
|
||||
self.color_attribute_red.default_extra_price = 20.0
|
||||
self.assertTrue(self.color_attribute_red.default_extra_price_changed)
|
||||
|
||||
wizard = Form.from_action(self.env, self.color_attribute_red.action_update_prices()).save()
|
||||
wizard.action_confirm()
|
||||
self.assertEqual(
|
||||
self.product_template_sofa.attribute_line_ids.product_template_value_ids.filtered(
|
||||
lambda ptav: ptav.product_attribute_value_id == self.color_attribute_red,
|
||||
).price_extra,
|
||||
20.0,
|
||||
)
|
||||
self.assertFalse(any(self.color_attribute.value_ids.mapped('default_extra_price_changed')))
|
||||
|
|
@ -1,53 +1,81 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import base64
|
||||
from collections import OrderedDict
|
||||
from datetime import timedelta
|
||||
import io
|
||||
import unittest.mock
|
||||
|
||||
from collections import OrderedDict
|
||||
from datetime import timedelta
|
||||
from unittest.mock import patch
|
||||
|
||||
from PIL import Image
|
||||
|
||||
from odoo.fields import Command
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tests import tagged, TransactionCase, Form
|
||||
from odoo.fields import Command
|
||||
from odoo.tests import Form, TransactionCase, tagged
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
from odoo.addons.product.tests.common import ProductVariantsCommon, ProductAttributesCommon
|
||||
from odoo.addons.product.tests.common import ProductVariantsCommon
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestVariantsSearch(ProductVariantsCommon):
|
||||
|
||||
def test_attribute_line_search(self):
|
||||
product_template_shirt = self.env['product.template'].create({
|
||||
'name': 'Shirt',
|
||||
'categ_id': self.product_category.id,
|
||||
'attribute_line_ids': [
|
||||
Command.create({
|
||||
'attribute_id': self.size_attribute.id,
|
||||
'value_ids': [Command.set([self.size_attribute_l.id])],
|
||||
}),
|
||||
],
|
||||
})
|
||||
search_not_to_be_found = self.env['product.template'].search(
|
||||
[('attribute_line_ids', '=', 'M')]
|
||||
)
|
||||
self.assertNotIn(self.product_template_shirt, search_not_to_be_found,
|
||||
self.assertNotIn(product_template_shirt, search_not_to_be_found,
|
||||
'Shirt should not be found searching M')
|
||||
|
||||
search_attribute = self.env['product.template'].search(
|
||||
[('attribute_line_ids', '=', 'Size')]
|
||||
)
|
||||
self.assertIn(self.product_template_shirt, search_attribute,
|
||||
self.assertIn(product_template_shirt, search_attribute,
|
||||
'Shirt should be found searching Size')
|
||||
|
||||
search_value = self.env['product.template'].search(
|
||||
[('attribute_line_ids', '=', 'L')]
|
||||
)
|
||||
self.assertIn(self.product_template_shirt, search_value,
|
||||
self.assertIn(product_template_shirt, search_value,
|
||||
'Shirt should be found searching L')
|
||||
|
||||
def test_name_search(self):
|
||||
self.product_slip_template = self.env['product.template'].create({
|
||||
'name': 'Slip',
|
||||
'default_code': 'ABC',
|
||||
})
|
||||
res = self.env['product.product'].name_search('Shirt', [], 'not ilike', None)
|
||||
res_ids = [r[0] for r in res]
|
||||
self.assertIn(self.product_slip_template.product_variant_ids.id, res_ids,
|
||||
'Slip should be found searching \'not ilike\'')
|
||||
|
||||
templates = self.product_slip_template.name_search(
|
||||
"ABC",
|
||||
[['id', '!=', -1]],
|
||||
)
|
||||
self.assertFalse(templates, "Should not return template when searching on code")
|
||||
templates = self.product_slip_template.with_context(search_product_product=True).name_search(
|
||||
"ABC",
|
||||
[['id', '!=', -1]],
|
||||
)
|
||||
self.assertTrue(templates, "Should return template when searching on code")
|
||||
|
||||
templates = self.product_slip_template.with_context(search_product_product=True).name_search(
|
||||
"ABC",
|
||||
[['id', '!=', self.product_slip_template.id]],
|
||||
)
|
||||
self.assertFalse(templates, "Should not return template.")
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestVariants(ProductVariantsCommon):
|
||||
|
|
@ -78,7 +106,6 @@ class TestVariants(ProductVariantsCommon):
|
|||
test_template = self.env['product.template'].create({
|
||||
'name': 'Sofa',
|
||||
'uom_id': self.uom_unit.id,
|
||||
'uom_po_id': self.uom_unit.id,
|
||||
'attribute_line_ids': [Command.create({
|
||||
'attribute_id': self.size_attribute.id,
|
||||
'value_ids': [Command.link(self.size_attribute_s.id)],
|
||||
|
|
@ -93,7 +120,6 @@ class TestVariants(ProductVariantsCommon):
|
|||
test_template = self.env['product.template'].create({
|
||||
'name': 'Sofa',
|
||||
'uom_id': self.uom_unit.id,
|
||||
'uom_po_id': self.uom_unit.id,
|
||||
'attribute_line_ids': [Command.create({
|
||||
'attribute_id': self.color_attribute.id,
|
||||
'value_ids': [Command.link(self.color_attribute_blue.id)],
|
||||
|
|
@ -111,7 +137,6 @@ class TestVariants(ProductVariantsCommon):
|
|||
test_template = self.env['product.template'].create({
|
||||
'name': 'Sofa',
|
||||
'uom_id': self.uom_unit.id,
|
||||
'uom_po_id': self.uom_unit.id,
|
||||
'attribute_line_ids': [
|
||||
Command.create({
|
||||
'attribute_id': self.color_attribute.id,
|
||||
|
|
@ -146,7 +171,6 @@ class TestVariants(ProductVariantsCommon):
|
|||
test_template = self.env['product.template'].create({
|
||||
'name': 'Sofa',
|
||||
'uom_id': self.uom_unit.id,
|
||||
'uom_po_id': self.uom_unit.id,
|
||||
'attribute_line_ids': [
|
||||
Command.create({
|
||||
'attribute_id': self.color_attribute.id,
|
||||
|
|
@ -185,7 +209,6 @@ class TestVariants(ProductVariantsCommon):
|
|||
test_template = self.env['product.template'].create({
|
||||
'name': 'Sofa',
|
||||
'uom_id': self.uom_unit.id,
|
||||
'uom_po_id': self.uom_unit.id,
|
||||
'attribute_line_ids': [Command.create({
|
||||
'attribute_id': self.color_attribute.id,
|
||||
'value_ids': [Command.link(self.color_attribute_red.id), Command.link(self.color_attribute_blue.id)],
|
||||
|
|
@ -243,6 +266,10 @@ class TestVariants(ProductVariantsCommon):
|
|||
self.assertEqual(template_dyn.name, 'Test Dynamical')
|
||||
|
||||
variant_dyn = template_dyn._create_product_variant(template_dyn._get_first_possible_combination())
|
||||
if 'create_product_product' in variant_dyn.env.context:
|
||||
new_context = dict(variant_dyn.env.context)
|
||||
new_context.pop('create_product_product')
|
||||
variant_dyn = variant_dyn.with_context(new_context)
|
||||
self.assertEqual(len(template_dyn.product_variant_ids), 1)
|
||||
|
||||
variant_dyn_copy = variant_dyn.copy()
|
||||
|
|
@ -291,12 +318,12 @@ class TestVariants(ProductVariantsCommon):
|
|||
})
|
||||
self.assertEqual(len(template.product_variant_ids), 2)
|
||||
variant_1 = template.product_variant_ids[0]
|
||||
variant_1.toggle_active()
|
||||
variant_1.action_archive()
|
||||
self.assertFalse(variant_1.active)
|
||||
self.assertEqual(len(template.product_variant_ids), 1)
|
||||
self.assertEqual(len(template.with_context(
|
||||
active_test=False).product_variant_ids), 2)
|
||||
variant_1.toggle_active()
|
||||
variant_1.action_unarchive()
|
||||
self.assertTrue(variant_1.active)
|
||||
self.assertTrue(template.active)
|
||||
|
||||
|
|
@ -360,16 +387,57 @@ class TestVariants(ProductVariantsCommon):
|
|||
self.assertEqual(len(template.product_variant_ids), 2)
|
||||
variant_1 = template.product_variant_ids[0]
|
||||
variant_2 = template.product_variant_ids[1]
|
||||
template.product_variant_ids.toggle_active()
|
||||
template.product_variant_ids.action_archive()
|
||||
self.assertFalse(variant_1.active, 'Should archive all variants')
|
||||
self.assertFalse(template.active, 'Should archive related template')
|
||||
variant_1.toggle_active()
|
||||
variant_1.action_unarchive()
|
||||
self.assertTrue(variant_1.active, 'Should activate variant')
|
||||
self.assertFalse(variant_2.active, 'Should not re-activate other variant')
|
||||
self.assertTrue(template.active, 'Should re-activate template')
|
||||
|
||||
def test_open_product_form_with_default_uom_id_is_false(self):
|
||||
""" Test default UoM is False when creating a product. """
|
||||
uom_unit = self.env.ref('uom.product_uom_unit')
|
||||
product_form = Form(self.env['product.product'].with_context(
|
||||
default_uom_id=False,
|
||||
))
|
||||
product_form.name = 'Test Product'
|
||||
product = product_form.save()
|
||||
self.assertEqual(uom_unit, product.uom_id)
|
||||
|
||||
def test_single_variant_template_computed_values_after_creation(self):
|
||||
"""Check that variant-related fields on templates are correctly set."""
|
||||
product_template = self.env['product.template'].create({
|
||||
'name': "one variant template",
|
||||
'attribute_line_ids': [Command.create({
|
||||
'attribute_id': self.size_attribute.id,
|
||||
'value_ids': [Command.set(self.size_attribute_s.ids)],
|
||||
})],
|
||||
'barcode': 'THIS IS A TEST',
|
||||
})
|
||||
self.assertEqual(
|
||||
product_template.barcode,
|
||||
product_template.product_variant_id.barcode,
|
||||
)
|
||||
self.assertEqual(
|
||||
product_template.barcode,
|
||||
'THIS IS A TEST',
|
||||
)
|
||||
product_template = self.env['product.template'].create({
|
||||
'name': "one variant template without attribute lines",
|
||||
'barcode': 'THIS IS A BARCODE',
|
||||
})
|
||||
self.assertEqual(
|
||||
product_template.barcode,
|
||||
product_template.product_variant_id.barcode,
|
||||
)
|
||||
self.assertEqual(
|
||||
product_template.barcode,
|
||||
'THIS IS A BARCODE',
|
||||
)
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestVariantsNoCreate(ProductAttributesCommon):
|
||||
class TestVariantsNoCreate(ProductVariantsCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
|
|
@ -382,7 +450,6 @@ class TestVariantsNoCreate(ProductAttributesCommon):
|
|||
template = self.env['product.template'].create({
|
||||
'name': 'Sofa',
|
||||
'uom_id': self.uom_unit.id,
|
||||
'uom_po_id': self.uom_unit.id,
|
||||
'attribute_line_ids': [Command.create({
|
||||
'attribute_id': self.size_attribute.id,
|
||||
'value_ids': [Command.link(self.size_attribute_s.id)],
|
||||
|
|
@ -396,7 +463,6 @@ class TestVariantsNoCreate(ProductAttributesCommon):
|
|||
template = self.env['product.template'].create({
|
||||
'name': 'Sofa',
|
||||
'uom_id': self.uom_unit.id,
|
||||
'uom_po_id': self.uom_unit.id,
|
||||
})
|
||||
self.assertEqual(len(template.product_variant_ids), 1)
|
||||
|
||||
|
|
@ -414,7 +480,6 @@ class TestVariantsNoCreate(ProductAttributesCommon):
|
|||
template = self.env['product.template'].create({
|
||||
'name': 'Sofa',
|
||||
'uom_id': self.uom_unit.id,
|
||||
'uom_po_id': self.uom_unit.id,
|
||||
'attribute_line_ids': [Command.create({
|
||||
'attribute_id': self.size_attribute.id,
|
||||
'value_ids': [Command.set(self.size_attribute.value_ids.ids)],
|
||||
|
|
@ -428,7 +493,6 @@ class TestVariantsNoCreate(ProductAttributesCommon):
|
|||
template = self.env['product.template'].create({
|
||||
'name': 'Sofa',
|
||||
'uom_id': self.uom_unit.id,
|
||||
'uom_po_id': self.uom_unit.id,
|
||||
})
|
||||
self.assertEqual(len(template.product_variant_ids), 1)
|
||||
|
||||
|
|
@ -446,7 +510,6 @@ class TestVariantsNoCreate(ProductAttributesCommon):
|
|||
template = self.env['product.template'].create({
|
||||
'name': 'Sofa',
|
||||
'uom_id': self.uom_unit.id,
|
||||
'uom_po_id': self.uom_unit.id,
|
||||
'attribute_line_ids': [
|
||||
Command.create({ # no variants for this one
|
||||
'attribute_id': self.size_attribute.id,
|
||||
|
|
@ -470,7 +533,6 @@ class TestVariantsNoCreate(ProductAttributesCommon):
|
|||
template = self.env['product.template'].create({
|
||||
'name': 'Sofa',
|
||||
'uom_id': self.uom_unit.id,
|
||||
'uom_po_id': self.uom_unit.id,
|
||||
})
|
||||
self.assertEqual(len(template.product_variant_ids), 1)
|
||||
|
||||
|
|
@ -497,7 +559,6 @@ class TestVariantsNoCreate(ProductAttributesCommon):
|
|||
template = self.env['product.template'].create({
|
||||
'name': 'Sofa',
|
||||
'uom_id': self.uom_unit.id,
|
||||
'uom_po_id': self.uom_unit.id,
|
||||
'attribute_line_ids': [
|
||||
Command.create({ # no variants for this one
|
||||
'attribute_id': self.size_attribute.id,
|
||||
|
|
@ -521,7 +582,6 @@ class TestVariantsNoCreate(ProductAttributesCommon):
|
|||
template = self.env['product.template'].create({
|
||||
'name': 'Sofa',
|
||||
'uom_id': self.uom_unit.id,
|
||||
'uom_po_id': self.uom_unit.id,
|
||||
})
|
||||
self.assertEqual(len(template.product_variant_ids), 1)
|
||||
|
||||
|
|
@ -548,7 +608,6 @@ class TestVariantsNoCreate(ProductAttributesCommon):
|
|||
template = self.env['product.template'].create({
|
||||
'name': 'Sofax',
|
||||
'uom_id': self.uom_unit.id,
|
||||
'uom_po_id': self.uom_unit.id,
|
||||
'attribute_line_ids': [
|
||||
Command.create({ # one variant for this one
|
||||
'attribute_id': self.color_attribute.id,
|
||||
|
|
@ -780,11 +839,11 @@ class TestVariantsImages(ProductVariantsCommon):
|
|||
images = self.variants.mapped('image_1920')
|
||||
self.assertEqual(len(set(images)), 4)
|
||||
variant_no_image = self.variants[0]
|
||||
old_last_update = variant_no_image['__last_update']
|
||||
old_last_update = variant_no_image.write_date
|
||||
|
||||
self.assertFalse(variant_no_image.image_1920)
|
||||
self.template.image_1920 = image_black
|
||||
new_last_update = variant_no_image['__last_update']
|
||||
new_last_update = variant_no_image.write_date
|
||||
|
||||
# the first has no image variant, all the others do
|
||||
self.assertFalse(variant_no_image.image_variant_1920)
|
||||
|
|
@ -881,7 +940,7 @@ class TestVariantsArchive(ProductVariantsCommon):
|
|||
|
||||
def unlink(self):
|
||||
raise Exception('just')
|
||||
Product._patch_method('unlink', unlink)
|
||||
self.patch(type(Product), 'unlink', unlink)
|
||||
|
||||
variants_2x1 = self.template.product_variant_ids
|
||||
self._assert_2color_x_1size()
|
||||
|
|
@ -903,8 +962,6 @@ class TestVariantsArchive(ProductVariantsCommon):
|
|||
archived_variants = self._get_archived_variants()
|
||||
self.assertFalse(archived_variants)
|
||||
|
||||
Product._revert_method('unlink')
|
||||
|
||||
def test_02_update_variant_archive_2_value(self):
|
||||
"""We do the same operations on the template as in the previous tests,
|
||||
except we simulate that the variants can't be unlinked.
|
||||
|
|
@ -916,7 +973,7 @@ class TestVariantsArchive(ProductVariantsCommon):
|
|||
|
||||
def unlink(slef):
|
||||
raise Exception('just')
|
||||
Product._patch_method('unlink', unlink)
|
||||
self.patch(type(Product), 'unlink', unlink)
|
||||
|
||||
variants_2x2 = self.template.product_variant_ids
|
||||
self._assert_2color_x_2size()
|
||||
|
|
@ -972,8 +1029,6 @@ class TestVariantsArchive(ProductVariantsCommon):
|
|||
self.assertEqual(archived_variants, variants_2x2)
|
||||
self._assert_2color_x_2size(archived_variants)
|
||||
|
||||
Product._revert_method('unlink')
|
||||
|
||||
@mute_logger('odoo.models.unlink')
|
||||
def test_03_update_variant_archive_3_value(self):
|
||||
self._remove_ptal_size()
|
||||
|
|
@ -983,7 +1038,7 @@ class TestVariantsArchive(ProductVariantsCommon):
|
|||
|
||||
def unlink(slef):
|
||||
raise Exception('just')
|
||||
Product._patch_method('unlink', unlink)
|
||||
self.patch(type(Product), 'unlink', unlink)
|
||||
|
||||
self._assert_2color_x_1size()
|
||||
archived_variants = self._get_archived_variants()
|
||||
|
|
@ -1031,14 +1086,12 @@ class TestVariantsArchive(ProductVariantsCommon):
|
|||
archived_variants = self._get_archived_variants()
|
||||
self.assertEqual(archived_variants, variants_2x1 + variant_0x0)
|
||||
|
||||
Product._revert_method('unlink')
|
||||
|
||||
def test_04_from_to_single_values(self):
|
||||
Product = self.env['product.product']
|
||||
|
||||
def unlink(slef):
|
||||
raise Exception('just')
|
||||
Product._patch_method('unlink', unlink)
|
||||
self.patch(type(Product), 'unlink', unlink)
|
||||
|
||||
# CASE: remove one value, line becoming single value
|
||||
variants_2x2 = self.template.product_variant_ids
|
||||
|
|
@ -1075,11 +1128,9 @@ class TestVariantsArchive(ProductVariantsCommon):
|
|||
self._assert_2color_x_0size(archived_variants)
|
||||
self.assertEqual(archived_variants, variants_2x0)
|
||||
|
||||
Product._revert_method('unlink')
|
||||
|
||||
def test_name_search_dynamic_attributes(self):
|
||||
# To be able to test dynamic variant "variants" feature must be set up
|
||||
self.env.user.write({'groups_id': [(4, self.env.ref('product.group_product_variant').id)]})
|
||||
self._enable_variants()
|
||||
dynamic_attr = self.env['product.attribute'].create({
|
||||
'name': 'Dynamic',
|
||||
'create_variant': 'dynamic',
|
||||
|
|
@ -1105,20 +1156,16 @@ class TestVariantsArchive(ProductVariantsCommon):
|
|||
self._enable_uom()
|
||||
|
||||
units = self.uom_unit
|
||||
cm = self.env.ref('uom.product_uom_cm')
|
||||
mm = self.env.ref('uom.product_uom_millimeter')
|
||||
template = self.product.product_tmpl_id
|
||||
|
||||
template_form = Form(template)
|
||||
template_form.uom_id = cm
|
||||
self.assertEqual(template_form.uom_po_id, cm)
|
||||
template_form.uom_id = mm
|
||||
template = template_form.save()
|
||||
|
||||
variant_form = Form(template.product_variant_ids)
|
||||
variant_form.uom_id = units
|
||||
self.assertEqual(variant_form.uom_po_id, units)
|
||||
variant = variant_form.save()
|
||||
self.assertEqual(variant.uom_po_id, units)
|
||||
self.assertEqual(template.uom_po_id, units)
|
||||
variant_form.save()
|
||||
|
||||
@mute_logger('odoo.models.unlink')
|
||||
def test_dynamic_attributes_archiving(self):
|
||||
|
|
@ -1129,7 +1176,7 @@ class TestVariantsArchive(ProductVariantsCommon):
|
|||
# Patch unlink method to force archiving instead deleting
|
||||
def unlink(self):
|
||||
self.active = False
|
||||
Product._patch_method('unlink', unlink)
|
||||
self.patch(type(Product), 'unlink', unlink)
|
||||
|
||||
# Creating attributes
|
||||
pa_color = ProductAttribute.create({'sequence': 1, 'name': 'color', 'create_variant': 'dynamic'})
|
||||
|
|
@ -1234,15 +1281,13 @@ class TestVariantsArchive(ProductVariantsCommon):
|
|||
})
|
||||
self.assertTrue(product_white.active)
|
||||
|
||||
Product._revert_method('unlink')
|
||||
|
||||
def test_set_barcode(self):
|
||||
tmpl = self.product.product_tmpl_id
|
||||
tmpl.barcode = '123'
|
||||
self.assertEqual(tmpl.barcode, '123')
|
||||
self.assertEqual(self.product.barcode, '123')
|
||||
|
||||
tmpl.toggle_active()
|
||||
tmpl.action_archive()
|
||||
|
||||
tmpl.barcode = '456'
|
||||
tmpl.invalidate_recordset(fnames=['barcode'])
|
||||
|
|
@ -1367,6 +1412,40 @@ class TestVariantsArchive(ProductVariantsCommon):
|
|||
self.assertEqual(len(variants), 1)
|
||||
self.assertFalse(variants[0].product_template_attribute_value_ids)
|
||||
|
||||
@mute_logger('odoo.models.unlink')
|
||||
def test_unlink_and_archive_multiple_variants(self):
|
||||
"""
|
||||
Test unlinking multiple variants in various scenarios
|
||||
- Unlink one archived variant
|
||||
- Unlink one archived and one active variant
|
||||
- Unlink multiple archived variants and multiple active variants at once
|
||||
"""
|
||||
products = self.env['product.product'].create([
|
||||
{'name': 'variant 1', 'description': 'var 1'},
|
||||
{'name': 'variant 2', 'description': 'var 2'},
|
||||
{'name': 'variant 3', 'description': 'var 3'},
|
||||
{'name': 'variant 4', 'description': 'var 4'},
|
||||
{'name': 'variant 5', 'description': 'var 5'},
|
||||
{'name': 'variant 6', 'description': 'var 6'},
|
||||
{'name': 'variant 7', 'description': 'var 7'},
|
||||
])
|
||||
# Unlink one archived variant
|
||||
products[0].action_archive()
|
||||
products[0].unlink()
|
||||
self.assertFalse(products[0].exists())
|
||||
|
||||
# Unlink one archived and one active variant
|
||||
products[1].action_archive()
|
||||
active_and_archived = products[1] + products[2]
|
||||
active_and_archived.unlink()
|
||||
self.assertFalse(active_and_archived.exists())
|
||||
|
||||
# Unlink multiple archived variants and multiple active variants at once
|
||||
multiple_archived = products[3] + products[4]
|
||||
multiple_active = products[5] + products[6]
|
||||
multiple_archived.action_archive()
|
||||
(multiple_archived + multiple_active).unlink()
|
||||
self.assertFalse(products.exists())
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestVariantWrite(TransactionCase):
|
||||
|
|
@ -1419,7 +1498,7 @@ class TestVariantWrite(TransactionCase):
|
|||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestVariantsExclusion(ProductAttributesCommon):
|
||||
class TestVariantsExclusion(ProductVariantsCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
|
|
@ -1551,3 +1630,90 @@ class TestVariantsExclusion(ProductAttributesCommon):
|
|||
|
||||
exclude.unlink()
|
||||
self.assertEqual(len(self.smartphone.product_variant_ids), 4)
|
||||
|
||||
@mute_logger('odoo.models.unlink')
|
||||
def test_dynamic_variants_unarchive(self):
|
||||
""" Make sure that exclusions creation, update & delete are correctly handled.
|
||||
|
||||
Exclusions updates are not necessarily done from a specific template.
|
||||
"""
|
||||
product_template = self.env['product.template'].create({
|
||||
'name': 'Test dynamic',
|
||||
'attribute_line_ids': [
|
||||
Command.create({
|
||||
'attribute_id': self.dynamic_attribute.id,
|
||||
'value_ids': [Command.set(self.dynamic_attribute.value_ids.ids)],
|
||||
}),
|
||||
Command.create({
|
||||
'attribute_id': self.dynamic_attribute.id,
|
||||
'value_ids': [Command.set(self.dynamic_attribute.value_ids.ids)],
|
||||
})
|
||||
]
|
||||
})
|
||||
self.assertFalse(product_template.product_variant_ids)
|
||||
first_line_ptavs = product_template.attribute_line_ids[0].product_template_value_ids
|
||||
second_line_ptavs = product_template.attribute_line_ids[1].product_template_value_ids
|
||||
for ptav1, ptav2 in zip(first_line_ptavs, second_line_ptavs, strict=True):
|
||||
product_template._create_product_variant(ptav1 + ptav2)
|
||||
|
||||
self.assertEqual(len(product_template.product_variant_ids), 2)
|
||||
|
||||
pav_to_remove = self.dynamic_attribute.value_ids[:1]
|
||||
variant_to_archive = product_template.product_variant_ids.filtered(
|
||||
lambda pp:
|
||||
pav_to_remove in pp.product_template_attribute_value_ids.product_attribute_value_id
|
||||
)
|
||||
|
||||
# Removing one option will archive one variant
|
||||
with patch(
|
||||
'odoo.addons.product.models.product_product.ProductProduct._filter_to_unlink',
|
||||
lambda products: products.filtered(
|
||||
lambda pp: pp.product_tmpl_id.id != product_template.id
|
||||
),
|
||||
):
|
||||
product_template.attribute_line_ids[1].value_ids = [
|
||||
Command.unlink(self.dynamic_attribute.value_ids[:1].id)
|
||||
]
|
||||
self.assertEqual(len(product_template.product_variant_ids), 1)
|
||||
self.assertFalse(variant_to_archive.active)
|
||||
|
||||
# Putting it back should unarchive the archived variant
|
||||
product_template.attribute_line_ids[1].value_ids = [
|
||||
Command.link(self.dynamic_attribute.value_ids[:1].id)
|
||||
]
|
||||
self.assertEqual(len(product_template.product_variant_ids), 2)
|
||||
self.assertTrue(variant_to_archive.active)
|
||||
|
||||
def test_supplierinfo_with_dynamic_attribute(self):
|
||||
"""
|
||||
Ensure that supplierinfo.product_id is never automatically set when
|
||||
variants are created dynamically.
|
||||
|
||||
The supplierinfo should remain template-level (product_id = False)
|
||||
unless the user explicitly assigns a specific variant manually,
|
||||
even if only one variant exists initially.
|
||||
"""
|
||||
product_template = self.env['product.template'].create({
|
||||
'name': 'Test dynamic',
|
||||
'attribute_line_ids': [
|
||||
Command.create({
|
||||
'attribute_id': self.dynamic_attribute.id,
|
||||
'value_ids': [Command.set(self.dynamic_attribute.value_ids.ids)],
|
||||
}),
|
||||
]
|
||||
})
|
||||
self.assertFalse(product_template.product_variant_ids)
|
||||
|
||||
supplierinfo = self.env['product.supplierinfo'].create({
|
||||
'partner_id': self.partner.id,
|
||||
'product_tmpl_id': product_template.id,
|
||||
})
|
||||
self.assertFalse(product_template.product_variant_ids)
|
||||
|
||||
product_template._create_product_variant(product_template.attribute_line_ids.product_template_value_ids[0])
|
||||
self.assertEqual(len(product_template.product_variant_ids), 1)
|
||||
self.assertFalse(supplierinfo.product_id)
|
||||
|
||||
product_template._create_product_variant(product_template.attribute_line_ids.product_template_value_ids[1])
|
||||
self.assertEqual(len(product_template.product_variant_ids), 2)
|
||||
self.assertFalse(supplierinfo.product_id)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue