kalenislims/lims_industry/industry.py

666 lines
23 KiB
Python

# This file is part of lims_industry module for Tryton.
# The COPYRIGHT file at the top level of this repository contains
# the full copyright notices and license terms.
from trytond.model import ModelSQL, ModelView, DeactivableMixin, fields, Unique
from trytond.pool import Pool
from trytond.pyson import Eval, If
from trytond.transaction import Transaction
from trytond.exceptions import UserError
from trytond.i18n import gettext
from trytond import backend
class Plant(ModelSQL, ModelView):
'Plant'
__name__ = 'lims.plant'
party = fields.Many2One('party.party', 'Party', required=True,
ondelete='CASCADE', select=True)
name = fields.Char('Name', required=True)
street = fields.Char('Street', required=True)
postal_code = fields.Char('Postal Code', required=True)
city = fields.Char('City', required=True)
subdivision = fields.Many2One('country.subdivision',
'Subdivision', required=True, domain=[
('country', '=', Eval('country', -1)),
('parent', '=', None),
],
depends=['country'])
country = fields.Many2One('country.country', 'Country',
required=True)
equipments = fields.One2Many('lims.equipment', 'plant',
'Equipments')
contacts = fields.One2Many('party.address', 'plant',
'Contacts', domain=[('party', '=', Eval('party'))],
depends=['party'])
invoice_party = fields.Many2One('party.party', 'Invoice Party')
latitude = fields.Numeric('Latitude', digits=(3, 14))
longitude = fields.Numeric('Longitude', digits=(4, 14))
@classmethod
def __setup__(cls):
super().__setup__()
cls._order.insert(0, ('party', 'ASC'))
cls._order.insert(1, ('name', 'ASC'))
t = cls.__table__()
cls._sql_constraints = [
('name_unique', Unique(t, t.party, t.name),
'lims_industry.msg_plant_name_unique'),
]
@classmethod
def __register__(cls, module_name):
table = cls.__table_handler__(module_name)
table.column_rename('zip', 'postal_code')
super().__register__(module_name)
@staticmethod
def default_country():
Company = Pool().get('company.company')
company_id = Transaction().context.get('company')
if company_id:
address = Company(company_id).party.address_get()
if address and address.country:
return address.country.id
def get_rec_name(self, name):
res = '%s [%s]' % (self.name, self.party.name)
return res
@classmethod
def copy(cls, records, default=None):
if default is None:
default = {}
current_default = default.copy()
current_default['equipments'] = None
new_records = []
for record in records:
current_default['name'] = '%s (copy)' % record.name
new_record, = super().copy([record], default=current_default)
new_records.append(new_record)
return new_records
class EquipmentType(ModelSQL, ModelView):
'Equipment Type'
__name__ = 'lims.equipment.type'
name = fields.Char('Name', required=True)
@classmethod
def __setup__(cls):
super().__setup__()
t = cls.__table__()
cls._sql_constraints = [
('name_unique', Unique(t, t.name),
'lims_industry.msg_equipment_type_name_unique'),
]
@classmethod
def copy(cls, records, default=None):
if default is None:
default = {}
current_default = default.copy()
new_records = []
for record in records:
current_default['name'] = '%s (copy)' % record.name
new_record, = super().copy([record], default=current_default)
new_records.append(new_record)
return new_records
class Brand(ModelSQL, ModelView):
'Brand'
__name__ = 'lims.brand'
name = fields.Char('Name', required=True)
@classmethod
def __setup__(cls):
super().__setup__()
t = cls.__table__()
cls._sql_constraints = [
('name_unique', Unique(t, t.name),
'lims_industry.msg_brand_name_unique'),
]
@classmethod
def copy(cls, records, default=None):
if default is None:
default = {}
current_default = default.copy()
new_records = []
for record in records:
current_default['name'] = '%s (copy)' % record.name
new_record, = super().copy([record], default=current_default)
new_records.append(new_record)
return new_records
class ComponentKind(ModelSQL, ModelView):
'Component Kind'
__name__ = 'lims.component.kind'
name = fields.Char('Name', required=True)
product_type = fields.Many2One('lims.product.type', 'Product type',
required=True)
@classmethod
def __setup__(cls):
super().__setup__()
t = cls.__table__()
cls._sql_constraints = [
('name_unique', Unique(t, t.name),
'lims_industry.msg_component_kind_name_unique'),
]
@classmethod
def copy(cls, records, default=None):
if default is None:
default = {}
current_default = default.copy()
new_records = []
for record in records:
current_default['name'] = '%s (copy)' % record.name
new_record, = super().copy([record], default=current_default)
new_records.append(new_record)
return new_records
class ComponentLocation(ModelSQL, ModelView):
'Component Location'
__name__ = 'lims.component.location'
name = fields.Char('Name', required=True)
class ComponentType(ModelSQL, ModelView):
'Component Type'
__name__ = 'lims.component.type'
name = fields.Char('Name', required=True)
product_type = fields.Many2One('lims.product.type', 'Product type',
required=True)
kind = fields.Many2One('lims.component.kind', 'Kind')
location = fields.Many2One('lims.component.location', 'Location')
@classmethod
def __setup__(cls):
super().__setup__()
t = cls.__table__()
cls._sql_constraints = [
('name_unique', Unique(t, t.name),
'lims_industry.msg_component_type_name_unique'),
]
@classmethod
def copy(cls, records, default=None):
if default is None:
default = {}
current_default = default.copy()
new_records = []
for record in records:
current_default['name'] = '%s (copy)' % record.name
new_record, = super().copy([record], default=current_default)
new_records.append(new_record)
return new_records
class EquipmentTemplate(ModelSQL, ModelView):
'Equipment Template'
__name__ = 'lims.equipment.template'
type = fields.Many2One('lims.equipment.type', 'Type', required=True)
brand = fields.Many2One('lims.brand', 'Brand', required=True)
model = fields.Char('Model')
power = fields.Char('Power')
component_kinds = fields.One2Many(
'lims.equipment.template-component.kind',
'template', 'Component kinds')
@classmethod
def __setup__(cls):
super().__setup__()
cls._order.insert(0, ('type', 'ASC'))
cls._order.insert(1, ('brand', 'ASC'))
cls._order.insert(2, ('model', 'ASC'))
t = cls.__table__()
cls._sql_constraints = [
('type_brand_model_unique', Unique(t, t.type, t.brand, t.model),
'lims_industry.msg_equipment_template_unique'),
]
def get_rec_name(self, name):
res = '%s - %s' % (self.type.rec_name, self.brand.rec_name)
if self.model:
res += ' - ' + self.model
return res
@classmethod
def search_rec_name(cls, name, clause):
return ['OR',
('type.name',) + tuple(clause[1:]),
('brand.name',) + tuple(clause[1:]),
('model',) + tuple(clause[1:]),
]
@classmethod
def copy(cls, records, default=None):
if default is None:
default = {}
current_default = default.copy()
new_records = []
for record in records:
current_default['model'] = '%s (copy)' % record.model
new_record, = super().copy([record], default=current_default)
new_records.append(new_record)
return new_records
class EquipmentTemplateComponentKind(ModelSQL, ModelView):
'Equipment Template - Component Kind'
__name__ = 'lims.equipment.template-component.kind'
_table = 'lims_equipment_template_component_kind'
template = fields.Many2One('lims.equipment.template', 'Template',
required=True, ondelete='CASCADE', select=True)
kind = fields.Many2One('lims.component.kind', 'Kind',
required=True, ondelete='CASCADE', select=True)
location = fields.Many2One('lims.component.location', 'Location')
@classmethod
def __register__(cls, module_name):
cursor = Transaction().connection.cursor()
TableHandler = backend.TableHandler
sql_table = cls.__table__()
super().__register__(module_name)
old_table_name = 'lims_equipment_template_component_type'
if TableHandler.table_exist(old_table_name):
cursor.execute('SELECT etct.template, ct.kind, ct.location '
'FROM lims_equipment_template_component_type etct '
'INNER JOIN lims_component_type ct '
'ON ct.id = etct.type '
'WHERE ct.kind IS NOT NULL')
res = cursor.fetchall()
if res:
cursor.execute(*sql_table.insert(
columns=[sql_table.template, sql_table.kind,
sql_table.location],
values=[[x[0], x[1], x[2]] for x in res]))
TableHandler.drop_table('', old_table_name)
class Equipment(DeactivableMixin, ModelSQL, ModelView):
'Equipment'
__name__ = 'lims.equipment'
template = fields.Many2One('lims.equipment.template', 'Template',
required=True)
name = fields.Char('Name', required=True)
type = fields.Function(fields.Many2One('lims.equipment.type', 'Type'),
'get_type', searcher='search_type')
brand = fields.Function(fields.Many2One('lims.brand', 'Brand'),
'get_brand', searcher='search_brand')
model = fields.Char('Model', required=True)
power = fields.Char('Power')
voltage = fields.Char('Primary Voltage')
voltage_secondary = fields.Char('Secondary Voltage')
amperage = fields.Char('Secondary Amperage')
serial_number = fields.Char('Serial number')
internal_id = fields.Char('Internal ID Code')
latitude = fields.Numeric('Latitude', digits=(3, 14))
longitude = fields.Numeric('Longitude', digits=(4, 14))
plant = fields.Many2One('lims.plant', 'Plant',
required=True, select=True,
domain=[If(Eval('context', {}).contains('party'),
('party', '=', Eval('context', {}).get('party', -1)),
())])
components = fields.One2Many('lims.component', 'equipment',
'Components')
year_manufacturing = fields.Integer('Year of manufacturing')
year_service_start = fields.Integer('Year of service start')
internal_location = fields.Char('Internal location')
contacts = fields.One2Many('party.address', 'equipment',
'Contacts', domain=[('party', '=', Eval('party'))],
context={'plant': Eval('plant')},
depends=['party', 'plant'])
party = fields.Function(fields.Many2One('party.party', 'Party'),
'get_party', searcher='search_party')
missing_data = fields.Boolean('Missing data')
@classmethod
def __setup__(cls):
super().__setup__()
cls._order.insert(0, ('template', 'ASC'))
cls._order.insert(1, ('name', 'ASC'))
t = cls.__table__()
cls._sql_constraints = [
('name_unique', Unique(t, t.plant, t.name),
'lims_industry.msg_equipment_name_unique'),
]
@classmethod
def create(cls, vlist):
TaskTemplate = Pool().get('lims.administrative.task.template')
equipments = super().create(vlist)
TaskTemplate.create_tasks('equipment_missing_data',
cls._for_task_missing_data(equipments))
return equipments
@classmethod
def _for_task_missing_data(cls, equipments):
AdministrativeTask = Pool().get('lims.administrative.task')
res = []
for equipment in equipments:
if not equipment.missing_data:
continue
if AdministrativeTask.search([
('type', '=', 'equipment_missing_data'),
('origin', '=', '%s,%s' % (cls.__name__, equipment.id)),
('state', 'not in', ('done', 'discarded')),
]):
continue
res.append(equipment)
return res
def get_rec_name(self, name):
res = '%s [%s]' % (self.name, self.plant.name)
return res
@classmethod
def search_rec_name(cls, name, clause):
return ['OR',
('name',) + tuple(clause[1:]),
('serial_number',) + tuple(clause[1:]),
('brand.name',) + tuple(clause[1:]),
('plant.name',) + tuple(clause[1:]),
('components.customer_description',) + tuple(clause[1:]),
]
@fields.depends('plant', '_parent_plant.party')
def on_change_with_party(self, name=None):
return self.get_party([self], name)[self.id]
@classmethod
def get_party(cls, equipments, name):
result = {}
for e in equipments:
result[e.id] = e.plant and e.plant.party.id or None
return result
@classmethod
def search_party(cls, name, clause):
return [('plant.party',) + tuple(clause[1:])]
@fields.depends('template', '_parent_template.type')
def on_change_with_type(self, name=None):
return self.get_type([self], name)[self.id]
@classmethod
def get_type(cls, equipments, name):
result = {}
for e in equipments:
result[e.id] = e.template and e.template.type.id or None
return result
@classmethod
def search_type(cls, name, clause):
return [('template.type',) + tuple(clause[1:])]
@fields.depends('template', '_parent_template.brand')
def on_change_with_brand(self, name=None):
return self.get_brand([self], name)[self.id]
@classmethod
def get_brand(cls, equipments, name):
result = {}
for e in equipments:
result[e.id] = e.template and e.template.brand.id or None
return result
@classmethod
def search_brand(cls, name, clause):
return [('template.brand',) + tuple(clause[1:])]
@fields.depends('template', 'components')
def on_change_template(self):
pool = Pool()
Component = pool.get('lims.component')
if not self.template:
return
current_components_ids = [(component.kind.id,
component.location and component.location.id or None)
for component in self.components]
components = list(self.components)
for record in self.template.component_kinds:
kind_id = record.kind.id
location_id = record.location and record.location.id or None
if (kind_id, location_id) in current_components_ids:
continue
value = Component(**Component.default_get(
list(Component._fields.keys()), with_rec_name=False))
value.kind = kind_id
value.location = location_id
components.append(value)
self.model = self.template.model
self.power = self.template.power
self.components = components
@classmethod
def copy(cls, records, default=None):
if default is None:
default = {}
current_default = default.copy()
new_records = []
for record in records:
current_default['name'] = '%s (copy)' % record.name
new_record, = super().copy([record], default=current_default)
new_records.append(new_record)
return new_records
class Component(ModelSQL, ModelView):
'Component'
__name__ = 'lims.component'
equipment = fields.Many2One('lims.equipment', 'Equipment',
required=True, ondelete='CASCADE', select=True)
kind = fields.Many2One('lims.component.kind', 'Kind', required=True)
location = fields.Many2One('lims.component.location', 'Location')
product_type = fields.Function(fields.Many2One('lims.product.type',
'Product type'), 'get_product_type')
comercial_product = fields.Many2One('lims.comercial.product',
'Comercial product')
capacity = fields.Char('Capacity (lts)')
serial_number = fields.Char('Serial number')
model = fields.Char('Model')
power = fields.Char('Power')
brand = fields.Many2One('lims.brand', 'Brand')
internal_id = fields.Char('Internal ID Code')
customer_description = fields.Char('Customer description')
year_manufacturing = fields.Integer('Year of manufacturing')
plant = fields.Function(fields.Many2One('lims.plant', 'Plant'),
'get_plant', searcher='search_plant')
party = fields.Function(fields.Many2One('party.party', 'Party'),
'get_party', searcher='search_party')
missing_data = fields.Boolean('Missing data')
@classmethod
def __setup__(cls):
super().__setup__()
cls._order.insert(0, ('equipment', 'ASC'))
cls._order.insert(1, ('kind', 'ASC'))
t = cls.__table__()
cls._sql_constraints = [
('kind_location_description_unique', Unique(t, t.equipment,
t.kind, t.location, t.customer_description),
'lims_industry.msg_component_unique'),
]
@classmethod
def __register__(cls, module_name):
table_h = cls.__table_handler__(module_name)
type_exist = table_h.column_exist('type')
super().__register__(module_name)
if type_exist:
cursor = Transaction().connection.cursor()
ComponentType = Pool().get('lims.component.type')
cursor.execute('UPDATE "' + cls._table + '" c '
'SET kind = ct.kind, location = ct.location '
'FROM "' + ComponentType._table + '" ct '
'WHERE ct.id = c.type')
table_h.drop_constraint('type_unique')
table_h.drop_constraint('type_description_unique')
table_h.drop_column('type')
@classmethod
def create(cls, vlist):
TaskTemplate = Pool().get('lims.administrative.task.template')
components = super().create(vlist)
TaskTemplate.create_tasks('component_missing_data',
cls._for_task_missing_data(components))
return components
@classmethod
def _for_task_missing_data(cls, components):
AdministrativeTask = Pool().get('lims.administrative.task')
res = []
for component in components:
if not component.missing_data:
continue
if AdministrativeTask.search([
('type', '=', 'component_missing_data'),
('origin', '=', '%s,%s' % (cls.__name__, component.id)),
('state', 'not in', ('done', 'discarded')),
]):
continue
res.append(component)
return res
@classmethod
def delete(cls, components):
cls.check_delete(components)
super().delete(components)
@classmethod
def check_delete(cls, components):
Sample = Pool().get('lims.sample')
for component in components:
samples = Sample.search_count([
('component', '=', component.id),
])
if samples != 0:
raise UserError(gettext('lims_industry.msg_delete_component',
component=component.get_rec_name(None)))
@classmethod
def copy(cls, records, default=None):
if default is None:
default = {}
current_default = default.copy()
new_records = []
for record in records:
current_default['customer_description'] = '%s (copy)' % (
record.customer_description)
new_record, = super().copy([record], default=current_default)
new_records.append(new_record)
return new_records
def get_rec_name(self, name):
res = self.kind.rec_name
if self.location:
res += ' ' + self.location.name
if self.brand:
res += ' - ' + self.brand.rec_name
if self.model:
res += ' - ' + self.model
if self.customer_description:
res += ' [' + self.customer_description + ']'
return res
@classmethod
def search_rec_name(cls, name, clause):
return ['OR',
('kind.name',) + tuple(clause[1:]),
('location.name',) + tuple(clause[1:]),
('brand.name',) + tuple(clause[1:]),
('model',) + tuple(clause[1:]),
('customer_description',) + tuple(clause[1:]),
]
@classmethod
def get_plant(cls, component, name):
result = {}
for c in component:
result[c.id] = c.equipment and c.equipment.plant.id or None
return result
@classmethod
def search_plant(cls, name, clause):
return [('equipment.plant',) + tuple(clause[1:])]
@classmethod
def get_party(cls, component, name):
result = {}
for c in component:
result[c.id] = c.equipment and c.equipment.plant.party.id or None
return result
@classmethod
def search_party(cls, name, clause):
return [('equipment.plant.party',) + tuple(clause[1:])]
@fields.depends('kind', '_parent_kind.product_type')
def on_change_with_product_type(self, name=None):
return self.get_product_type([self], name)[self.id]
@classmethod
def get_product_type(cls, components, name):
result = {}
for c in components:
result[c.id] = c.kind and c.kind.product_type.id or None
return result
class ComercialProductBrand(ModelSQL, ModelView):
'Comercial Product Brand'
__name__ = 'lims.comercial.product.brand'
name = fields.Char('Name', required=True)
class ComercialProduct(ModelSQL, ModelView):
'Comercial Product'
__name__ = 'lims.comercial.product'
name = fields.Char('Name', required=True)
brand = fields.Many2One('lims.comercial.product.brand', 'Brand',
required=True)
matrix = fields.Many2One('lims.matrix', 'Base/Matrix', required=True)
dangerous = fields.Boolean('Dangerous')
@staticmethod
def default_dangerous():
return False
def get_rec_name(self, name):
res = '%s %s' % (self.brand.name, self.name)
return res
@classmethod
def search_rec_name(cls, name, clause):
return ['OR',
('name',) + tuple(clause[1:]),
('brand.name',) + tuple(clause[1:]),
]