Files
tryton/uom.py
T

268 lines
8.9 KiB
Python
Raw Normal View History

2014-12-02 12:55:30 +01:00
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
2015-06-17 08:28:58 +02:00
from __future__ import division
2010-08-15 00:23:46 +01:00
from decimal import Decimal
2013-09-06 22:34:24 +02:00
from sql import Table
from trytond.model import ModelView, ModelSQL, fields, Check
2011-08-21 11:20:46 +02:00
from trytond.pyson import Eval
2010-08-15 00:23:46 +01:00
from trytond.transaction import Transaction
2008-03-03 18:36:08 +01:00
2012-09-24 22:18:00 +02:00
__all__ = ['UomCategory', 'Uom']
2008-03-03 18:36:08 +01:00
STATES = {
2011-08-21 11:20:46 +02:00
'readonly': ~Eval('active', True),
}
2011-08-04 16:57:33 +02:00
DEPENDS = ['active']
2008-03-03 18:36:08 +01:00
2012-04-03 20:07:18 +02:00
2009-02-28 17:00:24 +01:00
class UomCategory(ModelSQL, ModelView):
2008-03-03 18:36:08 +01:00
'Product uom category'
2012-09-24 22:18:00 +02:00
__name__ = 'product.uom.category'
name = fields.Char('Name', required=True, translate=True)
2008-04-08 17:25:39 +02:00
uoms = fields.One2Many('product.uom', 'category', 'Unit of Measures')
2008-03-03 18:36:08 +01:00
2012-09-24 22:18:00 +02:00
@classmethod
def __setup__(cls):
super(UomCategory, cls).__setup__()
cls._order.insert(0, ('name', 'ASC'))
2008-03-03 18:36:08 +01:00
2009-02-28 17:00:24 +01:00
class Uom(ModelSQL, ModelView):
2008-03-03 18:36:08 +01:00
'Unit of measure'
2012-09-24 22:18:00 +02:00
__name__ = 'product.uom'
2008-07-16 18:08:48 +02:00
name = fields.Char('Name', size=None, required=True, states=STATES,
2011-08-04 16:57:33 +02:00
translate=True, depends=DEPENDS)
2008-07-16 18:08:48 +02:00
symbol = fields.Char('Symbol', size=10, required=True, states=STATES,
2011-08-04 16:57:33 +02:00
translate=True, depends=DEPENDS)
2014-05-27 22:31:27 +02:00
category = fields.Many2One('product.uom.category', 'Category',
2011-08-04 16:57:33 +02:00
required=True, ondelete='RESTRICT', states=STATES, depends=DEPENDS)
rate = fields.Float('Rate', digits=(12, 12), required=True,
2014-01-26 14:29:18 +01:00
states=STATES, depends=DEPENDS,
2013-02-24 14:18:51 +01:00
help=('The coefficient for the formula:\n'
'1 (base unit) = coef (this unit)'))
factor = fields.Float('Factor', digits=(12, 12), states=STATES,
2014-01-26 14:29:18 +01:00
required=True, depends=DEPENDS,
2013-02-24 14:18:51 +01:00
help=('The coefficient for the formula:\n'
'coef (base unit) = 1 (this unit)'))
rounding = fields.Float('Rounding Precision', digits=(12, 12),
required=True, states=STATES, depends=DEPENDS,
domain=[
('rounding', '>', 0),
])
digits = fields.Integer('Display Digits', required=True)
2008-03-03 18:36:08 +01:00
active = fields.Boolean('Active')
2012-09-24 22:18:00 +02:00
@classmethod
def __register__(cls, module_name):
cursor = Transaction().connection.cursor()
2013-09-06 22:34:24 +02:00
model_data = Table('ir_model_data')
# Migration from 1.6: corrected misspelling of ounce (was once)
2013-09-06 22:34:24 +02:00
cursor.execute(*model_data.update(
columns=[model_data.fs_id],
values=['uom_ounce'],
where=(model_data.fs_id == 'uom_once')
& (model_data.module == 'product')))
2012-09-24 22:18:00 +02:00
super(Uom, cls).__register__(module_name)
2012-09-24 22:18:00 +02:00
@classmethod
def __setup__(cls):
super(Uom, cls).__setup__()
t = cls.__table__()
2012-09-24 22:18:00 +02:00
cls._sql_constraints += [
('non_zero_rate_factor', Check(t, (t.rate != 0) | (t.factor != 0)),
2009-04-06 13:03:51 +02:00
'Rate and factor can not be both equal to zero.')
2012-09-24 22:18:00 +02:00
]
cls._order.insert(0, ('name', 'ASC'))
cls._error_messages.update({
2013-02-24 14:18:51 +01:00
'change_uom_rate_title': ('You cannot change Rate, Factor or '
'Category on a Unit of Measure.'),
2013-02-24 14:18:51 +01:00
'change_uom_rate': ('If the UOM is still not used, you can '
'delete it otherwise you can deactivate it '
'and create a new one.'),
2013-10-05 12:17:07 +02:00
'invalid_factor_and_rate': (
'Invalid Factor and Rate values in UOM "%s".'),
2012-09-24 22:18:00 +02:00
})
2009-04-06 13:03:51 +02:00
2012-09-24 22:18:00 +02:00
@classmethod
def check_xml_record(cls, records, values):
2008-08-26 18:18:38 +02:00
return True
2012-09-24 22:18:00 +02:00
@staticmethod
def default_rate():
2008-03-03 18:36:08 +01:00
return 1.0
2012-09-24 22:18:00 +02:00
@staticmethod
def default_factor():
2008-03-03 18:36:08 +01:00
return 1.0
2012-09-24 22:18:00 +02:00
@staticmethod
def default_active():
2012-03-24 09:28:27 +01:00
return True
2008-03-03 18:36:08 +01:00
2012-09-24 22:18:00 +02:00
@staticmethod
def default_rounding():
2008-03-03 18:36:08 +01:00
return 0.01
2012-09-24 22:18:00 +02:00
@staticmethod
def default_digits():
2008-08-26 19:06:12 +02:00
return 2
2014-01-26 14:29:18 +01:00
@fields.depends('factor')
2012-09-24 22:18:00 +02:00
def on_change_factor(self):
if (self.factor or 0.0) == 0.0:
2014-10-29 20:39:55 +01:00
self.rate = 0.0
else:
self.rate = round(1.0 / self.factor, self.__class__.rate.digits[1])
2008-03-03 18:36:08 +01:00
2014-01-26 14:29:18 +01:00
@fields.depends('rate')
2012-09-24 22:18:00 +02:00
def on_change_rate(self):
if (self.rate or 0.0) == 0.0:
2014-10-29 20:39:55 +01:00
self.factor = 0.0
else:
2014-12-02 12:55:30 +01:00
self.factor = round(
1.0 / self.rate, self.__class__.factor.digits[1])
2008-03-13 01:22:57 +01:00
2012-09-24 22:18:00 +02:00
@classmethod
def search_rec_name(cls, name, clause):
2015-04-05 23:36:13 +02:00
if clause[1].startswith('!') or clause[1].startswith('not '):
bool_op = 'AND'
else:
bool_op = 'OR'
return [bool_op,
2014-02-24 20:21:33 +01:00
(cls._rec_name,) + tuple(clause[1:]),
('symbol',) + tuple(clause[1:]),
]
2016-01-11 10:28:00 -03:00
def round(self, number):
precision = self.rounding
2015-06-17 08:28:58 +02:00
i, d = divmod(precision, 1)
base = round(number / precision)
# Instead of multiply by the decimal part, we must divide by the
# integer to avoid precision lost due to float
return (base * i) + ((base / (1 / d)) if d != 0 else 0)
@classmethod
def validate(cls, uoms):
super(Uom, cls).validate(uoms)
for uom in uoms:
uom.check_factor_and_rate()
2012-09-24 22:18:00 +02:00
def check_factor_and_rate(self):
"Check coherence between factor and rate"
2012-09-24 22:18:00 +02:00
if self.rate == self.factor == 0.0:
2013-10-15 15:09:04 +02:00
return
2012-09-24 22:18:00 +02:00
if (self.rate != round(
1.0 / self.factor, self.__class__.rate.digits[1])
and self.factor != round(
1.0 / self.rate, self.__class__.factor.digits[1])):
self.raise_user_error('invalid_factor_and_rate', (
self.rec_name,))
2012-09-24 22:18:00 +02:00
@classmethod
2013-12-19 14:45:07 +01:00
def write(cls, *args):
2010-08-15 00:23:46 +01:00
if Transaction().user == 0:
2013-12-19 14:45:07 +01:00
super(Uom, cls).write(*args)
2012-09-24 22:18:00 +02:00
return
2013-12-19 14:45:07 +01:00
actions = iter(args)
all_uoms = []
for uoms, values in zip(actions, actions):
if 'rate' not in values and 'factor' not in values \
and 'category' not in values:
continue
all_uoms += uoms
2013-02-24 14:18:51 +01:00
old_uom = dict((uom.id, (uom.factor, uom.rate, uom.category.id))
2013-12-19 14:45:07 +01:00
for uom in all_uoms)
2013-12-19 14:45:07 +01:00
super(Uom, cls).write(*args)
2013-12-19 14:45:07 +01:00
for uom in all_uoms:
if uom.factor != old_uom[uom.id][0] \
or uom.rate != old_uom[uom.id][1] \
or uom.category.id != old_uom[uom.id][2]:
2012-09-24 22:18:00 +02:00
cls.raise_user_error('change_uom_rate_title',
error_description='change_uom_rate')
2012-09-24 22:18:00 +02:00
@property
def accurate_field(self):
"""
Select the more accurate field.
It chooses the field that has the least decimal.
"""
lengths = {}
for field in ('rate', 'factor'):
2012-09-24 22:18:00 +02:00
format = '%%.%df' % getattr(self.__class__, field).digits[1]
lengths[field] = len((format % getattr(self,
field)).split('.')[1].rstrip('0'))
if lengths['rate'] < lengths['factor']:
return 'rate'
elif lengths['factor'] < lengths['rate']:
return 'factor'
2012-09-24 22:18:00 +02:00
elif self.factor >= 1.0:
return 'factor'
else:
return 'rate'
2012-09-24 22:18:00 +02:00
@classmethod
def compute_qty(cls, from_uom, qty, to_uom, round=True):
"""
Convert quantity for given uom's.
"""
if not qty or (from_uom is None and to_uom is None):
return qty
if from_uom is None:
raise ValueError("missing from_uom")
if to_uom is None:
raise ValueError("missing to_uom")
if from_uom.category.id != to_uom.category.id:
raise ValueError("cannot convert between %s and %s"
% (from_uom.category.name, to_uom.category.name))
2012-09-24 22:18:00 +02:00
if from_uom.accurate_field == 'factor':
2008-03-13 01:22:57 +01:00
amount = qty * from_uom.factor
else:
2008-03-13 01:22:57 +01:00
amount = qty / from_uom.rate
if to_uom.accurate_field == 'factor':
amount = amount / to_uom.factor
else:
amount = amount * to_uom.rate
if round:
2016-01-11 10:28:00 -03:00
amount = to_uom.round(amount)
return amount
2012-09-24 22:18:00 +02:00
@classmethod
def compute_price(cls, from_uom, price, to_uom):
"""
Convert price for given uom's.
"""
if not price or (from_uom is None and to_uom is None):
return price
if from_uom is None:
raise ValueError("missing from_uom")
if to_uom is None:
raise ValueError("missing to_uom")
if from_uom.category.id != to_uom.category.id:
raise ValueError('cannot convert between %s and %s'
% (from_uom.category.name, to_uom.category.name))
2012-09-24 22:18:00 +02:00
factor_format = '%%.%df' % cls.factor.digits[1]
rate_format = '%%.%df' % cls.rate.digits[1]
2008-05-13 13:08:29 +02:00
2012-09-24 22:18:00 +02:00
if from_uom.accurate_field == 'factor':
new_price = price / Decimal(factor_format % from_uom.factor)
else:
new_price = price * Decimal(rate_format % from_uom.rate)
2012-09-24 22:18:00 +02:00
if to_uom.accurate_field == 'factor':
new_price = new_price * Decimal(factor_format % to_uom.factor)
else:
new_price = new_price / Decimal(rate_format % to_uom.rate)
2008-03-14 14:08:26 +01:00
return new_price