# 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:]), ]