diff --git a/__init__.py b/__init__.py
index c8d1b24..f107064 100644
--- a/__init__.py
+++ b/__init__.py
@@ -20,6 +20,7 @@ def register():
specie.UIMenu,
specie.ActionActWindow,
specie.ActionWizard,
+ specie.SpecieProduct,
animal.Tag,
events.removal_event.RemovalType,
events.removal_event.RemovalReason,
@@ -39,7 +40,6 @@ def register():
animal_group.AnimalGroupWeight,
stock.Location,
stock.LocationSiloLocation,
- stock.LotAnimal,
stock.LotAnimalGroup,
stock.Lot,
stock.LotCostLine,
@@ -72,6 +72,7 @@ def register():
events.weaning_event.WeaningEvent,
events.weaning_event.WeaningEventAnimal,
events.weaning_event.WeaningEventFemaleCycle,
+ events.reclassification_event.ReclassficationEvent,
stock.Move,
production.BOM,
quality.QualityTest,
diff --git a/animal.py b/animal.py
index 7fc866a..4940868 100644
--- a/animal.py
+++ b/animal.py
@@ -10,6 +10,8 @@ from trytond.pool import Pool, PoolMeta
from trytond.wizard import Wizard, StateView, StateAction, Button, StateTransition
from trytond.exceptions import UserError
from trytond.i18n import gettext
+from trytond import backend
+from sql import Table
_STATES_MALE_FIELD = {
'invisible': Not(Equal(Eval('type'), 'male')),
@@ -142,8 +144,8 @@ class Animal(ModelSQL, ModelView, AnimalMixin):
})
breed = fields.Many2One('farm.specie.breed', 'Breed', required=True,
domain=[('specie', '=', Eval('specie'))], depends=['specie'])
- lot = fields.One2One('stock.lot-farm.animal', 'animal', 'lot',
- string='Lot', required=True, readonly=True, domain=[
+ lot = fields.Many2One('stock.lot', 'Lot',
+ readonly=True, domain=[
('animal_type', '=', Eval('type')),
], depends=['type'])
number = fields.Function(fields.Char('Number'),
@@ -211,11 +213,34 @@ class Animal(ModelSQL, ModelView, AnimalMixin):
], 'Purpose', states=_STATES_INDIVIDUAL_FIELD,
depends=_DEPENDS_INDIVIDUAL_FIELD)
active = fields.Boolean('Active')
+ lots= fields.One2Many(
+ 'stock.lot', 'animal', 'Lots', readonly=True)
# We can't use the 'required' attribute in field because it's
# checked on view before execute 'create()' function where this
# field is filled in.
+ @classmethod
+ def __register__(cls, module_name):
+ TableHandler = backend.get('TableHandler')
+ table = cls.__table_handler__(module_name)
+ sql_table = cls.__table__()
+ update_lot = False
+ if not table.column_exist('lot'):
+ update_lot = True
+ super().__register__(module_name)
+ table = cls.__table_handler__(module_name)
+ if update_lot:
+ sql_table_animal_lot = 'stock_lot-farm_animal'
+ if TableHandler.table_exist(sql_table_animal_lot):
+ sql_table_animal_lot = Table(sql_table_animal_lot)
+ cursor = Transaction().connection.cursor()
+ cursor.execute(*sql_table_animal_lot.select(
+ sql_table_animal_lot.animal, sql_table_animal_lot.lot))
+ for animal_id, lot_id in cursor.fetchall():
+ cursor.execute(*sql_table.update(columns=[sql_table.lot],
+ values=[lot_id], where=sql_table.id == animal_id))
+
@staticmethod
def default_specie():
return Transaction().context.get('specie')
@@ -388,13 +413,19 @@ class Animal(ModelSQL, ModelView, AnimalMixin):
location = Location(vals['initial_location'])
vals['number'] = cls._calc_number(vals['specie'],
location.warehouse.id, vals['type'])
+
+ new_animals = super(Animal, cls).create(vlist)
+ for animal, vals in zip(new_animals, vlist):
+ vals['id'] = animal.id
if vals.get('lot'):
lot = Lot(vals['lot'])
Lot.write([lot], cls._get_lot_values(vals, False))
+ animal.lot = lot
+ animal.save()
else:
new_lot, = Lot.create([cls._get_lot_values(vals, True)])
- vals['lot'] = new_lot.id
- new_animals = super(Animal, cls).create(vlist)
+ animal.lot = new_lot
+ animal.save()
if not context.get('no_create_stock_move'):
cls._create_and_done_first_stock_move(new_animals)
return new_animals
@@ -455,6 +486,7 @@ class Animal(ModelSQL, ModelView, AnimalMixin):
'number': animal_vals['number'],
'product': product.id,
'animal_type': animal_vals['type'],
+ 'animal': animal_vals['id']
}
if Transaction().context.get('create_cost_lines', True):
cost_lines = lot_tmp._on_change_product_cost_lines().get('add')
diff --git a/events/__init__.py b/events/__init__.py
index ce48ffa..12eba14 100644
--- a/events/__init__.py
+++ b/events/__init__.py
@@ -15,6 +15,7 @@ from . import abort_event
from . import farrowing_event
from . import foster_event
from . import weaning_event
+from . import reclassification_event
from . import event_order
@@ -22,4 +23,4 @@ __all__ = ['abstract_event', 'move_event', 'feed_event', 'feed_inventory',
'medication_event', 'transformation_event', 'removal_event',
'semen_extraction_event', 'insemination_event',
'pregnancy_diagnosis_event', 'abort_event', 'farrowing_event',
- 'foster_event', 'weaning_event', 'event_order']
+ 'foster_event', 'weaning_event', 'reclassification_event', 'event_order']
diff --git a/events/farrowing_event.py b/events/farrowing_event.py
index 019189a..7607bc2 100644
--- a/events/farrowing_event.py
+++ b/events/farrowing_event.py
@@ -17,6 +17,7 @@ _INVISIBLE_NOT_GROUP = {
}
+
class FarrowingProblem(ModelSQL, ModelView):
'''Farrowing Event Problem'''
__name__ = 'farm.farrowing.problem'
diff --git a/events/reclassification_event.py b/events/reclassification_event.py
new file mode 100644
index 0000000..284849f
--- /dev/null
+++ b/events/reclassification_event.py
@@ -0,0 +1,198 @@
+# The COPYRIGHT file at the top level of this repository contains the full
+# copyright notices and license terms.
+from trytond.exceptions import UserError
+from trytond.model import fields, ModelView, Workflow
+from trytond.pyson import Equal, Eval, If, Not
+from trytond.pool import Pool
+from trytond.transaction import Transaction
+from trytond.i18n import gettext
+
+from .abstract_event import AbstractEvent, _STATES_VALIDATED_ADMIN, \
+ _DEPENDS_VALIDATED_ADMIN
+
+
+class ReclassficationEvent(AbstractEvent):
+ '''Farm Reclassification Event'''
+ __name__ = 'farm.reclassification.event'
+ _table = 'farm_reclassification_event'
+
+ reclassification_product = fields.Many2One(
+ 'product.product', "Reclassification Product", required=True,
+ domain=[('id', 'in', Eval('valid_products'))],
+ states={
+ 'readonly': Not(Equal(Eval('state'), 'draft')),
+ }, depends=['animal_type', 'state', 'valid_products'])
+ valid_products = fields.Function(fields.Many2Many(
+ 'product.product', None, None, 'Valid Products'),
+ 'on_change_with_valid_products')
+ in_move = fields.Many2One(
+ 'stock.move', 'Input Stock Move', readonly=True,
+ states=_STATES_VALIDATED_ADMIN, depends=_DEPENDS_VALIDATED_ADMIN)
+ out_move = fields.Many2One(
+ 'stock.move', 'Output Stock Move',
+ readonly=True, states=_STATES_VALIDATED_ADMIN,
+ depends=_DEPENDS_VALIDATED_ADMIN)
+ to_location = fields.Many2One('stock.location', 'Destination',
+ required=True, states={
+ 'readonly': Not(Equal(Eval('state'), 'draft')),
+ }, domain=[
+ ('type', '=', 'storage'),
+ ('silo', '=', False),
+ ], depends=['state'])
+
+ @classmethod
+ def __setup__(cls):
+ super().__setup__()
+ cls.animal.domain += [
+ ('farm', '=', Eval('farm')),
+ ('location.type', '=', 'storage'),
+ ('type', '=', 'individual'),
+ ]
+ if 'farm' not in cls.animal.depends:
+ cls.animal.depends.append('farm')
+
+ cls._buttons.update({
+ 'draft': {
+ 'invisible': True,
+ },
+ })
+
+ @fields.depends('animal')
+ def on_change_with_valid_products(self, name=None):
+ if self.animal and self.animal.specie:
+ return [p.id for p in self.animal.specie.reclassification_products]
+ return []
+
+ @classmethod
+ @ModelView.button
+ @Workflow.transition('validated')
+ def validate_event(cls, events):
+ """
+ Create the input and output stock moves.
+ """
+ pool = Pool()
+ Move = pool.get('stock.move')
+
+ for reclass_event in events:
+ if reclass_event.in_move and reclass_event.out_move:
+ raise UserError(gettext(
+ 'farm.related_stock_moves',
+ event=reclass_event.id,
+ in_move=reclass_event.in_move.id,
+ out_move=reclass_event.out_move.id
+ ))
+ if (reclass_event.animal.lot.product ==
+ reclass_event.reclassification_product):
+ raise UserError(gettext(
+ 'farm.invalid_reclassification_product',
+ event=reclass_event.id,
+ product=reclass_event.reclassification_product
+ ))
+
+ new_in_move = reclass_event._get_event_input_move()
+ new_in_move.save()
+ Move.assign([new_in_move])
+ Move.do([new_in_move])
+ new_out_move = reclass_event._get_event_output_move()
+ new_out_move.save()
+ Move.assign([new_out_move])
+ Move.do([new_out_move])
+ reclass_event.in_move = new_in_move
+ reclass_event.out_move = new_out_move
+ reclass_event.save()
+
+ @fields.depends('animal', 'valid_products', 'to_location')
+ def on_change_animal(self):
+ super().on_change_animal()
+ if not self.animal:
+ return
+ self.to_location = self.animal.location
+
+ def _get_new_lot_values(self):
+ """
+ Prepare values to create the new stock.lot for the reclassificated
+ animal. It returns a dictionary with values to create stock.lot
+ """
+ pool = Pool()
+ Lot = pool.get('stock.lot')
+ if not self.animal:
+ return {}
+ product = self.reclassification_product
+ lot_tmp = Lot(product=product)
+ # TODO Improve the manage of animal/lot number, currently using
+ # the animal number to create the new lot
+ res = {
+ 'number': self.animal.number,
+ 'product': product.id,
+ 'animal_type': self.animal.type,
+ 'animal': self.animal,
+ }
+ if Transaction().context.get('create_cost_lines', True):
+ cost_lines = lot_tmp._on_change_product_cost_lines().get('add')
+ if cost_lines:
+ res['cost_lines'] = [('create', [cl[1] for cl in cost_lines])]
+ return res
+
+ def _get_event_input_move(self):
+ pool = Pool()
+ Move = pool.get('stock.move')
+ context = Transaction().context
+
+ if self.animal_type == 'group':
+ lot = self.animal_group.lot
+ else:
+ lot = self.animal.lot
+ production_location = self.farm.production_location
+ return Move(
+ product=lot.product,
+ uom=lot.product.default_uom,
+ quantity=1,
+ from_location=self.animal.location,
+ to_location=production_location,
+ planned_date=self.timestamp.date(),
+ effective_date=self.timestamp.date(),
+ company=context.get('company'),
+ lot=lot,
+ origin=self,
+ )
+
+ def _get_event_output_move(self):
+ pool = Pool()
+ Move = pool.get('stock.move')
+ Lot = pool.get('stock.lot')
+ context = Transaction().context
+ lots = Lot.create([self._get_new_lot_values()])
+ if lots:
+ lot, = lots
+ lot.save()
+ self.animal.lot = lot
+ self.animal.save()
+ production_location = self.farm.production_location
+
+ return Move(
+ product=lot.product,
+ uom=lot.product.default_uom,
+ quantity=1,
+ from_location=production_location,
+ to_location=self.to_location,
+ planned_date=self.timestamp.date(),
+ effective_date=self.timestamp.date(),
+ company=context.get('company'),
+ lot=lot,
+ unit_price=lot.product.cost_price,
+ origin=self,
+ )
+
+ @classmethod
+ def copy(cls, records, default=None):
+ if default is None:
+ default = {}
+ else:
+ default = default.copy()
+ default.update({
+ 'reclassification_product': None,
+ 'to_location': None,
+ 'in_move': None,
+ 'out_move': None,
+ })
+ return super().copy(records, default=default)
diff --git a/events/reclassification_event.xml b/events/reclassification_event.xml
new file mode 100644
index 0000000..b5a90ee
--- /dev/null
+++ b/events/reclassification_event.xml
@@ -0,0 +1,116 @@
+
+
+