trytond-farm/specie.py

661 lines
28 KiB
Python

# The COPYRIGHT file at the top level of this repository contains the full
# copyright notices and license terms.
import logging
from operator import attrgetter
from trytond.model import ModelView, ModelSQL, fields, Unique
from trytond.pool import Pool, PoolMeta
from trytond.pyson import PYSONDecoder, PYSONEncoder, Bool, Eval, Id, Not, Or
from trytond.transaction import Transaction
from trytond.exceptions import UserError
from trytond.i18n import gettext
MODULE_NAME = 'farm'
def _enabled_STATES(depending_fieldname):
return {
'readonly': Not(Bool(Eval(depending_fieldname))),
'required': Bool(Eval(depending_fieldname)),
}
class Specie(ModelSQL, ModelView):
'''Animal Specie'''
__name__ = 'farm.specie'
name = fields.Char('Name', translate=True, required=True,
help='Name of the specie. ie. "Pig"')
male_product = fields.Many2One('product.product', "Male's Product",
domain=[('default_uom.category', '=', Id('product', 'uom_cat_unit'))],
states=_enabled_STATES('male_enabled'), depends=['male_enabled'])
male_enabled = fields.Boolean('Males Enabled', help="If checked the menus "
"to manage this kind of animal will be generated. If you don't want "
"it, uncheck and put a generic product or other type of animal "
"because it is required.")
female_product = fields.Many2One('product.product', "Female's Product",
domain=[('default_uom.category', '=', Id('product', 'uom_cat_unit'))],
states=_enabled_STATES('female_enabled'), depends=['female_enabled'])
female_enabled = fields.Boolean('Females Enabled', help="If checked the "
"menus to manage this kind of animal will be generated. If you don't "
"want it, uncheck and put a generic product or other type of animal "
"because it is required.")
individual_product = fields.Many2One('product.product',
"Individual's Product",
domain=[('default_uom.category', '=', Id('product', 'uom_cat_unit'))],
states=_enabled_STATES('individual_enabled'),
depends=['individual_enabled'])
individual_enabled = fields.Boolean('Individuals Enabled', help="If "
"checked the menus to manage this kind of animal will be generated. "
"If you don't want it, uncheck and put a generic product or other "
"type of animal because it is required.")
group_product = fields.Many2One('product.product', "Group's Product",
domain=[('default_uom.category', '=', Id('product', 'uom_cat_unit'))],
states=_enabled_STATES('group_enabled'), depends=['group_enabled'])
group_enabled = fields.Boolean('Groups Enabled', help="If checked the "
"menus to manage this kind of animal will be generated. If you don't "
"want it, uncheck and put a generic product or other type of animal "
"because it is required.")
semen_product = fields.Many2One('product.product', "Semen's Product",
# TODO: it doesn't work but it should
# domain=[
# ('default_uom.category', '=', Id('product', 'uom_cat_volume')),
# ],
states={
'readonly': Not(Or(Bool(Eval('male_enabled')),
Bool(Eval('female_enabled')))),
'required': Or(Bool(Eval('male_enabled')),
Bool(Eval('female_enabled'))),
}, depends=['male_enabled', 'female_enabled'],
help="Product for the mixture of semen to raise the expected "
"quality.\nIt is used in the Production lots produced in the "
"Extraction Events and in the BoM containers for doses used in "
"deliveries to farms for inseminations.")
breeds = fields.One2Many('farm.specie.breed', 'specie', 'Breeds')
farm_lines = fields.One2Many('farm.specie.farm_line',
'specie', 'Farms')
removed_location = fields.Many2One('stock.location', 'Removed Location',
domain=[('type', '=', 'lost_found')], required=True,
help='Virtual location where removed animals are moved to.')
foster_location = fields.Many2One('stock.location', 'Foster Location',
domain=[('type', '=', 'lost_found')], required=True,
help='Virtual location where fostered animals are moved to.')
lost_found_location = fields.Many2One('stock.location',
'Lost and Found Location', domain=[('type', '=', 'lost_found')],
required=True,
help='Virtual location where lost or found animals are moved to.')
feed_lost_found_location = fields.Many2One('stock.location',
'Feed Lost Location', domain=[('type', '=', 'lost_found')],
required=True)
events = fields.Many2Many('farm.specie-ir.model', 'specie', 'model',
'Events', domain=[('model', 'like', 'farm.%.event')],
help='Type of events available for this specie')
menus = fields.One2Many('ir.ui.menu', 'specie', 'Menus')
actions = fields.One2Many('ir.action.act_window', 'specie', 'Actions')
wizards = fields.One2Many('ir.action.wizard', 'specie', 'Wizards')
produced_animal_type = fields.Selection([
('individual', 'Individual'),
('group', 'Group'),
], 'Produced Animal Type')
reclassification_products = fields.Many2Many(
'farm.specie-product.product', 'specie', 'product',
'Reclassification Products')
@classmethod
def __setup__(cls):
super(Specie, cls).__setup__()
t = cls.__table__()
cls._sql_constraints += [
('semen_product_uniq', Unique(t, t.semen_product),
'farm.specie_semen_product_unique'),
]
cls._buttons.update({
'create_menu_entries': {
'icon': 'tryton-ok',
}
})
@staticmethod
def default_produced_animal_type():
return 'group'
@staticmethod
def default_male_enabled():
return True
@staticmethod
def default_female_enabled():
return True
@staticmethod
def default_individual_enabled():
return True
@staticmethod
def default_group_enabled():
return True
@classmethod
@ModelView.button
def create_menu_entries(cls, species):
"""
Create all menu options (and actions) for:
- Males
- Females
- Groups/Lots
- Individuals
- Events related with the specie
Store menus and actions created in order to be able to remove or
recreate them later.
If menu entries (and actions) had already been created, do not remove
them but create only the missing ones, otherwise user shortcuts to
existing menu entries would become invalid or would be removed.
If necessary, replace events many2many by a one2many one with
related menu and actions.
"""
pool = Pool()
Menu = pool.get('ir.ui.menu')
ModelData = pool.get('ir.model.data')
ActWindow = pool.get('ir.action.act_window')
EventOrder = pool.get('farm.event.order')
menus_by_animal_type = cls._get_menus_by_animal_type()
animal_types_set = set(menus_by_animal_type.keys())
menus_by_event_type = cls._get_menus_by_event_type()
event_name_by_model = {}
farm_menu = Menu(ModelData.get_id(MODULE_NAME, 'menu_farm'))
specie_menu_template = Menu(ModelData.get_id(MODULE_NAME,
'menu_specie_menu_template'))
event_order_menu = Menu(ModelData.get_id(MODULE_NAME,
'menu_farm_event_order'))
# order_type_labels = dict(EventOrder.fields_get(
# ['event_type'])['event_type']['selection'])
specie_seq = 1
for specie in species:
enabled_animal_types = []
for animal_type in animal_types_set:
if getattr(specie, '%s_enabled' % animal_type):
enabled_animal_types.append(animal_type)
if not enabled_animal_types:
raise UserError(gettext('farm.no_animal_type_enabled',
specie=specie.rec_name))
current_menus = list(specie.menus)[:]
current_actions = list(specie.actions)[:]
current_wizards = list(specie.wizards)[:]
logging.getLogger('farm.specie').debug(
"current_menus=%s\ncurrent_actions=%s\ncurrent_wizards=%s"
% (current_menus, current_actions, current_wizards))
specie_menu = specie._duplicate_menu(specie_menu_template,
farm_menu, specie_seq, current_menus, current_actions,
current_wizards, new_name=specie.name)
specie_seq += 1
specie_submenu_seq = 1
for animal_type in enabled_animal_types:
new_domain = [
('specie', '=', specie.id),
]
new_context = {
'specie': specie.id,
'animal_type': animal_type,
}
animals_menu = specie._duplicate_menu(
menus_by_animal_type[animal_type]['animals'],
specie_menu, specie_submenu_seq, current_menus,
current_actions, current_wizards, new_domain=new_domain,
new_context=new_context)
specie_submenu_seq += 1
# The event's models are created in the system in a logic order
# to improve useability
enabled_events = sorted(specie.events[:], key=attrgetter('id'))
new_domain.append(('animal_type', '=', animal_type))
animal_submenu_seq = 1
for event in enabled_events:
model_name = event.model
event_menu = None
if model_name in menus_by_event_type['generic']:
event_menu = menus_by_event_type['generic'][model_name]
elif (animal_type in menus_by_event_type and
model_name in menus_by_event_type[animal_type]):
event_menu = (
menus_by_event_type[animal_type][model_name])
else:
continue
event_name_by_model[model_name] = event_menu.name
specie._duplicate_menu(event_menu, animals_menu,
animal_submenu_seq, current_menus, current_actions,
current_wizards, new_domain=new_domain,
new_context=new_context)
animal_submenu_seq += 1
for extra_menu in menus_by_animal_type[animal_type]['extra']:
specie._duplicate_menu(extra_menu, animals_menu,
animal_submenu_seq, current_menus, current_actions,
current_wizards, new_domain=new_domain,
new_icon='tryton-launch', new_context=new_context)
animal_submenu_seq += 1
# Orders submenus
for animal_type in enabled_animal_types:
orders_menu = specie._duplicate_menu(
menus_by_animal_type[animal_type]['orders'],
specie_menu, specie_submenu_seq, current_menus,
current_actions, current_wizards)
specie_submenu_seq += 1
orders_submenu_seq = 1
for order_type in EventOrder.event_types_by_animal_type(
animal_type, True):
event_name = event_name_by_model.get('farm.%s.event'
% order_type)
order_domain = [
('specie', '=', specie.id),
('animal_type', '=', animal_type),
('event_type', '=', order_type),
]
order_context = {
'specie': specie.id,
'animal_type': animal_type,
'event_type': order_type,
}
specie._duplicate_menu(event_order_menu,
orders_menu, orders_submenu_seq, current_menus,
current_actions, current_wizards,
new_name=event_name, new_icon='tryton-board',
new_domain=order_domain, new_context=order_context)
orders_submenu_seq += 1
specie_submenu_seq += 1
specie._create_additional_menus(specie_menu,
specie_submenu_seq, current_menus, current_actions,
current_wizards)
logging.getLogger('farm.specie').debug(
"Current actions (to be deleted): %s" % current_actions)
if current_actions:
ActWindow.delete(current_actions)
logging.getLogger('farm.specie').debug(
"Current wizards (to be deleted): %s" % current_wizards)
if current_wizards:
ActWindow.delete(current_wizards)
logging.getLogger('farm.specie').debug(
"Current menus (to be deleted): %s" % current_menus)
if current_menus:
Menu.delete(current_menus)
return 'reload menu'
def _create_additional_menus(self, specie_menu, specie_submenu_seq,
current_menus, current_actions, current_wizards):
pool = Pool()
Menu = pool.get('ir.ui.menu')
ModelData = pool.get('ir.model.data')
silo_inventories_menu = Menu(ModelData.get_id(MODULE_NAME,
'menu_farm_silo_inventories'))
feed_inventory_menu = Menu(ModelData.get_id(MODULE_NAME,
'menu_farm_feed_inventory'))
feed_provisional_inventory_menu = Menu(ModelData.get_id(MODULE_NAME,
'menu_farm_feed_provisional_inventory'))
# Feed Inventories submenu
feed_inventories_menu = self._duplicate_menu(silo_inventories_menu,
specie_menu, specie_submenu_seq, current_menus,
current_actions, current_wizards)
specie_submenu_seq += 1
new_domain = [
('specie', '=', self.id),
]
new_context = {
'specie': self.id,
}
self._duplicate_menu(feed_inventory_menu, feed_inventories_menu, 1,
current_menus, current_actions, current_wizards,
new_domain=new_domain, new_context=new_context)
self._duplicate_menu(feed_provisional_inventory_menu,
feed_inventories_menu, 2, current_menus, current_actions,
current_wizards, new_domain=new_domain, new_context=new_context)
return specie_submenu_seq
@staticmethod
def _get_menus_by_animal_type():
pool = Pool()
Menu = pool.get('ir.ui.menu')
ModelData = pool.get('ir.model.data')
return {
'male': {
'animals': Menu(ModelData.get_id(MODULE_NAME,
'menu_farm_animal_males')),
'orders': Menu(ModelData.get_id(MODULE_NAME,
'menu_farm_order_males')),
'extra': [],
},
'female': {
'animals': Menu(ModelData.get_id(MODULE_NAME,
'menu_farm_animal_females')),
'orders': Menu(ModelData.get_id(MODULE_NAME,
'menu_farm_order_females')),
'extra': [
Menu(ModelData.get_id(MODULE_NAME,
'menu_farm_create_female')),
],
},
'individual': {
'animals': Menu(ModelData.get_id(MODULE_NAME,
'menu_farm_animal_individuals')),
'orders': Menu(ModelData.get_id(MODULE_NAME,
'menu_farm_order_individuals')),
'extra': [],
},
'group': {
'animals': Menu(ModelData.get_id(MODULE_NAME,
'menu_farm_animal_groups')),
'orders': Menu(ModelData.get_id(MODULE_NAME,
'menu_farm_order_groups')),
'extra': [],
},
}
@staticmethod
def _get_menus_by_event_type():
pool = Pool()
Menu = pool.get('ir.ui.menu')
ModelData = pool.get('ir.model.data')
return {
'generic': {
'farm.move.event': Menu(ModelData.get_id(MODULE_NAME,
'menu_farm_move_event')),
'farm.feed.event': Menu(ModelData.get_id(MODULE_NAME,
'menu_farm_feed_event')),
'farm.medication.event': Menu(ModelData.get_id(MODULE_NAME,
'menu_farm_medication_event')),
'farm.transformation.event': Menu(ModelData.get_id(MODULE_NAME,
'menu_farm_transformation_event')),
'farm.removal.event': Menu(ModelData.get_id(MODULE_NAME,
'menu_farm_removal_event')),
},
'male': {
'farm.semen_extraction.event': Menu(
ModelData.get_id(MODULE_NAME,
'menu_farm_semen_extraction_event')),
},
'female': {
'farm.insemination.event': Menu(ModelData.get_id(MODULE_NAME,
'menu_farm_insemination_event')),
'farm.pregnancy_diagnosis.event': Menu(
ModelData.get_id(MODULE_NAME,
'menu_farm_pregnancy_diagnosis_event')),
'farm.abort.event': Menu(ModelData.get_id(MODULE_NAME,
'menu_farm_abort_event')),
'farm.farrowing.event': Menu(ModelData.get_id(MODULE_NAME,
'menu_farm_farrowing_event')),
'farm.foster.event': Menu(ModelData.get_id(MODULE_NAME,
'menu_farm_foster_event')),
'farm.weaning.event': Menu(ModelData.get_id(MODULE_NAME,
'menu_farm_weaning_event')),
},
'individual': {
'farm.reclassification.event': Menu(ModelData.get_id(MODULE_NAME,
'menu_farm_reclassification_event')),
}
}
def _duplicate_menu(self, original_menu, parent_menu, sequence,
current_menus, current_actions, current_wizards, new_name=None,
new_icon=None, new_domain=None, new_context=None):
pool = Pool()
Menu = pool.get('ir.ui.menu')
menus = Menu.search([
('id', 'in', [x.id for x in current_menus]),
('parent', '=', parent_menu.id),
('name', '=', new_name if new_name else original_menu.name),
])
menu = None
if menus:
menu = menus[0]
menu_action = self._duplicate_menu_action(menu, original_menu,
current_actions, current_wizards, new_domain, new_context)
menu_vals = {
'parent': parent_menu.id,
'sequence': sequence,
'action': str(menu_action) if menu_action else None,
'specie': self.id,
'active': True,
}
if new_name:
menu_vals['name'] = new_name
if new_icon:
menu_vals['icon'] = new_icon
if menu:
logging.getLogger('farm.specie').debug("Writting menu %s" % menu)
Menu.write([menu], menu_vals)
if menu in current_menus:
current_menus.remove(menu)
else:
logging.getLogger('farm.specie').debug("Creating new menu copying "
"from original menu %s" % original_menu)
menu, = Menu.copy([original_menu])
Menu.write([menu], menu_vals)
if new_name:
self._write_field_in_langs(Menu, menu, 'name', new_name)
return menu
def _duplicate_menu_action(self, menu, original_menu, current_actions,
current_wizards, new_domain=None, new_context=None):
pool = Pool()
Keyword = pool.get('ir.action.keyword')
if not original_menu.action:
return None
original_action = original_menu.action
Action = original_action.__class__
action_vals = {
'specie': self.id,
}
if hasattr(original_action, 'domain'):
domain = (
PYSONDecoder().decode(original_action.pyson_domain)
if original_action.pyson_domain else [])
if new_domain:
domain.extend(new_domain)
action_vals['domain'] = PYSONEncoder().encode(domain)
if hasattr(original_action, 'context'):
original_context = (
PYSONDecoder().decode(original_action.pyson_context)
if original_action.pyson_context else [])
if original_context:
new_context.update(original_context)
action_vals['context'] = PYSONEncoder().encode(new_context)
menu_action = menu.action if menu else None
if menu_action:
Action.write([menu_action], action_vals)
logging.getLogger('farm.specie').debug("Writting action %s to "
"be placed in menu %s" % (menu_action, menu))
if menu_action in current_actions:
logging.getLogger('farm.specie').debug(
"Removing from current_actions")
current_actions.remove(menu_action)
elif menu_action in current_wizards:
logging.getLogger('farm.specie').debug(
"Removing from current_wizards")
current_wizards.remove(menu_action)
else:
logging.getLogger('farm.specie').info(
"Creating new action as copy of %s to be placed in menu %s"
% (original_action, menu if menu else original_menu))
menu_action, = Action.copy([original_action], action_vals)
Keyword.delete([a for a in menu_action.keywords])
return menu_action
@classmethod
def _write_field_in_langs(cls, Proxy, obj, fieldname, untranslated_value):
Translation = Pool().get('ir.translation')
lang_codes = cls._get_lang_codes()
for lang in lang_codes:
translated_value = Translation.get_source('farm.specie', 'view',
lang, untranslated_value)
logging.getLogger('farm.specie').debug(
"Translated value of name='farm.specie', ttype='view', "
"lang='%s', source='%s': >%s<" % (lang, untranslated_value,
translated_value))
if translated_value:
with Transaction().set_context(language=lang):
Proxy.write([obj], {
fieldname: translated_value,
})
@staticmethod
def _get_lang_codes():
Lang = Pool().get('ir.lang')
langs = Lang.search([
('translatable', '=', True),
])
return [x.code for x in langs]
@classmethod
def copy(cls, records, default=None):
if default is None:
default = {}
else:
default = default.copy()
default['actions'] = False
default['wizards'] = False
default['menus'] = False
return super(Specie, cls).copy(records, default=default)
class Breed(ModelSQL, ModelView):
'Breed of Specie'
__name__ = 'farm.specie.breed'
specie = fields.Many2One('farm.specie', 'Specie', required=True,
ondelete='CASCADE')
name = fields.Char('Name', required=True)
@classmethod
def __setup__(cls):
super(Breed, cls).__setup__()
t = cls.__table__()
cls._sql_constraints = [
('name_specie_uniq', Unique(t, t.specie, t.name),
'farm.specie_breed_name_unique'),
]
class SpecieFarmLine(ModelSQL, ModelView):
'Managed Farm of Specie'
__name__ = 'farm.specie.farm_line'
specie = fields.Many2One('farm.specie', 'Specie', required=True,
ondelete='CASCADE')
farm = fields.Many2One('stock.location', 'Farm', required=True,
domain=[('type', '=', 'warehouse')])
event_order_sequence = fields.Many2One('ir.sequence',
"Events Orders' Sequence", required=True, domain=[
('sequence_type', '=', Id('farm', 'sequence_type_event_order')),
],
help="Sequence used for the Event Orders in this farm.")
has_male = fields.Boolean('Males', help="In this farm there are males.")
male_sequence = fields.Many2One('ir.sequence', "Males' Sequence",
domain=[
('sequence_type', '=', Id('farm', 'sequence_type_animal')),
],
states=_enabled_STATES('has_male'), depends=['has_male'],
help='Sequence used for male lots and animals.')
semen_lot_sequence = fields.Many2One('ir.sequence',
"Extracted Semen Lots' Sequence", domain=[
('sequence_type', '=', Id('stock_lot', 'sequence_type_stock_lot')),
], states=_enabled_STATES('has_male'), depends=['has_male'])
dose_lot_sequence = fields.Many2One('ir.sequence',
"Semen Dose Lots' Sequence", domain=[
('sequence_type', '=', Id('stock_lot', 'sequence_type_stock_lot')),
], states=_enabled_STATES('has_male'), depends=['has_male'])
has_female = fields.Boolean('Females',
help="In this farm there are females.")
female_sequence = fields.Many2One('ir.sequence', "Females' Sequence",
domain=[
('sequence_type', '=', Id('farm', 'sequence_type_animal')),
],
states=_enabled_STATES('has_female'), depends=['has_female'],
help='Sequence used for female production lots and animals.')
has_individual = fields.Boolean('Individuals',
help="In this farm there are individuals.")
individual_sequence = fields.Many2One('ir.sequence',
"Individuals' Sequence", domain=[
('sequence_type', '=', Id('farm', 'sequence_type_animal')),
],
states=_enabled_STATES('has_individual'), depends=['has_individual'],
help="Sequence used for individual lots and animals.")
has_group = fields.Boolean('Groups',
help="In this farm there are groups.")
group_sequence = fields.Many2One('ir.sequence', "Groups' Sequence",
domain=[
('sequence_type', '=', Id('farm', 'sequence_type_animal_group')),
],
states=_enabled_STATES('has_group'), depends=['has_group'],
help='Sequence used for group production lots and animals.')
@classmethod
def __setup__(cls):
super(SpecieFarmLine, cls).__setup__()
t = cls.__table__()
cls._sql_constraints += [
('specie_farm_uniq', Unique(t, t.specie, t.farm),
'farm.specie_farm_unique'),
]
class SpecieModel(ModelSQL):
'Specie - Model'
__name__ = 'farm.specie-ir.model'
_table = 'farm_specie_ir_model'
specie = fields.Many2One('farm.specie', 'Specie', ondelete='CASCADE',
required=True)
model = fields.Many2One('ir.model', 'Model', required=True)
class UIMenu(metaclass=PoolMeta):
__name__ = 'ir.ui.menu'
specie = fields.Many2One('farm.specie', 'Specie', ondelete='CASCADE')
class ActionActWindow(metaclass=PoolMeta):
__name__ = 'ir.action.act_window'
specie = fields.Many2One('farm.specie', 'Specie', ondelete='CASCADE')
class ActionWizard(metaclass=PoolMeta):
__name__ = 'ir.action.wizard'
specie = fields.Many2One('farm.specie', 'Specie', ondelete='CASCADE')
class SpecieProduct(ModelSQL):
'Specie - Product'
__name__ = 'farm.specie-product.product'
specie = fields.Many2One('farm.specie', 'Specie', ondelete='CASCADE',
required=True)
product = fields.Many2One('product.product', 'Product', required=True)