#The COPYRIGHT file at the top level of this repository contains the full #copyright notices and license terms. from trytond.model import fields, ModelView, Workflow from trytond.pyson import Bool, Equal, Eval, Id, If, Not, Or from trytond.pool import Pool from trytond.transaction import Transaction from .abstract_event import AbstractEvent, _STATES_WRITE_DRAFT, \ _DEPENDS_WRITE_DRAFT, _STATES_VALIDATED_ADMIN, _DEPENDS_VALIDATED_ADMIN __all__ = ['MoveEvent'] class MoveEvent(AbstractEvent): '''Farm Move Event''' __name__ = 'farm.move.event' _table = 'farm_move_event' from_location = fields.Many2One('stock.location', 'Origin', required=True, domain=[ ('warehouse', '=', Eval('farm')), ('type', '=', 'storage'), ], states={ 'readonly': Or( Not(Bool(Eval('farm', 0))), Not(Equal(Eval('state'), 'draft')), ), }, depends=['farm', 'state'], context={'restrict_by_specie_animal_type': True}) to_location = fields.Many2One('stock.location', 'Destination', required=True, domain=[ ('type', '=', 'storage'), ('id', '!=', Eval('from_location')), ], states={ 'readonly': Or( Not(Bool(Eval('from_location', 0))), Not(Equal(Eval('state'), 'draft')), ), }, depends=['from_location', 'state'], context={'restrict_by_specie_animal_type': True}) quantity = fields.Integer('Quantity', required=True, states={ 'invisible': Not(Equal(Eval('animal_type'), 'group')), 'readonly': Not(Equal(Eval('state'), 'draft')), }, on_change_with=['animal_type', 'animal_group', 'timestamp', 'from_location'], depends=['animal_type', 'animal_group', 'from_location', 'state']) # TODO: register price? ajuntar explicacio amb la resta de 'cost_price' #cost_price = fields.Numeric('Cost Price', required=True, digits=(16, 4), # states={, # 'readonly': Not(Equal(Eval('state'), 'draft')), # }, on_change_with=['animal_type', 'animal_group'], # depends=['state', 'animal_type', 'animal_group'], # help='Product\'s cost of Animal or Group for analytical accounting.') uom = fields.Many2One('product.uom', "UOM", domain=[('category', '=', Id('product', 'uom_cat_weight'))], states={ 'readonly': Not(Equal(Eval('state'), 'draft')), 'required': Bool(Eval('weight')), }, depends=['state', 'weight']) unit_digits = fields.Function(fields.Integer('Unit Digits', on_change_with=['uom']), 'on_change_with_unit_digits') weight = fields.Numeric('Weight', digits=(16, Eval('unit_digits', 2)), states=_STATES_WRITE_DRAFT, depends=_DEPENDS_WRITE_DRAFT + ['unit_digits']) move = fields.Many2One('stock.move', 'Stock Move', readonly=True, domain=[ ('lot', '=', Eval('lot')), ], states=_STATES_VALIDATED_ADMIN, depends=_DEPENDS_VALIDATED_ADMIN + ['lot']) weight_record = fields.Reference('Weight Record', selection=[ (None, ''), ('farm.animal.weight', 'Animal Weight'), ('farm.animal.group.weight', 'Group Weight'), ], readonly=True, states={ 'invisible': Not(Eval('groups', []).contains( Id('farm', 'group_farm_admin'))), }, depends=['state', 'weight']) @classmethod def __setup__(cls): super(MoveEvent, cls).__setup__() cls.animal.domain += [ If(Equal(Eval('state'), 'draft'), If(Bool(Eval('from_location', 0)), ('location', '=', Eval('from_location')), ('location.type', '=', 'storage')), ('location', '=', Eval('to_location'))), ] if 'state' not in cls.animal.depends: cls.animal.depends.append('state') if 'from_location' not in cls.animal.depends: cls.animal.depends.append('from_location') if 'to_location' not in cls.animal.depends: cls.animal.depends.append('to_location') if 'from_location' not in cls.animal.on_change: cls.animal.on_change.append('from_location') cls._error_messages.update({ 'animal_not_in_location': ('The move event of animal ' '"%(animal)s" is trying to move it from location ' '"%(from_location)s" but it isn\'t there at ' '"%(timestamp)s".'), 'group_not_in_location': ('The move event of group ' '"%(group)s" is trying to move %(quantity)s animals ' 'from location "%(from_location)s" but there isn\'t ' 'enough there at "%(timestamp)s".'), }) cls._sql_constraints += [ ('quantity_positive', 'check ( quantity != 0 )', 'In Move Events, the quantity can\'t be zero'), ('quantity_1_for_animals', ("check ( animal_type = 'group' or " "(quantity = 1 or quantity = -1))"), 'In Move Events, the quantity must be 1 for Animals (not ' 'Groups).'), ('weight_0_or_positive', "check ( weight >= 0.0 )", 'In Move Events, the weight can\'t be zero'), ] @staticmethod def default_quantity(): return 1 @staticmethod def default_uom(): return Pool().get('ir.model.data').get_id('product', 'uom_kilogram') @staticmethod def default_unit_digits(): return 2 @staticmethod def valid_animal_types(): return ['male', 'female', 'individual', 'group'] def on_change_animal(self): res = super(MoveEvent, self).on_change_animal() res['from_location'] = (self.animal and self.animal.location.id or None) return res def on_change_with_quantity(self): if self.animal_type != 'group': return 1 if not self.animal_group or not self.from_location: return None with Transaction().set_context( locations=[self.from_location.id], stock_date_end=self.timestamp.date()): return self.animal_group.lot.quantity or None def on_change_with_unit_digits(self, name=None): if self.uom: return self.uom.digits return 2 @classmethod @ModelView.button @Workflow.transition('validated') def validate_event(cls, events): """ Create an stock move and, if weight > 0.0, a farm.animal[.group].weight """ Move = Pool().get('stock.move') todo_moves = [] for move_event in events: assert not move_event.move, ('Move Event "%s" already has a ' 'related stock move: "%s"' % (move_event.id, move_event.move.id)) if move_event.animal_type != 'group': if not move_event.animal.check_in_location( move_event.from_location, move_event.timestamp): cls.raise_user_error('animal_not_in_location', { 'animal': move_event.animal.rec_name, 'from_location': move_event.from_location.rec_name, 'timestamp': move_event.timestamp, }) move_event.animal.check_allowed_location( move_event.to_location, move_event.rec_name) else: if not move_event.animal_group.check_in_location( move_event.from_location, move_event.timestamp, move_event.quantity): cls.raise_user_error('group_not_in_location', { 'group': move_event.animal_group.rec_name, 'from_location': move_event.from_location.rec_name, 'quantity': move_event.quantity, 'timestamp': move_event.timestamp, }) move_event.animal_group.check_allowed_location( move_event.to_location, move_event.rec_name) new_move = move_event._get_event_move() new_move.save() todo_moves.append(new_move) move_event.move = new_move if move_event.weight: assert not move_event.weight_record, ('Move Event "%s" ' 'already has a related weight record: "%s"' % (move_event.id, move_event.weight_record)) new_weight_record = move_event._get_weight_record() new_weight_record.save() move_event.weight_record = new_weight_record move_event.save() Move.assign(todo_moves) Move.do(todo_moves) def _get_event_move(self): pool = Pool() Move = pool.get('stock.move') context = Transaction().context lot = (self.animal_type != 'group' and self.animal.lot or self.animal_group.lot) return Move( product=lot.product, uom=lot.product.default_uom, quantity=self.quantity, from_location=self.from_location, to_location=self.to_location, planned_date=self.timestamp.date(), effective_date=self.timestamp.date(), company=context.get('company'), lot=lot, origin=self) def _get_weight_record(self): pool = Pool() AnimalWeight = pool.get('farm.animal.weight') AnimalGroupWeight = pool.get('farm.animal.group.weight') if self.animal_type != 'group': return AnimalWeight( animal=self.animal.id, timestamp=self.timestamp, uom=self.uom, weight=self.weight, ) else: return AnimalGroupWeight( group=self.animal_group.id, timestamp=self.timestamp, quantity=self.quantity, uom=self.uom, weight=self.weight, ) @classmethod def copy(cls, records, default=None): if default is None: default = {} else: default = default.copy() default.updat({ 'move': None, 'weight_record': None, }) return super(MoveEvent, cls).copy(records, default=default)