mirror of
https://github.com/bringout/oca-ocb-sale.git
synced 2026-04-26 06:12:01 +02:00
Initial commit: Sale packages
This commit is contained in:
commit
14e3d26998
6469 changed files with 2479670 additions and 0 deletions
11
odoo-bringout-oca-ocb-product/product/tests/__init__.py
Normal file
11
odoo-bringout-oca-ocb-product/product/tests/__init__.py
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# -*- 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_name
|
||||
from . import test_pricelist
|
||||
from . import test_product_attribute_value_config
|
||||
from . import test_product_pricelist
|
||||
from . import test_seller
|
||||
from . import test_variants
|
||||
177
odoo-bringout-oca-ocb-product/product/tests/common.py
Normal file
177
odoo-bringout-oca-ocb-product/product/tests/common.py
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from contextlib import nullcontext
|
||||
from unittest.mock import patch
|
||||
|
||||
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,
|
||||
):
|
||||
|
||||
@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.product_category = cls.env['product.category'].create({
|
||||
'name': 'Test Category',
|
||||
})
|
||||
cls.product = cls.env['product.product'].with_context(**NO_TAXES_CONTEXT).create({
|
||||
'name': 'Test Product',
|
||||
'detailed_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',
|
||||
'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()
|
||||
|
||||
@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()
|
||||
|
||||
with archive_context:
|
||||
cls.env['product.pricelist'].search([
|
||||
('id', '!=', cls.pricelist.id),
|
||||
]).action_archive()
|
||||
|
||||
|
||||
class ProductAttributesCommon(ProductCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
cls.size_attribute = cls.env['product.attribute'].create({
|
||||
'name': 'Size',
|
||||
'value_ids': [
|
||||
Command.create({'name': 'S'}),
|
||||
Command.create({'name': 'M'}),
|
||||
Command.create({'name': 'L'}),
|
||||
]
|
||||
})
|
||||
(
|
||||
cls.size_attribute_s,
|
||||
cls.size_attribute_m,
|
||||
cls.size_attribute_l,
|
||||
) = cls.size_attribute.value_ids
|
||||
|
||||
cls.color_attribute = cls.env['product.attribute'].create({
|
||||
'name': 'Color',
|
||||
'value_ids': [
|
||||
Command.create({'name': 'red', 'sequence': 1}),
|
||||
Command.create({'name': 'blue', 'sequence': 2}),
|
||||
Command.create({'name': 'green', 'sequence': 3}),
|
||||
],
|
||||
})
|
||||
(
|
||||
cls.color_attribute_red,
|
||||
cls.color_attribute_blue,
|
||||
cls.color_attribute_green,
|
||||
) = cls.color_attribute.value_ids
|
||||
|
||||
|
||||
class ProductVariantsCommon(ProductAttributesCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
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,
|
||||
'value_ids': [Command.set([
|
||||
cls.color_attribute_red.id,
|
||||
cls.color_attribute_blue.id,
|
||||
cls.color_attribute_green.id
|
||||
])],
|
||||
})]
|
||||
})
|
||||
|
||||
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)
|
||||
93
odoo-bringout-oca-ocb-product/product/tests/test_barcode.py
Normal file
93
odoo-bringout-oca-ocb-product/product/tests/test_barcode.py
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.tests import tagged, TransactionCase
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestProductBarcode(TransactionCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.env['product.product'].create([
|
||||
{'name': 'BC1', 'barcode': '1'},
|
||||
{'name': 'BC2', 'barcode': '2'},
|
||||
])
|
||||
|
||||
def test_blank_barcodes_allowed(self):
|
||||
"""Makes sure duplicated blank barcodes are allowed."""
|
||||
for i in range(2):
|
||||
self.env['product.product'].create({'name': f'BC_{i}'})
|
||||
|
||||
def test_false_barcodes_allowed(self):
|
||||
"""Makes sure duplicated False barcodes are allowed."""
|
||||
for i in range(2):
|
||||
self.env['product.product'].create({'name': f'BC_{i}', 'barcode': False})
|
||||
|
||||
def test_duplicated_barcode(self):
|
||||
"""Tests for simple duplication."""
|
||||
with self.assertRaises(ValidationError):
|
||||
self.env['product.product'].create({'name': 'BC3', 'barcode': '1'})
|
||||
|
||||
def test_duplicated_barcode_in_batch_edit(self):
|
||||
"""Tests for duplication in batch edits."""
|
||||
batch = [
|
||||
{'name': 'BC3', 'barcode': '3'},
|
||||
{'name': 'BC4', 'barcode': '4'},
|
||||
]
|
||||
self.env['product.product'].create(batch)
|
||||
batch.append({'name': 'BC5', 'barcode': '1'})
|
||||
with self.assertRaises(ValidationError):
|
||||
self.env['product.product'].create(batch)
|
||||
|
||||
def test_test_duplicated_barcode_error_msg_content(self):
|
||||
"""Validates the error message shown when duplicated barcodes are found."""
|
||||
batch = [
|
||||
{'name': 'BC3', 'barcode': '3'},
|
||||
{'name': 'BC4', 'barcode': '3'},
|
||||
{'name': 'BC5', 'barcode': '4'},
|
||||
{'name': 'BC6', 'barcode': '4'},
|
||||
{'name': 'BC7', 'barcode': '1'},
|
||||
]
|
||||
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 "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."""
|
||||
product = self.env['product.product'].create({
|
||||
'name': 'product',
|
||||
'packaging_ids': [(0, 0, {
|
||||
'name': 'packing',
|
||||
'barcode': '1234',
|
||||
})]
|
||||
})
|
||||
package = product.packaging_ids
|
||||
self.assertTrue(package.exists())
|
||||
self.assertEqual(package.barcode, '1234')
|
||||
product.packaging_ids = False
|
||||
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()
|
||||
|
||||
self.env['product.product'].create({
|
||||
'name': 'product2',
|
||||
'packaging_ids': [(0, 0, {
|
||||
'name': 'packing2',
|
||||
'barcode': '1234',
|
||||
})]
|
||||
})
|
||||
31
odoo-bringout-oca-ocb-product/product/tests/test_common.py
Normal file
31
odoo-bringout-oca-ocb-product/product/tests/test_common.py
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.tests import tagged
|
||||
|
||||
from odoo.addons.product.tests.common import ProductCommon
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestProduct(ProductCommon):
|
||||
|
||||
def test_common(self):
|
||||
self.assertEqual(self.consumable_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([]),
|
||||
self.pricelist,
|
||||
)
|
||||
self.assertEqual(
|
||||
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')
|
||||
58
odoo-bringout-oca-ocb-product/product/tests/test_name.py
Normal file
58
odoo-bringout-oca-ocb-product/product/tests/test_name.py
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.tests import tagged, TransactionCase
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestName(TransactionCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.product_name = 'Product Test Name'
|
||||
cls.product_code = 'PTN'
|
||||
cls.product = cls.env['product.product'].create({
|
||||
'name': cls.product_name,
|
||||
'default_code': cls.product_code,
|
||||
})
|
||||
|
||||
def test_10_product_name(self):
|
||||
display_name = self.product.display_name
|
||||
self.assertEqual(display_name, "[%s] %s" % (self.product_code, self.product_name),
|
||||
"Code should be preprended the name as the context is not preventing it.")
|
||||
display_name = self.product.with_context(display_default_code=False).display_name
|
||||
self.assertEqual(display_name, self.product_name,
|
||||
"Code should not be preprended to the name as context should prevent it.")
|
||||
|
||||
def test_default_code_and_negative_operator(self):
|
||||
res = self.env['product.template'].name_search(name='PTN', operator='not ilike')
|
||||
res_ids = [r[0] for r in res]
|
||||
self.assertNotIn(self.product.id, res_ids)
|
||||
|
||||
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)]})
|
||||
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})
|
||||
template_dyn = self.env['product.template'].create({
|
||||
'name': 'Test Dynamical',
|
||||
'attribute_line_ids': [(0, 0, {
|
||||
'attribute_id': color_attr.id,
|
||||
'value_ids': [(4, color_attr_value_r.id), (4, color_attr_value_b.id)],
|
||||
})]
|
||||
})
|
||||
product = self.env['product.product'].create({
|
||||
'name': 'Dynamo Lamp',
|
||||
'default_code': 'Dynamo',
|
||||
})
|
||||
self.assertTrue(template_dyn.has_dynamic_attributes())
|
||||
# Ensure that template_dyn hasn't any product_product
|
||||
self.assertEqual(len(template_dyn.product_variant_ids), 0)
|
||||
# Ensure that Dynam search return Dynamo and Test Dynamical as this
|
||||
# last have no product_product
|
||||
res = self.env['product.template'].name_search(name='Dynam', operator='ilike')
|
||||
res_ids = [r[0] for r in res]
|
||||
self.assertIn(template_dyn.id, res_ids)
|
||||
self.assertIn(product.product_tmpl_id.id, res_ids)
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
# -*- 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 odoo.addons.product.tests.common import ProductCommon
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestPricelist(ProductCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
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({
|
||||
'name': 'Sale pricelist',
|
||||
'item_ids': [
|
||||
Command.create({
|
||||
'compute_price': 'formula',
|
||||
'base': 'list_price', # based on public price
|
||||
'price_discount': 10,
|
||||
'product_id': cls.usb_adapter.id,
|
||||
'applied_on': '0_product_variant',
|
||||
}),
|
||||
Command.create({
|
||||
'compute_price': 'formula',
|
||||
'base': 'list_price', # based on public price
|
||||
'price_surcharge': -0.5,
|
||||
'product_id': cls.datacard.id,
|
||||
'applied_on': '0_product_variant',
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
def test_10_discount(self):
|
||||
# Make sure the price using a pricelist is the same than without after
|
||||
# applying the computation manually
|
||||
|
||||
self.assertEqual(
|
||||
self.pricelist._get_product_price(self.usb_adapter, 1.0)*0.9,
|
||||
self.sale_pricelist_id._get_product_price(self.usb_adapter, 1.0))
|
||||
|
||||
self.assertEqual(
|
||||
self.pricelist._get_product_price(self.datacard, 1.0)-0.5,
|
||||
self.sale_pricelist_id._get_product_price(self.datacard, 1.0))
|
||||
|
||||
self.assertAlmostEqual(
|
||||
self.sale_pricelist_id._get_product_price(self.usb_adapter, 1.0, uom=self.uom_unit)*12,
|
||||
self.sale_pricelist_id._get_product_price(self.usb_adapter, 1.0, uom=self.uom_dozen))
|
||||
|
||||
# price_surcharge applies to product default UoM, here "Units", so surcharge will be multiplied
|
||||
self.assertAlmostEqual(
|
||||
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_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'
|
||||
})
|
||||
|
||||
self.env['product.pricelist.item'].create({
|
||||
'pricelist_id': self.pricelist.id,
|
||||
'applied_on': '0_product_variant',
|
||||
'compute_price': 'formula',
|
||||
'base': 'list_price', # based on public price
|
||||
'min_quantity': 3, # min = 3 tonnes
|
||||
'price_surcharge': -10, # -10 EUR / tonne
|
||||
'product_id': spam.id
|
||||
})
|
||||
|
||||
def test_unit_price(qty, uom_id, expected_unit_price):
|
||||
uom = self.env['uom.uom'].browse(uom_id)
|
||||
unit_price = self.pricelist._get_product_price(spam, qty, uom=uom)
|
||||
self.assertAlmostEqual(unit_price, expected_unit_price, msg='Computed unit price is wrong')
|
||||
|
||||
# Test prices - they are *per unit*, the quantity is only here to match the pricelist rules!
|
||||
test_unit_price(2, self.uom_kgm.id, tonne_price / 1000.0)
|
||||
test_unit_price(2000, self.uom_kgm.id, tonne_price / 1000.0)
|
||||
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)
|
||||
|
|
@ -0,0 +1,770 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# 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.tools import mute_logger
|
||||
|
||||
from odoo.addons.base.tests.common import DISABLED_MAIL_CONTEXT
|
||||
|
||||
|
||||
class TestProductAttributeValueCommon(TransactionCase):
|
||||
|
||||
@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,
|
||||
})
|
||||
|
||||
(
|
||||
cls.ssd_attribute,
|
||||
cls.ram_attribute,
|
||||
cls.hdd_attribute,
|
||||
cls.size_attribute,
|
||||
) = cls.env['product.attribute'].create([{
|
||||
'name': 'Memory',
|
||||
'sequence': 1,
|
||||
'value_ids': [
|
||||
Command.create({
|
||||
'name': '256 GB',
|
||||
'sequence': 1,
|
||||
}),
|
||||
Command.create({
|
||||
'name': '512 GB',
|
||||
'sequence': 2,
|
||||
})
|
||||
],
|
||||
}, {
|
||||
'name': 'RAM',
|
||||
'sequence': 2,
|
||||
'value_ids': [
|
||||
Command.create({
|
||||
'name': '8 GB',
|
||||
'sequence': 1,
|
||||
}),
|
||||
Command.create({
|
||||
'name': '16 GB',
|
||||
'sequence': 2,
|
||||
}),
|
||||
Command.create({
|
||||
'name': '32 GB',
|
||||
'sequence': 3,
|
||||
}),
|
||||
]
|
||||
}, {
|
||||
'name': 'HDD',
|
||||
'sequence': 3,
|
||||
'value_ids': [
|
||||
Command.create({
|
||||
'name': '1 To',
|
||||
'sequence': 1,
|
||||
}),
|
||||
Command.create({
|
||||
'name': '2 To',
|
||||
'sequence': 2,
|
||||
}),
|
||||
Command.create({
|
||||
'name': '4 To',
|
||||
'sequence': 3,
|
||||
})
|
||||
]
|
||||
}, {
|
||||
'name': 'Size',
|
||||
'sequence': 4,
|
||||
'value_ids': [
|
||||
Command.create({
|
||||
'name': 'M',
|
||||
'sequence': 1,
|
||||
}),
|
||||
Command.create({
|
||||
'name': 'L',
|
||||
'sequence': 2,
|
||||
}),
|
||||
Command.create({
|
||||
'name': 'XL',
|
||||
'sequence': 3,
|
||||
}),
|
||||
],
|
||||
}])
|
||||
|
||||
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.COMPUTER_SSD_PTAL_VALUES = {
|
||||
'product_tmpl_id': cls.computer.id,
|
||||
'attribute_id': cls.ssd_attribute.id,
|
||||
'value_ids': [Command.set([cls.ssd_256.id, cls.ssd_512.id])],
|
||||
}
|
||||
cls.COMPUTER_RAM_PTAL_VALUES = {
|
||||
'product_tmpl_id': cls.computer.id,
|
||||
'attribute_id': cls.ram_attribute.id,
|
||||
'value_ids': [Command.set([cls.ram_8.id, cls.ram_16.id, cls.ram_32.id])],
|
||||
}
|
||||
cls.COMPUTER_HDD_PTAL_VALUES = {
|
||||
'product_tmpl_id': cls.computer.id,
|
||||
'attribute_id': cls.hdd_attribute.id,
|
||||
'value_ids': [Command.set([cls.hdd_1.id, cls.hdd_2.id, cls.hdd_4.id])],
|
||||
}
|
||||
|
||||
cls._add_computer_attribute_lines()
|
||||
|
||||
cls.computer_case = cls.env['product.template'].create({
|
||||
'name': 'Super Computer Case'
|
||||
})
|
||||
|
||||
cls.computer_case_size_attribute_lines = cls.env['product.template.attribute.line'].create({
|
||||
'product_tmpl_id': cls.computer_case.id,
|
||||
'attribute_id': cls.size_attribute.id,
|
||||
'value_ids': [Command.set([cls.size_m.id, cls.size_l.id, cls.size_xl.id])],
|
||||
})
|
||||
|
||||
|
||||
@classmethod
|
||||
def _add_computer_attribute_lines(cls):
|
||||
(
|
||||
cls.computer_ssd_attribute_lines,
|
||||
cls.computer_ram_attribute_lines,
|
||||
cls.computer_hdd_attribute_lines,
|
||||
) = cls.env['product.template.attribute.line'].create([
|
||||
cls.COMPUTER_SSD_PTAL_VALUES,
|
||||
cls.COMPUTER_RAM_PTAL_VALUES,
|
||||
cls.COMPUTER_HDD_PTAL_VALUES,
|
||||
])
|
||||
|
||||
# Setup extra prices
|
||||
cls._setup_ssd_attribute_line()
|
||||
cls._setup_ram_attribute_line()
|
||||
cls._setup_hdd_attribute_line()
|
||||
|
||||
@classmethod
|
||||
def _add_ram_attribute_line(cls):
|
||||
cls.computer_ram_attribute_lines = cls.env['product.template.attribute.line'].create(
|
||||
cls.COMPUTER_HDD_PTAL_VALUES)
|
||||
|
||||
cls._setup_ram_attribute_line()
|
||||
|
||||
@classmethod
|
||||
def _setup_ram_attribute_line(cls):
|
||||
"""Setup extra prices"""
|
||||
|
||||
cls.computer_ram_attribute_lines.product_template_value_ids[0].price_extra = 20
|
||||
cls.computer_ram_attribute_lines.product_template_value_ids[1].price_extra = 40
|
||||
cls.computer_ram_attribute_lines.product_template_value_ids[2].price_extra = 80
|
||||
|
||||
@classmethod
|
||||
def _add_ssd_attribute_line(cls):
|
||||
cls.computer_ssd_attribute_lines = cls.env['product.template.attribute.line'].create(
|
||||
cls.COMPUTER_SSD_PTAL_VALUES)
|
||||
|
||||
cls._setup_ssd_attribute_line()
|
||||
|
||||
@classmethod
|
||||
def _setup_ssd_attribute_line(cls):
|
||||
"""Setup extra prices"""
|
||||
|
||||
cls.computer_ssd_attribute_lines.product_template_value_ids[0].price_extra = 200
|
||||
cls.computer_ssd_attribute_lines.product_template_value_ids[1].price_extra = 400
|
||||
|
||||
@classmethod
|
||||
def _add_hdd_attribute_line(cls):
|
||||
cls.computer_hdd_attribute_lines = cls.env['product.template.attribute.line'].create(
|
||||
cls.COMPUTER_HDD_PTAL_VALUES)
|
||||
|
||||
cls._setup_hdd_attribute_line()
|
||||
|
||||
@classmethod
|
||||
def _setup_hdd_attribute_line(cls):
|
||||
"""Setup extra prices"""
|
||||
|
||||
cls.computer_hdd_attribute_lines.product_template_value_ids[0].price_extra = 2
|
||||
cls.computer_hdd_attribute_lines.product_template_value_ids[1].price_extra = 4
|
||||
cls.computer_hdd_attribute_lines.product_template_value_ids[2].price_extra = 8
|
||||
|
||||
def _add_ram_exclude_for(self):
|
||||
self._get_product_value_id(self.computer_ram_attribute_lines, self.ram_16).update({
|
||||
'exclude_for': [Command.create({
|
||||
'product_tmpl_id': self.computer.id,
|
||||
'value_ids': [Command.set([
|
||||
self._get_product_value_id(self.computer_hdd_attribute_lines, self.hdd_1).id
|
||||
])],
|
||||
})]
|
||||
})
|
||||
|
||||
def _get_product_value_id(self, product_template_attribute_lines, product_attribute_value):
|
||||
return product_template_attribute_lines.product_template_value_ids.filtered(
|
||||
lambda product_value_id: product_value_id.product_attribute_value_id == product_attribute_value)[0]
|
||||
|
||||
def _get_product_template_attribute_value(self, product_attribute_value, model=False):
|
||||
"""
|
||||
Return the `product.template.attribute.value` matching
|
||||
`product_attribute_value` for self.
|
||||
|
||||
:param: recordset of one product.attribute.value
|
||||
:return: recordset of one product.template.attribute.value if found
|
||||
else empty
|
||||
"""
|
||||
if not model:
|
||||
model = self.computer
|
||||
return model.valid_product_template_attribute_line_ids.filtered(
|
||||
lambda l: l.attribute_id == product_attribute_value.attribute_id
|
||||
).product_template_value_ids.filtered(
|
||||
lambda v: v.product_attribute_value_id == product_attribute_value
|
||||
)
|
||||
|
||||
def _add_exclude(self, m1, m2, product_template=False):
|
||||
m1.update({
|
||||
'exclude_for': [(0, 0, {
|
||||
'product_tmpl_id': (product_template or self.computer).id,
|
||||
'value_ids': [(6, 0, [m2.id])]
|
||||
})]
|
||||
})
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestProductAttributeValueConfig(TestProductAttributeValueCommon):
|
||||
|
||||
def test_product_template_attribute_values_creation(self):
|
||||
self.assertEqual(len(self.computer_ssd_attribute_lines.product_template_value_ids), 2,
|
||||
'Product attribute values (ssd) were not automatically created')
|
||||
self.assertEqual(len(self.computer_ram_attribute_lines.product_template_value_ids), 3,
|
||||
'Product attribute values (ram) were not automatically created')
|
||||
self.assertEqual(len(self.computer_hdd_attribute_lines.product_template_value_ids), 3,
|
||||
'Product attribute values (hdd) were not automatically created')
|
||||
self.assertEqual(len(self.computer_case_size_attribute_lines.product_template_value_ids), 3,
|
||||
'Product attribute values (size) were not automatically created')
|
||||
|
||||
def test_get_variant_for_combination(self):
|
||||
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)
|
||||
|
||||
# completely defined variant
|
||||
combination = computer_ssd_256 + computer_ram_8 + computer_hdd_1
|
||||
ok_variant = self.computer._get_variant_for_combination(combination)
|
||||
self.assertEqual(ok_variant.product_template_attribute_value_ids, combination)
|
||||
|
||||
# over defined variant
|
||||
combination = computer_ssd_256 + computer_ram_8 + computer_ram_16 + computer_hdd_1
|
||||
variant = self.computer._get_variant_for_combination(combination)
|
||||
self.assertEqual(len(variant), 0)
|
||||
|
||||
# under defined variant
|
||||
combination = computer_ssd_256 + computer_ram_8
|
||||
variant = self.computer._get_variant_for_combination(combination)
|
||||
self.assertFalse(variant)
|
||||
|
||||
@mute_logger('odoo.models.unlink')
|
||||
def test_product_filtered_exclude_for(self):
|
||||
"""
|
||||
Super Computer has 18 variants total (2 ssd * 3 ram * 3 hdd)
|
||||
RAM 16 excludes HDD 1, that matches 2 variants:
|
||||
- SSD 256 RAM 16 HDD 1
|
||||
- SSD 512 RAM 16 HDD 1
|
||||
|
||||
=> There has to be 16 variants left when filtered
|
||||
"""
|
||||
computer_ssd_256 = self._get_product_template_attribute_value(self.ssd_256)
|
||||
computer_ssd_512 = self._get_product_template_attribute_value(self.ssd_512)
|
||||
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.assertEqual(len(self.computer._get_possible_variants()), 18)
|
||||
self._add_ram_exclude_for()
|
||||
self.assertEqual(len(self.computer._get_possible_variants()), 16)
|
||||
self.assertTrue(self.computer._get_variant_for_combination(computer_ssd_256 + computer_ram_8 + computer_hdd_1)._is_variant_possible())
|
||||
self.assertFalse(self.computer._get_variant_for_combination(computer_ssd_256 + computer_ram_16 + computer_hdd_1))
|
||||
self.assertFalse(self.computer._get_variant_for_combination(computer_ssd_512 + computer_ram_16 + computer_hdd_1))
|
||||
|
||||
def test_children_product_filtered_exclude_for(self):
|
||||
"""
|
||||
Super Computer Case has 3 variants total (3 size)
|
||||
Reference product Computer with HDD 4 excludes Size M
|
||||
The following variant will be excluded:
|
||||
- Size M
|
||||
|
||||
=> There has to be 2 variants left when filtered
|
||||
"""
|
||||
computer_hdd_4 = self._get_product_template_attribute_value(self.hdd_4)
|
||||
computer_size_m = self._get_product_template_attribute_value(self.size_m, self.computer_case)
|
||||
self._add_exclude(computer_hdd_4, computer_size_m, self.computer_case)
|
||||
self.assertEqual(len(self.computer_case._get_possible_variants(computer_hdd_4)), 2)
|
||||
self.assertFalse(self.computer_case._get_variant_for_combination(computer_size_m)._is_variant_possible(computer_hdd_4))
|
||||
|
||||
@mute_logger('odoo.models.unlink')
|
||||
def test_is_combination_possible(self):
|
||||
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: basic
|
||||
self.assertTrue(self.computer._is_combination_possible(computer_ssd_256 + computer_ram_8 + computer_hdd_1))
|
||||
|
||||
# CASE: ram 16 excluding hdd1
|
||||
self.assertFalse(self.computer._is_combination_possible(computer_ssd_256 + computer_ram_16 + computer_hdd_1))
|
||||
|
||||
# CASE: under defined combination
|
||||
self.assertFalse(self.computer._is_combination_possible(computer_ssd_256 + computer_ram_16))
|
||||
|
||||
# CASE: no combination, no variant, just return the only variant
|
||||
mouse = self.env['product.template'].create({'name': 'Mouse'})
|
||||
self.assertTrue(mouse._is_combination_possible(self.env['product.template.attribute.value']))
|
||||
|
||||
# prep work for the last part of the test
|
||||
color_attribute = self.env['product.attribute'].create({'name': 'Color'})
|
||||
color_red = self.env['product.attribute.value'].create({
|
||||
'name': 'Red',
|
||||
'attribute_id': color_attribute.id,
|
||||
})
|
||||
color_green = self.env['product.attribute.value'].create({
|
||||
'name': 'Green',
|
||||
'attribute_id': color_attribute.id,
|
||||
})
|
||||
self.env['product.template.attribute.line'].create({
|
||||
'product_tmpl_id': mouse.id,
|
||||
'attribute_id': color_attribute.id,
|
||||
'value_ids': [(6, 0, [color_red.id, color_green.id])],
|
||||
})
|
||||
|
||||
mouse_color_red = self._get_product_template_attribute_value(color_red, mouse)
|
||||
mouse_color_green = self._get_product_template_attribute_value(color_green, mouse)
|
||||
|
||||
self._add_exclude(computer_ssd_256, mouse_color_green, mouse)
|
||||
|
||||
variant = self.computer._get_variant_for_combination(computer_ssd_256 + computer_ram_8 + computer_hdd_1)
|
||||
|
||||
# CASE: wrong attributes (mouse_color_red not on computer)
|
||||
self.assertFalse(self.computer._is_combination_possible(computer_ssd_256 + computer_ram_16 + mouse_color_red))
|
||||
|
||||
# CASE: parent ok
|
||||
self.assertTrue(self.computer._is_combination_possible(computer_ssd_256 + computer_ram_8 + computer_hdd_1, mouse_color_red))
|
||||
self.assertTrue(mouse._is_combination_possible(mouse_color_red, computer_ssd_256 + computer_ram_8 + computer_hdd_1))
|
||||
|
||||
# CASE: parent exclusion but good direction (parent is directional)
|
||||
self.assertTrue(self.computer._is_combination_possible(computer_ssd_256 + computer_ram_8 + computer_hdd_1, mouse_color_green))
|
||||
|
||||
# CASE: parent exclusion and wrong direction (parent is directional)
|
||||
self.assertFalse(mouse._is_combination_possible(mouse_color_green, computer_ssd_256 + computer_ram_8 + computer_hdd_1))
|
||||
|
||||
# CASE: deleted combination
|
||||
variant.unlink()
|
||||
self.assertFalse(self.computer._is_combination_possible(computer_ssd_256 + computer_ram_8 + computer_hdd_1))
|
||||
|
||||
# CASE: if multiple variants exist for the same combination and at least
|
||||
# one of them is not archived, the combination is possible
|
||||
combination = computer_ssd_256 + computer_ram_8 + computer_hdd_1
|
||||
self.env['product.product'].create({
|
||||
'product_tmpl_id': self.computer.id,
|
||||
'product_template_attribute_value_ids': [(6, 0, combination.ids)],
|
||||
'active': False,
|
||||
})
|
||||
self.env['product.product'].create({
|
||||
'product_tmpl_id': self.computer.id,
|
||||
'product_template_attribute_value_ids': [(6, 0, combination.ids)],
|
||||
'active': True,
|
||||
})
|
||||
self.assertTrue(self.computer._is_combination_possible(computer_ssd_256 + computer_ram_8 + computer_hdd_1))
|
||||
|
||||
@mute_logger('odoo.models.unlink')
|
||||
def test_get_first_possible_combination(self):
|
||||
computer_ssd_256 = self._get_product_template_attribute_value(self.ssd_256)
|
||||
computer_ssd_512 = self._get_product_template_attribute_value(self.ssd_512)
|
||||
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_ram_32 = self._get_product_template_attribute_value(self.ram_32)
|
||||
computer_hdd_1 = self._get_product_template_attribute_value(self.hdd_1)
|
||||
computer_hdd_2 = self._get_product_template_attribute_value(self.hdd_2)
|
||||
computer_hdd_4 = self._get_product_template_attribute_value(self.hdd_4)
|
||||
self._add_exclude(computer_ram_16, computer_hdd_1)
|
||||
|
||||
# Basic case: test all iterations of generator
|
||||
gen = self.computer._get_possible_combinations()
|
||||
self.assertEqual(next(gen), computer_ssd_256 + computer_ram_8 + computer_hdd_1)
|
||||
self.assertEqual(next(gen), computer_ssd_256 + computer_ram_8 + computer_hdd_2)
|
||||
self.assertEqual(next(gen), computer_ssd_256 + computer_ram_8 + computer_hdd_4)
|
||||
self.assertEqual(next(gen), computer_ssd_256 + computer_ram_16 + computer_hdd_2)
|
||||
self.assertEqual(next(gen), computer_ssd_256 + computer_ram_16 + computer_hdd_4)
|
||||
self.assertEqual(next(gen), computer_ssd_256 + computer_ram_32 + computer_hdd_1)
|
||||
self.assertEqual(next(gen), computer_ssd_256 + computer_ram_32 + computer_hdd_2)
|
||||
self.assertEqual(next(gen), computer_ssd_256 + computer_ram_32 + computer_hdd_4)
|
||||
self.assertEqual(next(gen), computer_ssd_512 + computer_ram_8 + computer_hdd_1)
|
||||
self.assertEqual(next(gen), computer_ssd_512 + computer_ram_8 + computer_hdd_2)
|
||||
self.assertEqual(next(gen), computer_ssd_512 + computer_ram_8 + computer_hdd_4)
|
||||
self.assertEqual(next(gen), computer_ssd_512 + computer_ram_16 + computer_hdd_2)
|
||||
self.assertEqual(next(gen), computer_ssd_512 + computer_ram_16 + computer_hdd_4)
|
||||
self.assertEqual(next(gen), computer_ssd_512 + computer_ram_32 + computer_hdd_1)
|
||||
self.assertEqual(next(gen), computer_ssd_512 + computer_ram_32 + computer_hdd_2)
|
||||
self.assertEqual(next(gen), computer_ssd_512 + computer_ram_32 + computer_hdd_4)
|
||||
self.assertIsNone(next(gen, None))
|
||||
|
||||
# Give priority to ram_16 but it is not allowed by hdd_1 so it should return hhd_2 instead
|
||||
# Test invalidate_cache on product.attribute.value write
|
||||
computer_ram_16.product_attribute_value_id.sequence = -1
|
||||
self.assertEqual(self.computer._get_first_possible_combination(), computer_ssd_256 + computer_ram_16 + computer_hdd_2)
|
||||
|
||||
# Move down the ram, so it will try to change the ram instead of the hdd
|
||||
# Test invalidate_cache on product.attribute write
|
||||
self.ram_attribute.sequence = 10
|
||||
self.assertEqual(self.computer._get_first_possible_combination(), computer_ssd_256 + computer_ram_8 + computer_hdd_1)
|
||||
|
||||
# Give priority to ram_32 and is allowed with the rest so it should return it
|
||||
self.ram_attribute.sequence = 2
|
||||
computer_ram_16.product_attribute_value_id.sequence = 2
|
||||
computer_ram_32.product_attribute_value_id.sequence = -1
|
||||
self.assertEqual(self.computer._get_first_possible_combination(), computer_ssd_256 + computer_ram_32 + computer_hdd_1)
|
||||
|
||||
# Give priority to ram_16 but now it is not allowing any hdd so it should return ram_8 instead
|
||||
computer_ram_32.product_attribute_value_id.sequence = 3
|
||||
computer_ram_16.product_attribute_value_id.sequence = -1
|
||||
self._add_exclude(computer_ram_16, computer_hdd_2)
|
||||
self._add_exclude(computer_ram_16, computer_hdd_4)
|
||||
self.assertEqual(self.computer._get_first_possible_combination(), computer_ssd_256 + computer_ram_8 + computer_hdd_1)
|
||||
|
||||
# Only the last combination is possible
|
||||
computer_ram_16.product_attribute_value_id.sequence = 2
|
||||
self._add_exclude(computer_ram_8, computer_hdd_1)
|
||||
self._add_exclude(computer_ram_8, computer_hdd_2)
|
||||
self._add_exclude(computer_ram_8, computer_hdd_4)
|
||||
self._add_exclude(computer_ram_32, computer_hdd_1)
|
||||
self._add_exclude(computer_ram_32, computer_hdd_2)
|
||||
self._add_exclude(computer_ram_32, computer_ssd_256)
|
||||
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():
|
||||
self._add_exclude(computer_ram_32, computer_hdd_4)
|
||||
|
||||
# If an exclusion rule deletes all variants at once it does not delete the template.
|
||||
# Here we can test `_get_first_possible_combination` with a product template with no variants
|
||||
# Deletes all exclusions
|
||||
for exclusion in computer_ram_32.exclude_for:
|
||||
computer_ram_32.write({
|
||||
'exclude_for': [(2, exclusion.id, 0)]
|
||||
})
|
||||
|
||||
# Activates all exclusions at once
|
||||
computer_ram_32.write({
|
||||
'exclude_for': [(0, computer_ram_32.exclude_for.id, {
|
||||
'product_tmpl_id': self.computer.id,
|
||||
'value_ids': [(6, 0, [computer_hdd_1.id, computer_hdd_2.id, computer_hdd_4.id, computer_ssd_256.id, computer_ssd_512.id])]
|
||||
})]
|
||||
})
|
||||
|
||||
self.assertEqual(self.computer._get_first_possible_combination(), self.env['product.template.attribute.value'])
|
||||
gen = self.computer._get_possible_combinations()
|
||||
self.assertIsNone(next(gen, None))
|
||||
|
||||
# Testing parent case
|
||||
mouse = self.env['product.template'].create({'name': 'Mouse'})
|
||||
self.assertTrue(mouse._is_combination_possible(self.env['product.template.attribute.value']))
|
||||
|
||||
# prep work for the last part of the test
|
||||
color_attribute = self.env['product.attribute'].create({'name': 'Color'})
|
||||
color_red = self.env['product.attribute.value'].create({
|
||||
'name': 'Red',
|
||||
'attribute_id': color_attribute.id,
|
||||
})
|
||||
color_green = self.env['product.attribute.value'].create({
|
||||
'name': 'Green',
|
||||
'attribute_id': color_attribute.id,
|
||||
})
|
||||
self.env['product.template.attribute.line'].create({
|
||||
'product_tmpl_id': mouse.id,
|
||||
'attribute_id': color_attribute.id,
|
||||
'value_ids': [(6, 0, [color_red.id, color_green.id])],
|
||||
})
|
||||
|
||||
mouse_color_red = self._get_product_template_attribute_value(color_red, mouse)
|
||||
mouse_color_green = self._get_product_template_attribute_value(color_green, mouse)
|
||||
|
||||
self._add_exclude(computer_ssd_256, mouse_color_red, mouse)
|
||||
self.assertEqual(mouse._get_first_possible_combination(parent_combination=computer_ssd_256 + computer_ram_8 + computer_hdd_1), mouse_color_green)
|
||||
|
||||
# Test to see if several attribute_line for same attribute is well handled
|
||||
color_blue = self.env['product.attribute.value'].create({
|
||||
'name': 'Blue',
|
||||
'attribute_id': color_attribute.id,
|
||||
})
|
||||
color_yellow = self.env['product.attribute.value'].create({
|
||||
'name': 'Yellow',
|
||||
'attribute_id': color_attribute.id,
|
||||
})
|
||||
self.env['product.template.attribute.line'].create({
|
||||
'product_tmpl_id': mouse.id,
|
||||
'attribute_id': color_attribute.id,
|
||||
'value_ids': [(6, 0, [color_blue.id, color_yellow.id])],
|
||||
})
|
||||
mouse_color_yellow = self._get_product_template_attribute_value(color_yellow, mouse)
|
||||
self.assertEqual(mouse._get_first_possible_combination(necessary_values=mouse_color_yellow), mouse_color_red + mouse_color_yellow)
|
||||
|
||||
# Making sure it's not extremely slow (has to discard invalid combinations early !)
|
||||
product_template = self.env['product.template'].create({
|
||||
'name': 'many combinations',
|
||||
})
|
||||
|
||||
for i in range(10):
|
||||
# create the attributes
|
||||
product_attribute = self.env['product.attribute'].create({
|
||||
'name': "att %s" % i,
|
||||
'create_variant': 'dynamic',
|
||||
'sequence': i,
|
||||
})
|
||||
|
||||
for j in range(50):
|
||||
# create the attribute values
|
||||
value = self.env['product.attribute.value'].create([{
|
||||
'name': "val %s" % j,
|
||||
'attribute_id': product_attribute.id,
|
||||
'sequence': j,
|
||||
}])
|
||||
|
||||
# set attribute and attribute values on the template
|
||||
self.env['product.template.attribute.line'].create([{
|
||||
'attribute_id': product_attribute.id,
|
||||
'product_tmpl_id': product_template.id,
|
||||
'value_ids': [(6, 0, product_attribute.value_ids.ids)]
|
||||
}])
|
||||
|
||||
self._add_exclude(
|
||||
self._get_product_template_attribute_value(product_template.attribute_line_ids[1].value_ids[0],
|
||||
model=product_template),
|
||||
self._get_product_template_attribute_value(product_template.attribute_line_ids[0].value_ids[0],
|
||||
model=product_template),
|
||||
product_template)
|
||||
self._add_exclude(
|
||||
self._get_product_template_attribute_value(product_template.attribute_line_ids[0].value_ids[0],
|
||||
model=product_template),
|
||||
self._get_product_template_attribute_value(product_template.attribute_line_ids[1].value_ids[1],
|
||||
model=product_template),
|
||||
product_template)
|
||||
|
||||
combination = self.env['product.template.attribute.value']
|
||||
for idx, ptal in enumerate(product_template.attribute_line_ids):
|
||||
if idx != 1:
|
||||
value = ptal.product_template_value_ids[0]
|
||||
else:
|
||||
value = ptal.product_template_value_ids[2]
|
||||
combination += value
|
||||
|
||||
started_at = time.time()
|
||||
self.assertEqual(product_template._get_first_possible_combination(), combination)
|
||||
elapsed = time.time() - started_at
|
||||
# It should be about instantaneous, 0.5 to avoid false positives
|
||||
self.assertLess(elapsed, 0.5)
|
||||
|
||||
@mute_logger('odoo.models.unlink')
|
||||
def test_get_closest_possible_combinations(self):
|
||||
computer_ssd_256 = self._get_product_template_attribute_value(self.ssd_256)
|
||||
computer_ssd_512 = self._get_product_template_attribute_value(self.ssd_512)
|
||||
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_ram_32 = self._get_product_template_attribute_value(self.ram_32)
|
||||
computer_hdd_1 = self._get_product_template_attribute_value(self.hdd_1)
|
||||
computer_hdd_2 = self._get_product_template_attribute_value(self.hdd_2)
|
||||
computer_hdd_4 = self._get_product_template_attribute_value(self.hdd_4)
|
||||
self._add_exclude(computer_ram_16, computer_hdd_1)
|
||||
|
||||
# CASE nothing special (test 2 iterations)
|
||||
gen = self.computer._get_closest_possible_combinations(None)
|
||||
self.assertEqual(next(gen), computer_ssd_256 + computer_ram_8 + computer_hdd_1)
|
||||
self.assertEqual(next(gen), computer_ssd_256 + computer_ram_8 + computer_hdd_2)
|
||||
|
||||
# CASE contains computer_hdd_1 (test all iterations)
|
||||
gen = self.computer._get_closest_possible_combinations(computer_hdd_1)
|
||||
self.assertEqual(next(gen), computer_ssd_256 + computer_ram_8 + computer_hdd_1)
|
||||
self.assertEqual(next(gen), computer_ssd_256 + computer_ram_32 + computer_hdd_1)
|
||||
self.assertEqual(next(gen), computer_ssd_512 + computer_ram_8 + computer_hdd_1)
|
||||
self.assertEqual(next(gen), computer_ssd_512 + computer_ram_32 + computer_hdd_1)
|
||||
self.assertIsNone(next(gen, None))
|
||||
|
||||
# CASE contains computer_hdd_2
|
||||
self.assertEqual(self.computer._get_closest_possible_combination(computer_hdd_2),
|
||||
computer_ssd_256 + computer_ram_8 + computer_hdd_2)
|
||||
|
||||
# CASE contains computer_hdd_2, computer_ram_16
|
||||
self.assertEqual(self.computer._get_closest_possible_combination(computer_hdd_2 + computer_ram_16),
|
||||
computer_ssd_256 + computer_ram_16 + computer_hdd_2)
|
||||
|
||||
# CASE invalid combination (excluded):
|
||||
self.assertEqual(self.computer._get_closest_possible_combination(computer_hdd_1 + computer_ram_16),
|
||||
computer_ssd_256 + computer_ram_8 + computer_hdd_1)
|
||||
|
||||
# CASE invalid combination (too much):
|
||||
self.assertEqual(self.computer._get_closest_possible_combination(computer_ssd_256 + computer_ram_8 + computer_hdd_4 + computer_hdd_2),
|
||||
computer_ssd_256 + computer_ram_8 + computer_hdd_4)
|
||||
|
||||
# Make sure this is not extremely slow:
|
||||
product_template = self.env['product.template'].create({
|
||||
'name': 'many combinations',
|
||||
})
|
||||
|
||||
for i in range(10):
|
||||
# create the attributes
|
||||
product_attribute = self.env['product.attribute'].create({
|
||||
'name': "att %s" % i,
|
||||
'create_variant': 'dynamic',
|
||||
'sequence': i,
|
||||
})
|
||||
|
||||
for j in range(10):
|
||||
# create the attribute values
|
||||
self.env['product.attribute.value'].create([{
|
||||
'name': "val %s/%s" % (i, j),
|
||||
'attribute_id': product_attribute.id,
|
||||
'sequence': j,
|
||||
}])
|
||||
|
||||
# set attribute and attribute values on the template
|
||||
self.env['product.template.attribute.line'].create([{
|
||||
'attribute_id': product_attribute.id,
|
||||
'product_tmpl_id': product_template.id,
|
||||
'value_ids': [(6, 0, product_attribute.value_ids.ids)]
|
||||
}])
|
||||
|
||||
# Get a value in the middle for each attribute to make sure it would
|
||||
# take time to reach it (if looping one by one like before the fix).
|
||||
combination = self.env['product.template.attribute.value']
|
||||
for ptal in product_template.attribute_line_ids:
|
||||
combination += ptal.product_template_value_ids[5]
|
||||
|
||||
started_at = time.time()
|
||||
self.assertEqual(product_template._get_closest_possible_combination(combination), combination)
|
||||
elapsed = time.time() - started_at
|
||||
# It should take around 10ms, but to avoid false positives we check an
|
||||
# higher value. Before the fix it would take hours.
|
||||
self.assertLess(elapsed, 0.5)
|
||||
|
||||
@mute_logger('odoo.models.unlink')
|
||||
def test_clear_caches(self):
|
||||
"""The goal of this test is to make sure the cache is invalidated when
|
||||
it should be."""
|
||||
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_hdd_1 = self._get_product_template_attribute_value(self.hdd_1)
|
||||
combination = computer_ssd_256 + computer_ram_8 + computer_hdd_1
|
||||
|
||||
# CASE: initial result of _get_variant_for_combination
|
||||
variant = self.computer._get_variant_for_combination(combination)
|
||||
self.assertTrue(variant)
|
||||
|
||||
# CASE: clear_caches in product.product unlink
|
||||
variant.unlink()
|
||||
self.assertFalse(self.computer._get_variant_for_combination(combination))
|
||||
|
||||
# CASE: clear_caches in product.product create
|
||||
variant = self.env['product.product'].create({
|
||||
'product_tmpl_id': self.computer.id,
|
||||
'product_template_attribute_value_ids': [(6, 0, combination.ids)],
|
||||
})
|
||||
self.assertEqual(variant, self.computer._get_variant_for_combination(combination))
|
||||
|
||||
# CASE: clear_caches in product.product write
|
||||
variant.product_template_attribute_value_ids = False
|
||||
self.assertFalse(self.computer._get_variant_id_for_combination(combination))
|
||||
|
||||
def test_constraints(self):
|
||||
"""The goal of this test is to make sure constraints are correct."""
|
||||
with self.assertRaises(UserError, msg="can't change variants creation mode of attribute used on product"):
|
||||
self.ram_attribute.create_variant = 'no_variant'
|
||||
|
||||
with self.assertRaises(UserError, msg="can't delete attribute used on product"):
|
||||
self.ram_attribute.unlink()
|
||||
|
||||
with self.assertRaises(UserError, msg="can't change the attribute of an value used on product"):
|
||||
self.ram_32.attribute_id = self.hdd_attribute.id
|
||||
|
||||
with self.assertRaises(UserError, msg="can't delete value used on product"):
|
||||
self.ram_32.unlink()
|
||||
|
||||
with self.assertRaises(ValidationError, msg="can't have attribute without value on product"):
|
||||
self.env['product.template.attribute.line'].create({
|
||||
'product_tmpl_id': self.computer_case.id,
|
||||
'attribute_id': self.hdd_attribute.id,
|
||||
'value_ids': [(6, 0, [])],
|
||||
})
|
||||
|
||||
with self.assertRaises(ValidationError, msg="value attribute must match line attribute"):
|
||||
self.env['product.template.attribute.line'].create({
|
||||
'product_tmpl_id': self.computer_case.id,
|
||||
'attribute_id': self.ram_attribute.id,
|
||||
'value_ids': [(6, 0, [self.ssd_256.id])],
|
||||
})
|
||||
|
||||
with self.assertRaises(UserError, msg="can't change the attribute of an attribute line"):
|
||||
self.computer_ssd_attribute_lines.attribute_id = self.hdd_attribute.id
|
||||
|
||||
with self.assertRaises(UserError, msg="can't change the product of an attribute line"):
|
||||
self.computer_ssd_attribute_lines.product_tmpl_id = self.computer_case.id
|
||||
|
||||
with self.assertRaises(UserError, msg="can't change the value of a product template attribute value"):
|
||||
self.computer_ram_attribute_lines.product_template_value_ids[0].product_attribute_value_id = self.hdd_1
|
||||
|
||||
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):
|
||||
"""
|
||||
Create a product and give it a product attribute then archive it, delete the product attribute,
|
||||
unarchive the product and check that the product is not related to the product attribute.
|
||||
"""
|
||||
product_attribut = self.env['product.attribute'].create({
|
||||
'name': 'PA',
|
||||
'sequence': 1,
|
||||
'create_variant': 'no_variant',
|
||||
})
|
||||
a1 = self.env['product.attribute.value'].create({
|
||||
'name': 'pa_value',
|
||||
'attribute_id': product_attribut.id,
|
||||
'sequence': 1,
|
||||
})
|
||||
product = self.env['product.template'].create({
|
||||
'name': 'P1',
|
||||
'type': 'consu',
|
||||
'attribute_line_ids': [(0, 0, {
|
||||
'attribute_id': product_attribut.id,
|
||||
'value_ids': [(6, 0, [a1.id])],
|
||||
})]
|
||||
})
|
||||
self.assertEqual(product_attribut.number_related_products, 1, 'The product attribute must have an associated product')
|
||||
product.action_archive()
|
||||
self.assertFalse(product.active, 'The product should be archived.')
|
||||
product.write({'attribute_line_ids': [[5, 0, 0]]})
|
||||
product.action_unarchive()
|
||||
self.assertTrue(product.active, 'The product should be unarchived.')
|
||||
self.assertEqual(product_attribut.number_related_products, 0, 'The product attribute must not have an associated product')
|
||||
|
||||
def test_copy_extra_prices_of_product_attribute_values(self):
|
||||
"""
|
||||
Check that the extra price of attributes are copied along the duplication of a product.
|
||||
"""
|
||||
product_template = self.computer
|
||||
extra_prices = product_template.attribute_line_ids.product_template_value_ids.mapped(
|
||||
'price_extra'
|
||||
)
|
||||
copied_template = product_template.copy()
|
||||
copied_extra_prices = copied_template.attribute_line_ids.product_template_value_ids.mapped(
|
||||
'price_extra'
|
||||
)
|
||||
self.assertEqual(extra_prices, copied_extra_prices)
|
||||
|
|
@ -0,0 +1,285 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from datetime import datetime
|
||||
import time
|
||||
|
||||
from odoo.fields import Command, first
|
||||
from odoo.tools import float_compare
|
||||
|
||||
from odoo.addons.product.tests.common import ProductCommon
|
||||
|
||||
|
||||
class TestProductPricelist(ProductCommon):
|
||||
|
||||
@classmethod
|
||||
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
|
||||
}).id
|
||||
cls.computer_SC234 = cls.env['product.product'].create({
|
||||
'name': 'Desk Combination',
|
||||
'categ_id': cls.category_5_id,
|
||||
})
|
||||
cls.ipad_retina_display = cls.env['product.product'].create({
|
||||
'name': 'Customizable Desk',
|
||||
})
|
||||
cls.custom_computer_kit = cls.env['product.product'].create({
|
||||
'name': 'Corner Desk Right Sit',
|
||||
'categ_id': cls.category_5_id,
|
||||
})
|
||||
cls.ipad_mini = cls.env['product.product'].create({
|
||||
'name': 'Large Cabinet',
|
||||
'categ_id': cls.category_5_id,
|
||||
'standard_price': 800.0,
|
||||
})
|
||||
cls.monitor = cls.env['product.product'].create({
|
||||
'name': 'Super nice monitor',
|
||||
'categ_id': cls.category_5_id,
|
||||
'list_price': 1000.0,
|
||||
})
|
||||
|
||||
cls.apple_in_ear_headphones = cls.env['product.product'].create({
|
||||
'name': 'Storage Box',
|
||||
'categ_id': cls.category_5_id,
|
||||
})
|
||||
cls.laptop_E5023 = cls.env['product.product'].create({
|
||||
'name': 'Office Chair',
|
||||
'categ_id': cls.category_5_id,
|
||||
})
|
||||
cls.laptop_S3450 = cls.env['product.product'].create({
|
||||
'name': 'Acoustic Bloc Screens',
|
||||
'categ_id': cls.category_5_id,
|
||||
})
|
||||
cls.product_multi_price = cls.env['product.product'].create({
|
||||
'name': 'Multi Price',
|
||||
'categ_id': cls.product_category.id,
|
||||
})
|
||||
|
||||
cls.new_currency = cls.env['res.currency'].create({
|
||||
'name': 'Wonderful Currency',
|
||||
'symbol': ':)',
|
||||
'rate_ids': [Command.create({'rate': 10, 'name': time.strftime('%Y-%m-%d')})],
|
||||
})
|
||||
|
||||
cls.ipad_retina_display.write({'uom_id': cls.uom_unit.id, 'categ_id': cls.category_5_id})
|
||||
cls.customer_pricelist = cls.env['product.pricelist'].create({
|
||||
'name': 'Customer Pricelist',
|
||||
'item_ids': [
|
||||
Command.create({
|
||||
'name': 'Default pricelist',
|
||||
'compute_price': 'formula',
|
||||
'base': 'pricelist',
|
||||
'base_pricelist_id': cls.pricelist.id,
|
||||
}),
|
||||
Command.create({
|
||||
'name': '10% Discount on Assemble Computer',
|
||||
'applied_on': '1_product',
|
||||
'product_tmpl_id': cls.ipad_retina_display.product_tmpl_id.id,
|
||||
'compute_price': 'formula',
|
||||
'base': 'list_price',
|
||||
'price_discount': 10,
|
||||
}),
|
||||
Command.create({
|
||||
'name': '1 surchange on Laptop',
|
||||
'applied_on': '1_product',
|
||||
'product_tmpl_id': cls.laptop_E5023.product_tmpl_id.id,
|
||||
'compute_price': 'formula',
|
||||
'base': 'list_price',
|
||||
'price_surcharge': 1,
|
||||
}),
|
||||
Command.create({
|
||||
'name': '5% Discount on all Computer related products',
|
||||
'applied_on': '2_product_category',
|
||||
'min_quantity': 2,
|
||||
'compute_price': 'formula',
|
||||
'base': 'list_price',
|
||||
'categ_id': cls.product_category.id,
|
||||
'price_discount': 5,
|
||||
}),
|
||||
Command.create({
|
||||
'name': '30% Discount on all products',
|
||||
'applied_on': '3_global',
|
||||
'date_start': '2011-12-27',
|
||||
'date_end': '2011-12-31',
|
||||
'compute_price': 'formula',
|
||||
'price_discount': 30,
|
||||
'base': 'list_price',
|
||||
}),
|
||||
Command.create({
|
||||
'name': 'Fixed on all products',
|
||||
'applied_on': '1_product',
|
||||
'product_tmpl_id': cls.monitor.product_tmpl_id.id,
|
||||
'date_start': '2020-04-06 09:00:00',
|
||||
'date_end': '2020-04-09 12:00:00',
|
||||
'compute_price': 'formula',
|
||||
'price_discount': 50,
|
||||
'base': 'list_price',
|
||||
}),
|
||||
Command.create({
|
||||
'name': 'Multi Price Customer',
|
||||
'applied_on': '1_product',
|
||||
'product_tmpl_id': cls.product_multi_price.product_tmpl_id.id,
|
||||
'compute_price': 'fixed',
|
||||
'fixed_price': 99,
|
||||
'base': 'list_price',
|
||||
}),
|
||||
],
|
||||
})
|
||||
cls.business_pricelist = cls.env['product.pricelist'].create({
|
||||
'name': 'Business Pricelist',
|
||||
'item_ids': [(0, 0, {
|
||||
'name': 'Multi Price Business',
|
||||
'applied_on': '1_product',
|
||||
'product_tmpl_id': cls.product_multi_price.product_tmpl_id.id,
|
||||
'compute_price': 'fixed',
|
||||
'fixed_price': 50,
|
||||
'base': 'list_price'
|
||||
})]
|
||||
})
|
||||
|
||||
def test_10_calculation_price_of_products_pricelist(self):
|
||||
"""Test calculation of product price based on pricelist"""
|
||||
# I check sale price of Customizable Desk
|
||||
context = {}
|
||||
context.update({'pricelist': self.customer_pricelist.id, 'quantity': 1})
|
||||
product = self.ipad_retina_display
|
||||
price = self.customer_pricelist._get_product_price(product, quantity=1.0)
|
||||
msg = "Wrong sale price: Customizable Desk. should be %s instead of %s" % (price, (product.lst_price-product.lst_price*(0.10)))
|
||||
self.assertEqual(float_compare(
|
||||
price, (product.lst_price-product.lst_price*(0.10)), precision_digits=2), 0, msg)
|
||||
|
||||
# I check sale price of Laptop.
|
||||
product = self.laptop_E5023
|
||||
price = self.customer_pricelist._get_product_price(product, quantity=1.0)
|
||||
msg = "Wrong sale price: Laptop. should be %s instead of %s" % (price, (product.lst_price + 1))
|
||||
self.assertEqual(float_compare(price, product.lst_price + 1, precision_digits=2), 0, msg)
|
||||
|
||||
# I check sale price of IT component.
|
||||
product = self.apple_in_ear_headphones
|
||||
price = self.customer_pricelist._get_product_price(product, quantity=1.0)
|
||||
msg = "Wrong sale price: IT component. should be %s instead of %s" % (price, product.lst_price)
|
||||
self.assertEqual(float_compare(price, product.lst_price, precision_digits=2), 0, msg)
|
||||
|
||||
# I check sale price of IT component if more than 3 Unit.
|
||||
context.update({'quantity': 5})
|
||||
product = self.laptop_S3450
|
||||
price = self.customer_pricelist._get_product_price(product, quantity=5.0)
|
||||
msg = "Wrong sale price: IT component if more than 3 Unit. should be %s instead of %s" % (price, (product.lst_price-product.lst_price*(0.05)))
|
||||
self.assertEqual(float_compare(price, product.lst_price-product.lst_price*(0.05), precision_digits=2), 0, msg)
|
||||
|
||||
# I check sale price of LCD Monitor.
|
||||
product = self.ipad_mini
|
||||
price = self.customer_pricelist._get_product_price(product, quantity=1.0)
|
||||
msg = "Wrong sale price: LCD Monitor. should be %s instead of %s" % (price, product.lst_price)
|
||||
self.assertEqual(float_compare(price, product.lst_price, precision_digits=2), 0, msg)
|
||||
|
||||
# I check sale price of LCD Monitor on end of year.
|
||||
price = self.customer_pricelist._get_product_price(product, quantity=1.0, date='2011-12-31')
|
||||
msg = "Wrong sale price: LCD Monitor on end of year. should be %s instead of %s" % (price, product.lst_price-product.lst_price*(0.30))
|
||||
self.assertEqual(float_compare(price, product.lst_price-product.lst_price*(0.30), precision_digits=2), 0, msg)
|
||||
|
||||
# Check if the pricelist is applied at precise datetime
|
||||
product = self.monitor
|
||||
price = self.customer_pricelist._get_product_price(product, quantity=1.0, date='2020-04-05 08:00:00')
|
||||
context.update({'quantity': 1, 'date': datetime.strptime('2020-04-05 08:00:00', '%Y-%m-%d %H:%M:%S')})
|
||||
msg = "Wrong cost price: LCD Monitor. should be 1000 instead of %s" % price
|
||||
self.assertEqual(
|
||||
float_compare(price, product.lst_price, precision_digits=2), 0,
|
||||
msg)
|
||||
price = self.customer_pricelist._get_product_price(product, quantity=1.0, date='2020-04-06 10:00:00')
|
||||
msg = "Wrong cost price: LCD Monitor. should be 500 instead of %s" % price
|
||||
self.assertEqual(
|
||||
float_compare(price, product.lst_price/2, precision_digits=2), 0,
|
||||
msg)
|
||||
|
||||
# Check if the price is different when we change the pricelist
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
def test_20_price_different_currency_pricelist(self):
|
||||
pricelist = self.env['product.pricelist'].create({
|
||||
'name': 'Currency Pricelist',
|
||||
'currency_id': self.new_currency.id,
|
||||
'item_ids': [(0, 0, {
|
||||
'compute_price': 'formula',
|
||||
'base': 'list_price',
|
||||
'price_surcharge': 100
|
||||
})]
|
||||
})
|
||||
price = pricelist._get_product_price(self.monitor, quantity=1.0)
|
||||
# product price use the currency of the pricelist
|
||||
self.assertEqual(price, 10100)
|
||||
|
||||
def test_21_price_diff_cur_min_margin_pricelist(self):
|
||||
pricelist = self.env['product.pricelist'].create({
|
||||
'name': 'Currency with Margin Pricelist',
|
||||
'currency_id': self.new_currency.id,
|
||||
'item_ids': [(0, 0, {
|
||||
'compute_price': 'formula',
|
||||
'base': 'list_price',
|
||||
'price_min_margin': 10,
|
||||
'price_max_margin': 100,
|
||||
})]
|
||||
})
|
||||
price = pricelist._get_product_price(self.monitor, quantity=1.0)
|
||||
# product price use the currency of the pricelist
|
||||
self.assertEqual(price, 10010)
|
||||
|
||||
def test_22_price_diff_cur_max_margin_pricelist(self):
|
||||
pricelist = self.env['product.pricelist'].create({
|
||||
'name': 'Currency with Margin Pricelist',
|
||||
'currency_id': self.new_currency.id,
|
||||
'item_ids': [(0, 0, {
|
||||
'compute_price': 'formula',
|
||||
'base': 'list_price',
|
||||
'price_surcharge': 100,
|
||||
'price_max_margin': 90
|
||||
})]
|
||||
})
|
||||
price = pricelist._get_product_price(self.monitor, quantity=1.0)
|
||||
# product price use the currency of the pricelist
|
||||
self.assertEqual(price, 10090)
|
||||
|
||||
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({
|
||||
'name': 'Customer Pricelist',
|
||||
'item_ids': [
|
||||
Command.create({
|
||||
'compute_price': 'formula',
|
||||
'base': 'pricelist',
|
||||
}),
|
||||
] * 101,
|
||||
})
|
||||
self.customer_pricelist.unlink()
|
||||
|
||||
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.digits = 3
|
||||
pricelist_item = first(self.customer_pricelist.item_ids[0])
|
||||
precise_value = 1.234
|
||||
|
||||
# Act: Set a value for the increased precision
|
||||
pricelist_item.min_quantity = precise_value
|
||||
|
||||
# Assert: The set value is kept
|
||||
self.assertEqual(pricelist_item.min_quantity, precise_value)
|
||||
157
odoo-bringout-oca-ocb-product/product/tests/test_seller.py
Normal file
157
odoo-bringout-oca-ocb-product/product/tests/test_seller.py
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.fields import first, Command
|
||||
from odoo.tests import tagged, TransactionCase
|
||||
from odoo.tools import float_compare
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestSeller(TransactionCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.product_service = cls.env['product.product'].create({
|
||||
'name': 'Virtual Home Staging',
|
||||
})
|
||||
cls.product_service.default_code = 'DEFCODE'
|
||||
cls.product_consu = cls.env['product.product'].create({
|
||||
'name': 'Boudin',
|
||||
'type': 'consu',
|
||||
})
|
||||
cls.product_consu.default_code = 'DEFCODE'
|
||||
cls.asustec = cls.env['res.partner'].create({'name': 'Wood Corner'})
|
||||
cls.camptocamp = cls.env['res.partner'].create({'name': 'Azure Interior'})
|
||||
|
||||
def test_10_sellers(self):
|
||||
self.product_service.write({'seller_ids': [
|
||||
(0, 0, {'partner_id': self.asustec.id, 'product_code': 'ASUCODE'}),
|
||||
(0, 0, {'partner_id': self.camptocamp.id, 'product_code': 'C2CCODE'}),
|
||||
]})
|
||||
|
||||
default_code = self.product_service.code
|
||||
self.assertEqual("DEFCODE", default_code, "Default code not used in product name")
|
||||
|
||||
context_code = self.product_service\
|
||||
.with_context(partner_id=self.camptocamp.id)\
|
||||
.code
|
||||
self.assertEqual('C2CCODE', context_code, "Partner's code not used in product name with context set")
|
||||
|
||||
def test_20_sellers_company(self):
|
||||
company_a = self.env.company
|
||||
company_b = self.env['res.company'].create({
|
||||
'name': 'Saucisson Inc.',
|
||||
})
|
||||
self.product_consu.write({'seller_ids': [
|
||||
(0, 0, {'partner_id': self.asustec.id, 'product_code': 'A', 'company_id': company_a.id}),
|
||||
(0, 0, {'partner_id': self.asustec.id, 'product_code': 'B', 'company_id': company_b.id}),
|
||||
(0, 0, {'partner_id': self.asustec.id, 'product_code': 'NO', 'company_id': False}),
|
||||
]})
|
||||
|
||||
names = 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(
|
||||
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(
|
||||
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")
|
||||
|
||||
def test_30_select_seller(self):
|
||||
self.res_partner_1 = self.asustec
|
||||
self.res_partner_4 = self.camptocamp
|
||||
self.ipad_mini, self.monitor = self.env['product.product'].create([{
|
||||
'name': 'Large Cabinet',
|
||||
'standard_price': 800.0,
|
||||
}, {
|
||||
'name': 'Super nice monitor',
|
||||
'list_price': 1000.0,
|
||||
}])
|
||||
|
||||
self.env['product.supplierinfo'].create([
|
||||
{
|
||||
'partner_id': self.res_partner_1.id,
|
||||
'product_tmpl_id': self.ipad_mini.product_tmpl_id.id,
|
||||
'delay': 3,
|
||||
'min_qty': 1,
|
||||
'price': 750,
|
||||
}, {
|
||||
'partner_id': self.res_partner_4.id,
|
||||
'product_tmpl_id': self.ipad_mini.product_tmpl_id.id,
|
||||
'delay': 3,
|
||||
'min_qty': 1,
|
||||
'price': 790,
|
||||
}, {
|
||||
'partner_id': self.res_partner_4.id,
|
||||
'product_tmpl_id': self.ipad_mini.product_tmpl_id.id,
|
||||
'delay': 3,
|
||||
'min_qty': 3,
|
||||
'price': 785,
|
||||
}, {
|
||||
'partner_id': self.res_partner_4.id,
|
||||
'product_tmpl_id': self.monitor.product_tmpl_id.id,
|
||||
'delay': 3,
|
||||
'min_qty': 3,
|
||||
'price': 100,
|
||||
}
|
||||
])
|
||||
|
||||
product = self.ipad_mini
|
||||
# Supplierinfo pricing
|
||||
|
||||
# I check cost price of LCD Monitor.
|
||||
price = product._select_seller(partner_id=self.res_partner_4, quantity=1.0).price
|
||||
msg = "Wrong cost price: LCD Monitor. should be 790 instead of %s" % price
|
||||
self.assertEqual(float_compare(price, 790, precision_digits=2), 0, msg)
|
||||
|
||||
# I check cost price of LCD Monitor if more than 3 Unit.
|
||||
price = product._select_seller(partner_id=self.res_partner_4, quantity=3.0).price
|
||||
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_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.digits = 3
|
||||
product = self.product_service
|
||||
product.seller_ids = [
|
||||
Command.create({
|
||||
'partner_id': self.asustec.id,
|
||||
}),
|
||||
]
|
||||
supplier_info = first(product.seller_ids)
|
||||
precise_value = 1.234
|
||||
|
||||
# Act: Set a value for the increased precision
|
||||
supplier_info.min_qty = precise_value
|
||||
|
||||
# Assert: The set value is kept
|
||||
self.assertEqual(supplier_info.min_qty, precise_value)
|
||||
|
||||
def test_50_seller_ids(self):
|
||||
vendors = self.env['product.supplierinfo'].create([{
|
||||
'partner_id': self.asustec.id,
|
||||
'product_tmpl_id': self.product_consu.product_tmpl_id.id,
|
||||
}, {
|
||||
'partner_id': self.camptocamp.id,
|
||||
'product_id': self.product_consu.id,
|
||||
}])
|
||||
self.assertEqual(vendors, self.product_consu.seller_ids,
|
||||
"Sellers of a product should be listed in the product's seller_ids")
|
||||
vendors.write({'product_id': False})
|
||||
self.assertEqual(vendors, self.product_consu.seller_ids,
|
||||
"Setting the product_id to False shouldn't affect seller_ids.")
|
||||
1553
odoo-bringout-oca-ocb-product/product/tests/test_variants.py
Normal file
1553
odoo-bringout-oca-ocb-product/product/tests/test_variants.py
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue