[IMP] uom: unique reference UoM per category
Users can configure multiple references in a same category. This might cause problems during conversion and does not make sense. Since this is a "late" constraint, we define it in python to avoid conflict with existing data. We allow user to have multiple unactive reference, but force to have exactly one active. So when a new UoM category is created, user have to encode the reference unit first, and then the bigger/smaller ones. This commit also update stock tests, since they were redefining new reference UoM in the same category, and finally, this commit provides some tests for this uniqueness and custom UoM behavior. Task #1820076
This commit is contained in:
@@ -37,7 +37,8 @@ class TestSaleMrpFlow(common.TransactionCase):
|
||||
p.route_ids.add(r)
|
||||
return p.save()
|
||||
|
||||
self.uom_kg = self.UoM.create({
|
||||
self.uom_kg = self.env['uom.uom'].search([('category_id', '=', self.categ_kgm.id), ('uom_type', '=', 'reference')], limit=1)
|
||||
self.uom_kg.write({
|
||||
'name': 'Test-KG',
|
||||
'category_id': self.categ_kgm.id,
|
||||
'factor_inv': 1,
|
||||
@@ -50,7 +51,8 @@ class TestSaleMrpFlow(common.TransactionCase):
|
||||
'uom_type': 'smaller',
|
||||
'factor': 1000.0,
|
||||
'rounding': 0.001})
|
||||
self.uom_unit = self.UoM.create({
|
||||
self.uom_unit = self.env['uom.uom'].search([('category_id', '=', self.categ_unit.id), ('uom_type', '=', 'reference')], limit=1)
|
||||
self.uom_unit.write({
|
||||
'name': 'Test-Unit',
|
||||
'category_id': self.categ_unit.id,
|
||||
'factor': 1,
|
||||
|
||||
@@ -45,7 +45,8 @@ class TestStockCommon(common.TransactionCase):
|
||||
self.productE = self.ProductObj.create({'name': 'Product E', 'type': 'product'})
|
||||
|
||||
# Configure unit of measure.
|
||||
self.uom_kg = self.UomObj.create({
|
||||
self.uom_kg = self.env['uom.uom'].search([('category_id', '=', self.categ_kgm), ('uom_type', '=', 'reference')], limit=1)
|
||||
self.uom_kg.write({
|
||||
'name': 'Test-KG',
|
||||
'category_id': self.categ_kgm,
|
||||
'factor_inv': 1,
|
||||
@@ -71,7 +72,8 @@ class TestStockCommon(common.TransactionCase):
|
||||
'factor': 100000.0,
|
||||
'rounding': 0.001})
|
||||
# Check Unit
|
||||
self.uom_unit = self.UomObj.create({
|
||||
self.uom_unit = self.env['uom.uom'].search([('category_id', '=', self.categ_unit), ('uom_type', '=', 'reference')], limit=1)
|
||||
self.uom_unit.write({
|
||||
'name': 'Test-Unit',
|
||||
'category_id': self.categ_unit,
|
||||
'factor': 1,
|
||||
|
||||
@@ -15,7 +15,8 @@ class TestStockLandedCostsRounding(TestStockLandedCostsCommon):
|
||||
self._load('stock_account', 'test', 'stock_valuation_account.xml')
|
||||
|
||||
# Define undivisible units
|
||||
product_uom_unit_round_1 = self.env['uom.uom'].create({
|
||||
product_uom_unit_round_1 = self.env.ref('uom.product_uom_unit')
|
||||
product_uom_unit_round_1.write({
|
||||
'category_id': self.ref('uom.product_uom_categ_unit'),
|
||||
'name': 'Undivisible Unit(s)',
|
||||
'factor': 1.0,
|
||||
|
||||
@@ -51,17 +51,17 @@
|
||||
<field name="factor" eval="1000"/>
|
||||
<field name="uom_type">smaller</field>
|
||||
</record>
|
||||
<record id="product_uom_day" model="uom.uom">
|
||||
<field name="name">Day(s)</field>
|
||||
<field eval="uom_categ_wtime" name="category_id"/>
|
||||
<field name="factor" eval="1.0"/>
|
||||
</record>
|
||||
<record id="product_uom_hour" model="uom.uom">
|
||||
<field name="name">Hour(s)</field>
|
||||
<field eval="uom_categ_wtime" name="category_id"/>
|
||||
<field name="factor" eval="8.0"/>
|
||||
<field name="uom_type">smaller</field>
|
||||
</record>
|
||||
<record id="product_uom_day" model="uom.uom">
|
||||
<field name="name">Day(s)</field>
|
||||
<field eval="uom_categ_wtime" name="category_id"/>
|
||||
<field name="factor" eval="1.0"/>
|
||||
</record>
|
||||
<record id="product_uom_ton" model="uom.uom">
|
||||
<field name="category_id" ref="product_uom_categ_kgm"/>
|
||||
<!-- 'tonne' is the most common spelling in english-speaking countries,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, tools, models, _
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
|
||||
|
||||
class UoMCategory(models.Model):
|
||||
@@ -66,6 +66,27 @@ class UoM(models.Model):
|
||||
if self.uom_type == 'reference':
|
||||
self.factor = 1
|
||||
|
||||
@api.constrains('category_id', 'uom_type', 'active')
|
||||
def _check_category_reference_uniqueness(self):
|
||||
""" Force the existence of only one UoM reference per category
|
||||
NOTE: this is a constraint on the all table. This might not be a good practice, but this is
|
||||
not possible to do it in SQL directly.
|
||||
"""
|
||||
category_ids = self.mapped('category_id').ids
|
||||
self._cr.execute("""
|
||||
SELECT C.id AS category_id, count(U.id) AS uom_count
|
||||
FROM uom_category C
|
||||
LEFT JOIN uom_uom U ON C.id = U.category_id AND uom_type = 'reference'
|
||||
WHERE C.id IN %s
|
||||
AND U.active = 't'
|
||||
GROUP BY C.id
|
||||
""", (tuple(category_ids),))
|
||||
for uom_data in self._cr.dictfetchall():
|
||||
if uom_data['uom_count'] == 0:
|
||||
raise ValidationError(_("UoM category %s should have a reference unit of measure. If you just created a new category, please record the 'reference' unit first.") % (self.env['uom.category'].browse(uom_data['category_id']).name,))
|
||||
if uom_data['uom_count'] > 1:
|
||||
raise ValidationError(_("UoM category %s should only have one reference unit of measure.") % (self.env['uom.category'].browse(uom_data['category_id']).name,))
|
||||
|
||||
@api.model
|
||||
def create(self, values):
|
||||
if 'factor_inv' in values:
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.tests.common import TransactionCase
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class TestUom(TransactionCase):
|
||||
@@ -47,3 +48,50 @@ class TestUom(TransactionCase):
|
||||
|
||||
qty = self.uom_unit._compute_quantity(2, product_uom)
|
||||
self.assertEquals(qty, 1, "Converted quantity should be rounded up.")
|
||||
|
||||
def test_30_reference_uniqueness(self):
|
||||
""" Check the uniqueness of the reference UoM in a category """
|
||||
time_category = self.env['uom.category'].search([('measure_type', '=', 'time')], limit=1)
|
||||
|
||||
with self.assertRaises(ValidationError):
|
||||
self.env['uom.uom'].create({
|
||||
'name': 'Second Time Reference',
|
||||
'factor_inv': 1,
|
||||
'uom_type': 'reference',
|
||||
'rounding': 1.0,
|
||||
'category_id': time_category.id
|
||||
})
|
||||
|
||||
def test_40_custom_uom(self):
|
||||
""" A custom UoM is an UoM in a category without measurement type. It should behave like a normal UoM """
|
||||
category = self.env['uom.category'].create({
|
||||
'name': 'Custom UoM category',
|
||||
})
|
||||
|
||||
# create the reference
|
||||
self.env['uom.uom'].create({
|
||||
'name': 'Reference UoM of my category',
|
||||
'factor_inv': 1,
|
||||
'uom_type': 'reference',
|
||||
'rounding': 1.0,
|
||||
'category_id': category.id
|
||||
})
|
||||
|
||||
# we can create another UoM now
|
||||
self.env['uom.uom'].create({
|
||||
'name': 'Bigger UoM of my category',
|
||||
'factor_inv': 42,
|
||||
'uom_type': 'bigger',
|
||||
'rounding': 0.5,
|
||||
'category_id': category.id
|
||||
})
|
||||
|
||||
# we can not create a second reference in custom category
|
||||
with self.assertRaises(ValidationError):
|
||||
self.env['uom.uom'].create({
|
||||
'name': 'Second Time Reference',
|
||||
'factor_inv': 1,
|
||||
'uom_type': 'reference',
|
||||
'rounding': 1.0,
|
||||
'category_id': category.id
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user