Files
tryton/uom.py
T

284 lines
11 KiB
Python
Raw Normal View History

2009-02-02 13:11:13 +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.
2010-08-15 00:23:46 +01:00
from decimal import Decimal
2009-02-28 17:00:24 +01:00
from trytond.model import ModelView, ModelSQL, fields
2009-02-02 13:11:13 +01:00
from trytond.model.modelstorage import OPERATORS
2010-01-20 11:01:14 +01:00
from trytond.pyson import Not, Bool, Eval
2010-08-15 00:23:46 +01:00
from trytond.transaction import Transaction
2008-03-03 18:36:08 +01:00
STATES = {
2010-01-20 11:01:14 +01:00
'readonly': Not(Bool(Eval('active'))),
2008-03-03 18:36:08 +01:00
}
2009-02-28 17:00:24 +01:00
class UomCategory(ModelSQL, ModelView):
2008-03-03 18:36:08 +01:00
'Product uom category'
_name = 'product.uom.category'
_description = __doc__
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
2008-04-22 17:49:25 +02:00
def __init__(self):
super(UomCategory, self).__init__()
self._order.insert(0, ('name', 'ASC'))
2008-03-03 18:36:08 +01:00
UomCategory()
2009-02-28 17:00:24 +01:00
class Uom(ModelSQL, ModelView):
2008-03-03 18:36:08 +01:00
'Unit of measure'
_name = 'product.uom'
_description = __doc__
2008-07-16 18:08:48 +02:00
name = fields.Char('Name', size=None, required=True, states=STATES,
translate=True)
symbol = fields.Char('Symbol', size=10, required=True, states=STATES,
translate=True)
2008-03-03 18:36:08 +01:00
category = fields.Many2One('product.uom.category', 'UOM Category',
2009-03-25 12:55:24 +01:00
required=True, ondelete='RESTRICT', states=STATES)
rate = fields.Float('Rate', digits=(12, 12), required=True,
2008-08-26 19:06:12 +02:00
on_change=['rate'], states=STATES,
help='The coefficient for the formula:\n' \
'1 (base unit) = coef (this unit)')
factor = fields.Float('Factor', digits=(12, 12), states=STATES,
2008-08-26 19:06:12 +02:00
on_change=['factor'], required=True,
help='The coefficient for the formula:\n' \
'coef (base unit) = 1 (this unit)')
rounding = fields.Float('Rounding Precision', digits=(12, 12),
2008-08-26 19:06:12 +02:00
required=True, states=STATES)
digits = fields.Integer('Display Digits')
2008-03-03 18:36:08 +01:00
active = fields.Boolean('Active')
def init(self, module_name):
cursor = Transaction().cursor
# Migration from 1.6: corrected misspelling of ounce (was once)
cursor.execute("UPDATE ir_model_data "\
"SET fs_id = REPLACE(fs_id, 'uom_once', 'uom_ounce') "\
"WHERE fs_id = 'uom_once' AND module = 'product'")
super(Uom, self).init(module_name)
2009-04-06 13:03:51 +02:00
def __init__(self):
super(Uom, self).__init__()
self._sql_constraints += [
('non_zero_rate_factor', 'CHECK((rate != 0.0) or (factor != 0.0))',
'Rate and factor can not be both equal to zero.')
]
self._constraints += [
('check_factor_and_rate', 'invalid_factor_and_rate'),
]
2009-04-06 13:03:51 +02:00
self._order.insert(0, ('name', 'ASC'))
self._error_messages.update({
'change_uom_rate_title': 'You cannot change Rate, Factor or '
2009-04-06 13:03:51 +02:00
'Category on a Unit of Measure. ',
'change_uom_rate': 'If the UOM is still not used, you can '
2010-03-11 15:31:06 +01:00
'delete it otherwise you can deactivate it '
'and create a new one.',
'invalid_factor_and_rate': 'Invalid Factor and Rate values!',
2009-04-06 13:03:51 +02:00
})
2010-08-15 00:23:46 +01:00
def check_xml_record(self, ids, values):
2008-08-26 18:18:38 +02:00
return True
2010-08-15 00:23:46 +01:00
def default_rate(self):
2008-03-03 18:36:08 +01:00
return 1.0
2010-08-15 00:23:46 +01:00
def default_factor(self):
2008-03-03 18:36:08 +01:00
return 1.0
2010-08-15 00:23:46 +01:00
def default_active(self):
2008-03-03 18:36:08 +01:00
return 1
2010-08-15 00:23:46 +01:00
def default_rounding(self):
2008-03-03 18:36:08 +01:00
return 0.01
2010-08-15 00:23:46 +01:00
def default_digits(self):
2008-08-26 19:06:12 +02:00
return 2
2010-08-15 00:23:46 +01:00
def default_category(self):
2008-11-13 15:18:40 +01:00
category_obj = self.pool.get('product.uom.category')
product_obj = self.pool.get('product.product')
2010-08-15 00:23:46 +01:00
context = Transaction().context
2008-11-13 15:18:40 +01:00
if 'category' in context:
if isinstance(context['category'], (tuple, list)) \
and len(context['category']) > 1 \
and context['category'][1] in ('uom.category',
'product.default_uom.category'):
if context['category'][1] == 'uom.category':
if not context['category'][0]:
return False
2010-08-15 00:23:46 +01:00
uom = self.browse(context['category'][0])
return uom.category.id
2008-11-13 15:18:40 +01:00
else:
if not context['category'][0]:
return False
2010-08-15 00:23:46 +01:00
product = product_obj.browse(context['category'][0])
return product.default_uom.category.id
2008-11-13 15:18:40 +01:00
return False
2010-08-15 00:23:46 +01:00
def on_change_factor(self, value):
2008-03-03 18:36:08 +01:00
if value.get('factor', 0.0) == 0.0:
2008-03-14 14:08:26 +01:00
return {'rate': 0.0}
return {'rate': round(1.0 / value['factor'], self.rate.digits[1])}
2008-03-03 18:36:08 +01:00
2010-08-15 00:23:46 +01:00
def on_change_rate(self, value):
2008-03-03 18:36:08 +01:00
if value.get('rate', 0.0) == 0.0:
2008-03-14 14:08:26 +01:00
return {'factor': 0.0}
return {'factor': round(1.0 / value['rate'], self.factor.digits[1])}
2008-03-13 01:22:57 +01:00
2010-08-15 00:23:46 +01:00
def search_rec_name(self, name, clause):
ids = self.search(['OR',
(self._rec_name,) + clause[1:],
('symbol',) + clause[1:],
2010-08-15 00:23:46 +01:00
], order=[])
return [('id', 'in', ids)]
2008-03-13 01:22:57 +01:00
@staticmethod
def round(number, precision=1.0):
2008-10-29 18:27:08 +01:00
return round(number / precision) * precision
2010-08-15 00:23:46 +01:00
def check_factor_and_rate(self, ids):
"Check coherence between factor and rate"
2010-08-15 00:23:46 +01:00
for uom in self.browse(ids):
if uom.rate == uom.factor == 0.0:
continue
if uom.rate != round(1.0 / uom.factor, self.rate.digits[1]) and \
uom.factor != round(1.0 / uom.rate, self.factor.digits[1]):
return False
return True
2010-08-15 00:23:46 +01:00
def write(self, ids, values):
if Transaction().user == 0:
return super(Uom, self).write(ids, values)
if 'rate' not in values and 'factor' not in values \
and 'category' not in values:
2010-08-15 00:23:46 +01:00
return super(Uom, self).write(ids, values)
2009-04-06 16:44:55 +02:00
if isinstance(ids, (int, long)):
ids = [ids]
2010-08-15 00:23:46 +01:00
uoms = self.browse(ids)
old_uom = dict((uom.id, (uom.factor, uom.rate, uom.category.id)) \
for uom in uoms)
2010-08-15 00:23:46 +01:00
res = super(Uom, self).write(ids, values)
uoms = self.browse(ids)
for uom in 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]:
2010-08-15 00:23:46 +01:00
self.raise_user_error('change_uom_rate_title',
error_description='change_uom_rate')
return res
def select_accurate_field(self, uom):
"""
Select the more accurate field.
It chooses the field that has the least decimal.
:param uom: a BrowseRecord of UOM.
:return: 'factor' or 'rate'.
"""
lengths = {}
for field in ('rate', 'factor'):
format = '%%.%df' % getattr(self, field).digits[1]
lengths[field] = len((format % getattr(uom,
field)).split('.')[1].rstrip('0'))
if lengths['rate'] < lengths['factor']:
return 'rate'
elif lengths['factor'] < lengths['rate']:
return 'factor'
elif uom.factor >= 1.0:
return 'factor'
else:
return 'rate'
2010-08-15 00:23:46 +01:00
def compute_qty(self, from_uom, qty, to_uom=None, round=True):
"""
Convert quantity for given uom's.
:param from_uom: a BrowseRecord of product.uom
:param qty: an int or long or float value
:param to_uom: a BrowseRecord of product.uom
:param round: a boolean to round or not the result
:return: the converted quantity
"""
if not from_uom or not qty or not to_uom:
return qty
if from_uom.category.id != to_uom.category.id:
return qty
if self.select_accurate_field(from_uom) == '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 is not None:
if self.select_accurate_field(to_uom) == 'factor':
2008-11-03 15:39:40 +01:00
amount = amount / to_uom.factor
else:
2008-11-03 15:39:40 +01:00
amount = amount * to_uom.rate
if round:
amount = self.round(amount, to_uom.rounding)
return amount
2010-08-15 00:23:46 +01:00
def compute_price(self, from_uom, price, to_uom=False):
"""
Convert price for given uom's.
:param from_uom: a BrowseRecord of product.uom
:param price: a Decimal value
:param to_uom: a BrowseRecord of product.uom
:return: the converted price
"""
if not from_uom or not price or not to_uom:
return price
if from_uom.category.id != to_uom.category.id:
return price
factor_format = '%%.%df' % self.factor.digits[1]
rate_format = '%%.%df' % self.rate.digits[1]
2008-05-13 13:08:29 +02:00
if self.select_accurate_field(from_uom) == 'factor':
new_price = price / Decimal(factor_format % from_uom.factor)
else:
new_price = price * Decimal(rate_format % from_uom.rate)
if self.select_accurate_field(to_uom) == '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
2008-03-03 18:36:08 +01:00
2010-08-15 00:23:46 +01:00
def search(self, args, offset=0, limit=None, order=None,
count=False, query_string=False):
product_obj = self.pool.get('product.product')
args = args[:]
def process_args(args):
i = 0
while i < len(args):
2008-09-30 17:10:36 +02:00
#add test for xmlrpc that doesn't handle tuple
if (isinstance(args[i], tuple) \
or (isinstance(args[i], list) and len(args[i]) > 2 \
and args[i][1] in OPERATORS)) \
and args[i][0] == 'category' \
and isinstance(args[i][2], (list, tuple)) \
and len(args[i][2]) == 2 \
2008-09-30 17:10:36 +02:00
and args[i][2][1] in ('product.default_uom.category',
'uom.category'):
if not args[i][2][0]:
args[i] = ('id', '!=', '0')
else:
2008-09-30 17:10:36 +02:00
if args[i][2][1] == 'product.default_uom.category':
2010-08-15 00:23:46 +01:00
product = product_obj.browse(args[i][2][0])
2008-09-30 17:10:36 +02:00
category_id = product.default_uom.category.id
else:
2010-08-15 00:23:46 +01:00
uom = self.browse(args[i][2][0])
2008-09-30 17:10:36 +02:00
category_id = uom.category.id
args[i] = (args[i][0], args[i][1], category_id)
2008-09-30 17:10:36 +02:00
elif isinstance(args[i], list):
process_args(args[i])
i += 1
process_args(args)
2010-08-15 00:23:46 +01:00
return super(Uom, self).search(args, offset=offset, limit=limit,
order=order, count=count,
query_string=query_string)
2008-03-03 18:36:08 +01:00
Uom()