619 lines
22 KiB
Python
619 lines
22 KiB
Python
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
|
# this repository contains the full copyright notices and license terms.
|
|
|
|
import io
|
|
import unittest
|
|
from decimal import Decimal
|
|
|
|
from trytond.modules.company.tests import CompanyTestMixin
|
|
from trytond.modules.product import round_price
|
|
from trytond.modules.product.exceptions import UOMAccessError
|
|
from trytond.modules.product.product import barcode
|
|
from trytond.pool import Pool
|
|
from trytond.tests.test_tryton import ModuleTestCase, with_transaction
|
|
from trytond.transaction import Transaction
|
|
|
|
|
|
class ProductTestCase(CompanyTestMixin, ModuleTestCase):
|
|
'Test Product module'
|
|
module = 'product'
|
|
|
|
@with_transaction()
|
|
def test_uom_non_zero_rate_factor(self):
|
|
'Test uom non_zero_rate_factor constraint'
|
|
pool = Pool()
|
|
UomCategory = pool.get('product.uom.category')
|
|
Uom = pool.get('product.uom')
|
|
transaction = Transaction()
|
|
category, = UomCategory.create([{'name': 'Test'}])
|
|
|
|
self.assertRaises(Exception, Uom.create, [{
|
|
'name': 'Test',
|
|
'symbol': 'T',
|
|
'category': category.id,
|
|
'rate': 0,
|
|
'factor': 0,
|
|
}])
|
|
transaction.rollback()
|
|
|
|
def create():
|
|
category, = UomCategory.create([{'name': 'Test'}])
|
|
return Uom.create([{
|
|
'name': 'Test',
|
|
'symbol': 'T',
|
|
'category': category.id,
|
|
'rate': 1.0,
|
|
'factor': 1.0,
|
|
}])[0]
|
|
|
|
uom = create()
|
|
self.assertRaises(Exception, Uom.write, [uom], {
|
|
'rate': 0.0,
|
|
})
|
|
transaction.rollback()
|
|
|
|
uom = create()
|
|
self.assertRaises(Exception, Uom.write, [uom], {
|
|
'factor': 0.0,
|
|
})
|
|
transaction.rollback()
|
|
|
|
uom = create()
|
|
self.assertRaises(Exception, Uom.write, [uom], {
|
|
'rate': 0.0,
|
|
'factor': 0.0,
|
|
})
|
|
transaction.rollback()
|
|
|
|
@with_transaction()
|
|
def test_uom_check_factor_and_rate(self):
|
|
'Test uom check_factor_and_rate constraint'
|
|
pool = Pool()
|
|
UomCategory = pool.get('product.uom.category')
|
|
Uom = pool.get('product.uom')
|
|
transaction = Transaction()
|
|
category, = UomCategory.create([{'name': 'Test'}])
|
|
|
|
self.assertRaises(Exception, Uom.create, [{
|
|
'name': 'Test',
|
|
'symbol': 'T',
|
|
'category': category.id,
|
|
'rate': 2,
|
|
'factor': 2,
|
|
}])
|
|
transaction.rollback()
|
|
|
|
def create():
|
|
category, = UomCategory.create([{'name': 'Test'}])
|
|
return Uom.create([{
|
|
'name': 'Test',
|
|
'symbol': 'T',
|
|
'category': category.id,
|
|
'rate': 1.0,
|
|
'factor': 1.0,
|
|
}])[0]
|
|
|
|
uom = create()
|
|
self.assertRaises(Exception, Uom.write, [uom], {
|
|
'rate': 2.0,
|
|
})
|
|
transaction.rollback()
|
|
|
|
uom = create()
|
|
self.assertRaises(Exception, Uom.write, [uom], {
|
|
'factor': 2.0,
|
|
})
|
|
transaction.rollback()
|
|
|
|
@with_transaction()
|
|
def test_uom_select_accurate_field(self):
|
|
'Test uom select_accurate_field function'
|
|
pool = Pool()
|
|
Uom = pool.get('product.uom')
|
|
tests = [
|
|
('Meter', 'factor'),
|
|
('Kilometer', 'factor'),
|
|
('Centimeter', 'rate'),
|
|
('Foot', 'factor'),
|
|
]
|
|
for name, result in tests:
|
|
uom, = Uom.search([
|
|
('name', '=', name),
|
|
], limit=1)
|
|
self.assertEqual(result, uom.accurate_field)
|
|
|
|
@with_transaction()
|
|
def test_uom_compute_qty(self):
|
|
'Test uom compute_qty function'
|
|
pool = Pool()
|
|
Uom = pool.get('product.uom')
|
|
tests = [
|
|
('Kilogram', 100, 'Gram', 100000, 100000),
|
|
('Gram', 1, 'Pound', 0.0022046226218487759, 0.0),
|
|
('Ounce', 1 / 7, 'Ounce', 1 / 7, 0.14),
|
|
('Second', 5, 'Minute', 0.083333333333333343, 0.08),
|
|
('Second', 25, 'Hour', 0.0069444444444444441, 0.01),
|
|
('Millimeter', 3, 'Inch', 0.11811023622047245, 0.12),
|
|
('Millimeter', 0, 'Inch', 0, 0),
|
|
('Millimeter', None, 'Inch', None, None),
|
|
]
|
|
for from_name, qty, to_name, result, rounded_result in tests:
|
|
from_uom, = Uom.search([
|
|
('name', '=', from_name),
|
|
], limit=1)
|
|
to_uom, = Uom.search([
|
|
('name', '=', to_name),
|
|
], limit=1)
|
|
self.assertEqual(result, Uom.compute_qty(
|
|
from_uom, qty, to_uom, False))
|
|
self.assertEqual(rounded_result, Uom.compute_qty(
|
|
from_uom, qty, to_uom, True))
|
|
self.assertEqual(0.2, Uom.compute_qty(None, 0.2, None, False))
|
|
self.assertEqual(0.2, Uom.compute_qty(None, 0.2, None, True))
|
|
|
|
tests_exceptions = [
|
|
('Millimeter', 3, 'Pound', ValueError),
|
|
('Kilogram', 'not a number', 'Pound', TypeError),
|
|
]
|
|
for from_name, qty, to_name, exception in tests_exceptions:
|
|
from_uom, = Uom.search([
|
|
('name', '=', from_name),
|
|
], limit=1)
|
|
to_uom, = Uom.search([
|
|
('name', '=', to_name),
|
|
], limit=1)
|
|
self.assertRaises(exception, Uom.compute_qty,
|
|
from_uom, qty, to_uom, False)
|
|
self.assertRaises(exception, Uom.compute_qty,
|
|
from_uom, qty, to_uom, True)
|
|
self.assertRaises(ValueError, Uom.compute_qty,
|
|
None, qty, to_uom, True)
|
|
self.assertRaises(ValueError, Uom.compute_qty,
|
|
from_uom, qty, None, True)
|
|
|
|
@with_transaction()
|
|
def test_uom_compute_qty_category(self):
|
|
"Test uom compute_qty with different category"
|
|
pool = Pool()
|
|
Uom = pool.get('product.uom')
|
|
|
|
g, = Uom.search([
|
|
('name', '=', "Gram"),
|
|
], limit=1)
|
|
m3, = Uom.search([
|
|
('name', '=', "Cubic meter"),
|
|
], limit=1)
|
|
|
|
for quantity, result, keys in [
|
|
(10000, 0.02, dict(factor=2)),
|
|
(20000, 0.01, dict(rate=2)),
|
|
(30000, 0.01, dict(rate=3, factor=0.333333, round=False)),
|
|
]:
|
|
msg = 'quantity: %r, keys: %r' % (quantity, keys)
|
|
self.assertEqual(
|
|
Uom.compute_qty(g, quantity, m3, **keys), result,
|
|
msg=msg)
|
|
|
|
@with_transaction()
|
|
def test_uom_compute_price(self):
|
|
'Test uom compute_price function'
|
|
pool = Pool()
|
|
Uom = pool.get('product.uom')
|
|
tests = [
|
|
('Kilogram', Decimal('100'), 'Gram', Decimal('0.1')),
|
|
('Gram', Decimal('1'), 'Pound', Decimal('453.59237')),
|
|
('Ounce', Decimal(1 / 7), 'Ounce', Decimal(1 / 7)),
|
|
('Second', Decimal('5'), 'Minute', Decimal('300')),
|
|
('Second', Decimal('25'), 'Hour', Decimal('90000')),
|
|
('Millimeter', Decimal('3'), 'Inch', Decimal('76.2')),
|
|
('Millimeter', Decimal('0'), 'Inch', Decimal('0')),
|
|
('Millimeter', None, 'Inch', None),
|
|
]
|
|
for from_name, price, to_name, result in tests:
|
|
from_uom, = Uom.search([
|
|
('name', '=', from_name),
|
|
], limit=1)
|
|
to_uom, = Uom.search([
|
|
('name', '=', to_name),
|
|
], limit=1)
|
|
self.assertEqual(result, Uom.compute_price(
|
|
from_uom, price, to_uom))
|
|
self.assertEqual(Decimal('0.2'), Uom.compute_price(
|
|
None, Decimal('0.2'), None))
|
|
|
|
tests_exceptions = [
|
|
('Millimeter', Decimal('3'), 'Pound', ValueError),
|
|
('Kilogram', 'not a number', 'Pound', TypeError),
|
|
]
|
|
for from_name, price, to_name, exception in tests_exceptions:
|
|
from_uom, = Uom.search([
|
|
('name', '=', from_name),
|
|
], limit=1)
|
|
to_uom, = Uom.search([
|
|
('name', '=', to_name),
|
|
], limit=1)
|
|
self.assertRaises(exception, Uom.compute_price,
|
|
from_uom, price, to_uom)
|
|
self.assertRaises(ValueError, Uom.compute_price,
|
|
None, price, to_uom)
|
|
self.assertRaises(ValueError, Uom.compute_price,
|
|
from_uom, price, None)
|
|
|
|
@with_transaction()
|
|
def test_uom_compute_price_category(self):
|
|
"Test uom compute_price with different category"
|
|
pool = Pool()
|
|
Uom = pool.get('product.uom')
|
|
|
|
g, = Uom.search([
|
|
('name', '=', "Gram"),
|
|
], limit=1)
|
|
m3, = Uom.search([
|
|
('name', '=', "Cubic meter"),
|
|
], limit=1)
|
|
|
|
for price, result, keys in [
|
|
(Decimal('0.001'), Decimal('500'), dict(factor=2)),
|
|
(Decimal('0.002'), Decimal('4000'), dict(rate=2)),
|
|
(Decimal('0.003'), Decimal('9000'), dict(
|
|
rate=3, factor=0.333333)),
|
|
]:
|
|
msg = 'price: %r, keys: %r' % (price, keys)
|
|
self.assertEqual(
|
|
Uom.compute_price(g, price, m3, **keys), result,
|
|
msg=msg)
|
|
|
|
@with_transaction()
|
|
def test_uom_modify_factor_rate(self):
|
|
"Test can not modify factor or rate of uom"
|
|
pool = Pool()
|
|
Uom = pool.get('product.uom')
|
|
g, = Uom.search([('name', '=', "Gram")])
|
|
|
|
g.factor = 1
|
|
g.rate = 1
|
|
|
|
with self.assertRaises(UOMAccessError):
|
|
g.save()
|
|
|
|
@with_transaction()
|
|
def test_uom_modify_category(self):
|
|
"Test can not modify category of uom"
|
|
pool = Pool()
|
|
Uom = pool.get('product.uom')
|
|
Category = pool.get('product.uom.category')
|
|
g, = Uom.search([('name', '=', "Gram")])
|
|
units, = Category.search([('name', '=', "Units")])
|
|
|
|
g.category = units
|
|
|
|
with self.assertRaises(UOMAccessError):
|
|
g.save()
|
|
|
|
@with_transaction()
|
|
def test_uom_increase_digits(self):
|
|
"Test can increase digits of uom"
|
|
pool = Pool()
|
|
Uom = pool.get('product.uom')
|
|
g, = Uom.search([('name', '=', "Gram")])
|
|
|
|
g.digits += 1
|
|
|
|
g.save()
|
|
|
|
@with_transaction()
|
|
def test_uom_decrease_digits(self):
|
|
"Test can not decrease digits of uom"
|
|
pool = Pool()
|
|
Uom = pool.get('product.uom')
|
|
g, = Uom.search([('name', '=', "Gram")])
|
|
|
|
g.digits -= 1
|
|
g.rounding = 1
|
|
|
|
with self.assertRaises(UOMAccessError):
|
|
g.save()
|
|
|
|
@with_transaction()
|
|
def test_product_search_domain(self):
|
|
'Test product.product search_domain function'
|
|
pool = Pool()
|
|
Uom = pool.get('product.uom')
|
|
Template = pool.get('product.template')
|
|
Product = pool.get('product.product')
|
|
|
|
kilogram, = Uom.search([
|
|
('name', '=', 'Kilogram'),
|
|
], limit=1)
|
|
millimeter, = Uom.search([
|
|
('name', '=', 'Millimeter'),
|
|
])
|
|
pt1, pt2 = Template.create([{
|
|
'name': 'P1',
|
|
'type': 'goods',
|
|
'default_uom': kilogram.id,
|
|
'products': [('create', [{
|
|
'code': '1',
|
|
}])]
|
|
}, {
|
|
'name': 'P2',
|
|
'type': 'goods',
|
|
'default_uom': millimeter.id,
|
|
'products': [('create', [{
|
|
'code': '2',
|
|
}])]
|
|
}])
|
|
p, = Product.search([
|
|
('default_uom.name', '=', 'Kilogram'),
|
|
])
|
|
self.assertEqual(p, pt1.products[0])
|
|
p, = Product.search([
|
|
('default_uom.name', '=', 'Millimeter'),
|
|
])
|
|
self.assertEqual(p, pt2.products[0])
|
|
|
|
@with_transaction()
|
|
def test_search_domain_conversion(self):
|
|
'Test the search domain conversion'
|
|
pool = Pool()
|
|
Category = pool.get('product.category')
|
|
Template = pool.get('product.template')
|
|
Product = pool.get('product.product')
|
|
Uom = pool.get('product.uom')
|
|
|
|
category1, = Category.create([{'name': 'Category1'}])
|
|
category2, = Category.create([{'name': 'Category2'}])
|
|
uom, = Uom.search([], limit=1)
|
|
values1 = {
|
|
'name': 'Some product-1',
|
|
'categories': [('add', [category1.id])],
|
|
'type': 'goods',
|
|
'default_uom': uom.id,
|
|
'products': [('create', [{}])],
|
|
}
|
|
values2 = {
|
|
'name': 'Some product-2',
|
|
'categories': [('add', [category2.id])],
|
|
'type': 'goods',
|
|
'default_uom': uom.id,
|
|
'products': [('create', [{}])],
|
|
}
|
|
|
|
# This is a false positive as there is 1 product with the
|
|
# template 1 and the same product with category 1. If you do not
|
|
# create two categories (or any other relation on the template
|
|
# model) you wont be able to check as in most cases the
|
|
# id of the template and the related model would be same (1).
|
|
# So two products have been created with same category. So that
|
|
# domain ('template.categories', '=', 1) will return 2 records which
|
|
# it supposed to be.
|
|
template1, template2, template3, template4 = Template.create(
|
|
[values1, values1.copy(), values2, values2.copy()]
|
|
)
|
|
self.assertEqual(Product.search([], count=True), 4)
|
|
self.assertEqual(
|
|
Product.search([
|
|
('categories', '=', category1.id),
|
|
], count=True), 2)
|
|
|
|
self.assertEqual(
|
|
Product.search([
|
|
('template.categories', '=', category1.id),
|
|
], count=True), 2)
|
|
|
|
self.assertEqual(
|
|
Product.search([
|
|
('categories', '=', category2.id),
|
|
], count=True), 2)
|
|
self.assertEqual(
|
|
Product.search([
|
|
('template.categories', '=', category2.id),
|
|
], count=True), 2)
|
|
|
|
@with_transaction()
|
|
def test_uom_rounding(self):
|
|
'Test uom rounding functions'
|
|
pool = Pool()
|
|
Uom = pool.get('product.uom')
|
|
tests = [
|
|
(2.53, .1, 2.5, 2.6, 2.5),
|
|
(3.8, .1, 3.8, 3.8, 3.8),
|
|
(3.7, .1, 3.7, 3.7, 3.7),
|
|
(1.3, .5, 1.5, 1.5, 1.0),
|
|
(1.1, .3, 1.2, 1.2, 0.9),
|
|
(17, 10, 20, 20, 10),
|
|
(7, 10, 10, 10, 0),
|
|
(4, 10, 0, 10, 0),
|
|
(17, 15, 15, 30, 15),
|
|
(2.5, 1.4, 2.8, 2.8, 1.4),
|
|
]
|
|
for number, precision, round, ceil, floor in tests:
|
|
uom = Uom(rounding=precision)
|
|
self.assertEqual(uom.round(number), round)
|
|
self.assertEqual(uom.ceil(number), ceil)
|
|
self.assertEqual(uom.floor(number), floor)
|
|
|
|
@with_transaction()
|
|
def test_product_order(self):
|
|
'Test product field order'
|
|
pool = Pool()
|
|
Template = pool.get('product.template')
|
|
Product = pool.get('product.product')
|
|
Uom = pool.get('product.uom')
|
|
|
|
uom, = Uom.search([], limit=1)
|
|
values1 = {
|
|
'name': 'Product A',
|
|
'type': 'assets',
|
|
'default_uom': uom.id,
|
|
'products': [('create', [{'suffix_code': 'AA'}])],
|
|
}
|
|
values2 = {
|
|
'name': 'Product B',
|
|
'type': 'goods',
|
|
'default_uom': uom.id,
|
|
'products': [('create', [{'suffix_code': 'BB'}])],
|
|
}
|
|
|
|
template1, template2 = Template.create([values1, values2])
|
|
product1, product2 = Product.search([])
|
|
|
|
# Non-inherited field.
|
|
self.assertEqual(
|
|
Product.search([], order=[('code', 'ASC')]), [product1, product2])
|
|
self.assertEqual(
|
|
Product.search([], order=[('code', 'DESC')]), [product2, product1])
|
|
self.assertEqual(Product.search(
|
|
[('name', 'like', '%')], order=[('code', 'ASC')]),
|
|
[product1, product2])
|
|
self.assertEqual(Product.search(
|
|
[('name', 'like', '%')], order=[('code', 'DESC')]),
|
|
[product2, product1])
|
|
|
|
# Inherited field with custom order.
|
|
self.assertEqual(
|
|
Product.search([], order=[('name', 'ASC')]), [product1, product2])
|
|
self.assertEqual(
|
|
Product.search([], order=[('name', 'DESC')]), [product2, product1])
|
|
self.assertEqual(Product.search(
|
|
[('name', 'like', '%')], order=[('name', 'ASC')]),
|
|
[product1, product2])
|
|
self.assertEqual(Product.search(
|
|
[('name', 'like', '%')], order=[('name', 'DESC')]),
|
|
[product2, product1])
|
|
|
|
# Inherited field without custom order.
|
|
self.assertEqual(
|
|
Product.search([], order=[('type', 'ASC')]), [product1, product2])
|
|
self.assertEqual(
|
|
Product.search([], order=[('type', 'DESC')]), [product2, product1])
|
|
self.assertEqual(Product.search(
|
|
[('name', 'like', '%')], order=[('type', 'ASC')]),
|
|
[product1, product2])
|
|
self.assertEqual(Product.search(
|
|
[('name', 'like', '%')], order=[('type', 'DESC')]),
|
|
[product2, product1])
|
|
|
|
def test_round_price(self):
|
|
for value, result in [
|
|
(Decimal('1'), Decimal('1.0000')),
|
|
(Decimal('1.12345'), Decimal('1.1234')),
|
|
(1, Decimal('1')),
|
|
]:
|
|
with self.subTest(value=value):
|
|
self.assertEqual(round_price(value), result)
|
|
|
|
@with_transaction()
|
|
def test_product_identifier_get_single_type(self):
|
|
"Test identifier get with a single type"
|
|
pool = Pool()
|
|
Identifier = pool.get('product.identifier')
|
|
Product = pool.get('product.product')
|
|
Template = pool.get('product.template')
|
|
Uom = pool.get('product.uom')
|
|
|
|
uom, = Uom.search([], limit=1)
|
|
template = Template(name="Product", default_uom=uom)
|
|
template.save()
|
|
product = Product(template=template)
|
|
product.identifiers = [
|
|
Identifier(code='FOO'),
|
|
Identifier(type='ean', code='978-0-471-11709-4'),
|
|
]
|
|
product.save()
|
|
|
|
self.assertEqual(
|
|
product.identifier_get('ean').code,
|
|
'978-0-471-11709-4')
|
|
|
|
@with_transaction()
|
|
def test_product_identifier_get_many_types(self):
|
|
"Test identifier get with many types"
|
|
pool = Pool()
|
|
Identifier = pool.get('product.identifier')
|
|
Product = pool.get('product.product')
|
|
Template = pool.get('product.template')
|
|
Uom = pool.get('product.uom')
|
|
|
|
uom, = Uom.search([], limit=1)
|
|
template = Template(name="Product", default_uom=uom)
|
|
template.save()
|
|
product = Product(template=template)
|
|
product.identifiers = [
|
|
Identifier(code='FOO'),
|
|
Identifier(type='isbn', code='0-6332-4980-7'),
|
|
Identifier(type='ean', code='978-0-471-11709-4'),
|
|
]
|
|
product.save()
|
|
|
|
self.assertEqual(
|
|
product.identifier_get({'ean', 'isbn'}).code,
|
|
'0-6332-4980-7')
|
|
|
|
@with_transaction()
|
|
def test_product_identifier_get_any(self):
|
|
"Test identifier get for any type"
|
|
pool = Pool()
|
|
Identifier = pool.get('product.identifier')
|
|
Product = pool.get('product.product')
|
|
Template = pool.get('product.template')
|
|
Uom = pool.get('product.uom')
|
|
|
|
uom, = Uom.search([], limit=1)
|
|
template = Template(name="Product", default_uom=uom)
|
|
template.save()
|
|
product = Product(template=template)
|
|
product.identifiers = [
|
|
Identifier(code='FOO'),
|
|
]
|
|
product.save()
|
|
|
|
self.assertEqual(product.identifier_get(None).code, 'FOO')
|
|
|
|
@with_transaction()
|
|
def test_product_identifier_get_unknown_type(self):
|
|
"Test identifier get with a unknown type"
|
|
pool = Pool()
|
|
Identifier = pool.get('product.identifier')
|
|
Product = pool.get('product.product')
|
|
Template = pool.get('product.template')
|
|
Uom = pool.get('product.uom')
|
|
|
|
uom, = Uom.search([], limit=1)
|
|
template = Template(name="Product", default_uom=uom)
|
|
template.save()
|
|
product = Product(template=template)
|
|
product.identifiers = [
|
|
Identifier(code='FOO'),
|
|
]
|
|
product.save()
|
|
|
|
self.assertEqual(product.identifier_get('ean'), None)
|
|
|
|
@unittest.skipUnless(barcode, 'required barcode')
|
|
@with_transaction()
|
|
def test_product_identifier_barcode(self):
|
|
"Test identifier barcode"
|
|
pool = Pool()
|
|
Identifier = pool.get('product.identifier')
|
|
Product = pool.get('product.product')
|
|
Template = pool.get('product.template')
|
|
Uom = pool.get('product.uom')
|
|
|
|
uom, = Uom.search([], limit=1)
|
|
template = Template(name="Product", default_uom=uom)
|
|
template.save()
|
|
product = Product(template=template)
|
|
product.identifiers = [
|
|
Identifier(type='ean', code='978-0-471-11709-4'),
|
|
]
|
|
product.save()
|
|
identifier, = product.identifiers
|
|
|
|
image = identifier.barcode()
|
|
self.assertIsInstance(image, io.BytesIO)
|
|
self.assertIsNotNone(image.getvalue())
|
|
|
|
|
|
del ModuleTestCase
|