trytond-farm/events/move_event.py

268 lines
11 KiB
Python

#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)