#This file is part of Tryton. The COPYRIGHT file at the top level of #this repository contains the full copyright notices and license terms. from decimal import Decimal from sql import Table from trytond.model import ModelView, ModelSQL, fields from trytond.pyson import Eval from trytond.transaction import Transaction __all__ = ['UomCategory', 'Uom'] STATES = { 'readonly': ~Eval('active', True), } DEPENDS = ['active'] class UomCategory(ModelSQL, ModelView): 'Product uom category' __name__ = 'product.uom.category' name = fields.Char('Name', required=True, translate=True) uoms = fields.One2Many('product.uom', 'category', 'Unit of Measures') @classmethod def __setup__(cls): super(UomCategory, cls).__setup__() cls._order.insert(0, ('name', 'ASC')) class Uom(ModelSQL, ModelView): 'Unit of measure' __name__ = 'product.uom' name = fields.Char('Name', size=None, required=True, states=STATES, translate=True, depends=DEPENDS) symbol = fields.Char('Symbol', size=10, required=True, states=STATES, translate=True, depends=DEPENDS) category = fields.Many2One('product.uom.category', 'UOM Category', required=True, ondelete='RESTRICT', states=STATES, depends=DEPENDS) rate = fields.Float('Rate', digits=(12, 12), required=True, states=STATES, depends=DEPENDS, help=('The coefficient for the formula:\n' '1 (base unit) = coef (this unit)')) factor = fields.Float('Factor', digits=(12, 12), states=STATES, required=True, depends=DEPENDS, 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) digits = fields.Integer('Display Digits', required=True) active = fields.Boolean('Active') @classmethod def __register__(cls, module_name): cursor = Transaction().cursor model_data = Table('ir_model_data') # Migration from 1.6: corrected misspelling of ounce (was once) cursor.execute(*model_data.update( columns=[model_data.fs_id], values=['uom_ounce'], where=(model_data.fs_id == 'uom_once') & (model_data.module == 'product'))) super(Uom, cls).__register__(module_name) @classmethod def __setup__(cls): super(Uom, cls).__setup__() cls._sql_constraints += [ ('non_zero_rate_factor', 'CHECK((rate != 0.0) or (factor != 0.0))', 'Rate and factor can not be both equal to zero.') ] cls._order.insert(0, ('name', 'ASC')) cls._error_messages.update({ 'change_uom_rate_title': ('You cannot change Rate, Factor or ' 'Category on a Unit of Measure. '), 'change_uom_rate': ('If the UOM is still not used, you can ' 'delete it otherwise you can deactivate it ' 'and create a new one.'), 'invalid_factor_and_rate': ( 'Invalid Factor and Rate values in UOM "%s".'), }) @classmethod def __post_setup__(cls): super(Uom, cls).__post_setup__() if 'category' not in cls._fields: print cls, cls._fields @classmethod def check_xml_record(cls, records, values): return True @staticmethod def default_rate(): return 1.0 @staticmethod def default_factor(): return 1.0 @staticmethod def default_active(): return True @staticmethod def default_rounding(): return 0.01 @staticmethod def default_digits(): return 2 @fields.depends('factor') def on_change_factor(self): if (self.factor or 0.0) == 0.0: return {'rate': 0.0} return { 'rate': round(1.0 / self.factor, self.__class__.rate.digits[1]), } @fields.depends('rate') def on_change_rate(self): if (self.rate or 0.0) == 0.0: return {'factor': 0.0} return { 'factor': round(1.0 / self.rate, self.__class__.factor.digits[1]), } @classmethod def search_rec_name(cls, name, clause): return ['OR', (cls._rec_name,) + tuple(clause[1:]), ('symbol',) + tuple(clause[1:]), ] @staticmethod def round(number, precision=1.0): return round(number / precision) * precision @classmethod def validate(cls, uoms): super(Uom, cls).validate(uoms) for uom in uoms: uom.check_factor_and_rate() def check_factor_and_rate(self): "Check coherence between factor and rate" if self.rate == self.factor == 0.0: return 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,)) @classmethod def write(cls, *args): if Transaction().user == 0: super(Uom, cls).write(*args) return 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 old_uom = dict((uom.id, (uom.factor, uom.rate, uom.category.id)) for uom in all_uoms) super(Uom, cls).write(*args) 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]: cls.raise_user_error('change_uom_rate_title', error_description='change_uom_rate') @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'): 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' elif self.factor >= 1.0: return 'factor' else: return 'rate' @classmethod def compute_qty(cls, from_uom, qty, to_uom=None, round=True): """ Convert quantity for given uom's. """ 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 from_uom.accurate_field == 'factor': amount = qty * from_uom.factor else: amount = qty / from_uom.rate if to_uom is not None: if to_uom.accurate_field == 'factor': amount = amount / to_uom.factor else: amount = amount * to_uom.rate if round: amount = cls.round(amount, to_uom.rounding) return amount @classmethod def compute_price(cls, from_uom, price, to_uom=None): """ Convert price for given uom's. """ 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' % cls.factor.digits[1] rate_format = '%%.%df' % cls.rate.digits[1] 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) 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) return new_price