diff --git a/lims_industry/__init__.py b/lims_industry/__init__.py index 7ea5678b..01e3a4cb 100644 --- a/lims_industry/__init__.py +++ b/lims_industry/__init__.py @@ -34,6 +34,7 @@ def register(): analysis.Analysis, sample.Entry, sample.Sample, + sample.SampleEditionLog, sample.Fraction, sample.FractionType, sample.CreateSampleStart, diff --git a/lims_industry/locale/es.po b/lims_industry/locale/es.po index c59a7728..0a2e873e 100644 --- a/lims_industry/locale/es.po +++ b/lims_industry/locale/es.po @@ -466,6 +466,10 @@ msgctxt "field:lims.sample,component:" msgid "Component" msgstr "Componente" +msgctxt "field:lims.sample,edition_log:" +msgid "Edition log" +msgstr "Registro de cambios" + msgctxt "field:lims.sample,equipment:" msgid "Equipment" msgstr "Equipo" @@ -566,6 +570,10 @@ msgctxt "field:lims.sample.attribute.set,name:" msgid "Name" msgstr "Nombre" +msgctxt "field:lims.sample.edit.start,comercial_product:" +msgid "Comercial Product" +msgstr "Producto comercial" + msgctxt "field:lims.sample.edit.start,component:" msgid "Component" msgstr "Componente" @@ -576,12 +584,32 @@ msgstr "Equipo" msgctxt "field:lims.sample.edit.start,party:" msgid "Party" -msgstr "Tercero" +msgstr "Entidad" msgctxt "field:lims.sample.edit.start,plant:" msgid "Plant" msgstr "Planta" +msgctxt "field:lims.sample.edition.log,create_date2:" +msgid "Created at" +msgstr "Creado el" + +msgctxt "field:lims.sample.edition.log,field:" +msgid "Field" +msgstr "Campo" + +msgctxt "field:lims.sample.edition.log,final_value:" +msgid "Final value" +msgstr "Valor final" + +msgctxt "field:lims.sample.edition.log,initial_value:" +msgid "Initial value" +msgstr "Valor inicial" + +msgctxt "field:lims.sample.edition.log,sample:" +msgid "Sample" +msgstr "Muestra" + msgctxt "field:lims.sampling.type,description:" msgid "Description" msgstr "Descripción" @@ -726,6 +754,16 @@ msgctxt "model:ir.message,text:msg_component_type_unique" msgid "A component of the same type already exists for the equipment" msgstr "Ya existe un componente del mismo tipo para el equipo" +msgctxt "model:ir.message,text:msg_edit_entry_party" +msgid "You must select all samples from the same entry to change the Party" +msgstr "" +"Debe seleccionar todas las muestras del mismo ingreso para cambiar la " +"entidad" + +msgctxt "model:ir.message,text:msg_edit_results_report_party" +msgid "Sample \"%(sample)s\" has a Results report already released" +msgstr "La muestra «%(sample)s» tiene un Informe de resultados ya publicado" + msgctxt "model:ir.message,text:msg_equipment_name_unique" msgid "There is already an equipment with the same name for the plant" msgstr "Ya existe un equipo con el mismo nombre para la planta" @@ -859,6 +897,10 @@ msgctxt "model:lims.sample.edit.start,name:" msgid "Edit Samples" msgstr "Modificar muestras" +msgctxt "model:lims.sample.edition.log,name:" +msgid "Sample Edition Log" +msgstr "Registro de cambios en Muestra" + msgctxt "model:lims.sampling.type,name:" msgid "Sampling Type" msgstr "Muestreo" @@ -871,6 +913,30 @@ msgctxt "selection:lims.configuration,mail_ack_report_grouping:" msgid "Plant" msgstr "Planta" +msgctxt "selection:lims.sample.edition.log,field:" +msgid "Comercial Product" +msgstr "Producto comercial" + +msgctxt "selection:lims.sample.edition.log,field:" +msgid "Component" +msgstr "Componente" + +msgctxt "selection:lims.sample.edition.log,field:" +msgid "Equipment" +msgstr "Equipo" + +msgctxt "selection:lims.sample.edition.log,field:" +msgid "Matrix" +msgstr "Matriz" + +msgctxt "selection:lims.sample.edition.log,field:" +msgid "Party" +msgstr "Entidad" + +msgctxt "selection:lims.sample.edition.log,field:" +msgid "Product type" +msgstr "Tipo de producto" + msgctxt "selection:lims.trend.chart,filter:" msgid "Same Component" msgstr "Mismo Componente" @@ -999,6 +1065,14 @@ msgctxt "view:lims.results_report.version.detail.sample:" msgid "Precedents" msgstr "Antecedentes" +msgctxt "view:lims.sample.edition.log:" +msgid "Time" +msgstr "Hora" + +msgctxt "view:lims.sample:" +msgid "Edition log" +msgstr "Registro de cambios" + msgctxt "view:lims.sample:" msgid "Industry" msgstr "Industria" diff --git a/lims_industry/message.xml b/lims_industry/message.xml index df7f561a..79b90418 100644 --- a/lims_industry/message.xml +++ b/lims_industry/message.xml @@ -49,5 +49,11 @@ There is already a equipment template with the same type, brand and model + + You must select all samples from the same entry to change the Party + + + Sample "%(sample)s" has a Results report already released + diff --git a/lims_industry/sample.py b/lims_industry/sample.py index 688fb14d..62b646e1 100644 --- a/lims_industry/sample.py +++ b/lims_industry/sample.py @@ -2,11 +2,13 @@ # The COPYRIGHT file at the top level of this repository contains # the full copyright notices and license terms. -from trytond.model import ModelView, fields +from trytond.model import ModelSQL, ModelView, fields from trytond.wizard import Wizard, StateTransition, StateView, Button from trytond.pool import Pool, PoolMeta from trytond.pyson import Eval, Bool from trytond.transaction import Transaction +from trytond.exceptions import UserError +from trytond.i18n import gettext class Entry(metaclass=PoolMeta): @@ -64,6 +66,8 @@ class Sample(metaclass=PoolMeta): changed_oil = fields.Boolean('Did change Oil?') changed_oil_filter = fields.Boolean('Did change Oil Filter?') changed_air_filter = fields.Boolean('Did change Air Filter?') + edition_log = fields.One2Many('lims.sample.edition.log', 'sample', + 'Edition log', readonly=True) @classmethod def __setup__(cls): @@ -182,6 +186,48 @@ class Sample(metaclass=PoolMeta): return result +class SampleEditionLog(ModelSQL, ModelView): + 'Sample Edition Log' + __name__ = 'lims.sample.edition.log' + + create_date2 = fields.Function(fields.DateTime('Created at'), + 'get_create_date2', searcher='search_create_date2') + sample = fields.Many2One('lims.sample', 'Sample', required=True, + ondelete='CASCADE', select=True, readonly=True) + field = fields.Selection([ + ('party', 'Party'), + ('equipment', 'Equipment'), + ('component', 'Component'), + ('product_type', 'Product type'), + ('comercial_product', 'Comercial Product'), + ('matrix', 'Matrix'), + ], 'Field', readonly=True) + initial_value = fields.Text('Initial value', readonly=True) + final_value = fields.Text('Final value', readonly=True) + + @classmethod + def __setup__(cls): + super().__setup__() + cls._order.insert(0, ('create_date', 'ASC')) + + def get_create_date2(self, name): + return self.create_date.replace(microsecond=0) + + @classmethod + def search_create_date2(cls, name, clause): + cursor = Transaction().connection.cursor() + operator_ = clause[1:2][0] + cursor.execute('SELECT id ' + 'FROM "' + cls._table + '" ' + 'WHERE create_date' + operator_ + ' %s', + clause[2:3]) + return [('id', 'in', [x[0] for x in cursor.fetchall()])] + + @classmethod + def order_create_date2(cls, tables): + return cls.create_date.convert_order('create_date', tables, cls) + + class Fraction(metaclass=PoolMeta): __name__ = 'lims.fraction' @@ -450,6 +496,13 @@ class EditSampleStart(ModelView): component = fields.Many2One('lims.component', 'Component', domain=[('equipment', '=', Eval('equipment'))], depends=['equipment']) + comercial_product = fields.Many2One('lims.comercial.product', + 'Comercial Product') + + @fields.depends('component') + def on_change_component(self): + if self.component and self.component.comercial_product: + self.comercial_product = self.component.comercial_product.id class EditSample(Wizard): @@ -466,7 +519,8 @@ class EditSample(Wizard): def _get_filtered_samples(self): Sample = Pool().get('lims.sample') samples = Sample.browse(Transaction().context['active_ids']) - return [s for s in samples if s.entry.state == 'draft'] + #return [s for s in samples if s.entry.state == 'draft'] + return samples def default_start(self, fields): samples = self._get_filtered_samples() @@ -482,29 +536,154 @@ class EditSample(Wizard): } def transition_confirm(self): - component_changed = bool(self.start.component) - equipment_changed = bool(self.start.equipment) - party_changed = bool(self.start.party) + SampleEditionLog = Pool().get('lims.sample.edition.log') samples = self._get_filtered_samples() + samples_to_edit_party = [] for sample in samples: - if component_changed: - sample.component = self.start.component.id - if equipment_changed: + check_typifications = False + log = [] + if (self.start.party and + self.start.party != sample.party): + log.append({ + 'sample': sample.id, + 'field': 'party', + 'initial_value': sample.party.rec_name, + 'final_value': self.start.party.rec_name, + }) + samples_to_edit_party.append(sample) + + if (self.start.equipment and + self.start.equipment != sample.equipment): + log.append({ + 'sample': sample.id, + 'field': 'equipment', + 'initial_value': (sample.equipment and + sample.equipment.rec_name or None), + 'final_value': self.start.equipment.rec_name, + }) sample.equipment = self.start.equipment.id - if party_changed: - if self.start.party.id != sample.party.id: - entry = self._new_entry() - sample.entry = entry.id + + if (self.start.component and + self.start.component != sample.component): + log.append({ + 'sample': sample.id, + 'field': 'component', + 'initial_value': (sample.component and + sample.component.rec_name or None), + 'final_value': self.start.component.rec_name, + }) + sample.component = self.start.component.id + if (self.start.component.product_type and + self.start.component.product_type != + sample.product_type): + check_typifications = True + log.append({ + 'sample': sample.id, + 'field': 'product_type', + 'initial_value': sample.product_type.rec_name, + 'final_value': ( + self.start.component.product_type.rec_name), + }) + sample.product_type = self.start.component.product_type.id + + if (self.start.comercial_product and + self.start.comercial_product != sample.comercial_product): + log.append({ + 'sample': sample.id, + 'field': 'comercial_product', + 'initial_value': (sample.comercial_product and + sample.comercial_product.rec_name), + 'final_value': self.start.comercial_product.rec_name, + }) + sample.comercial_product = self.start.comercial_product.id + if (self.start.comercial_product.matrix and + self.start.comercial_product.matrix != + self.start.comercial_product.matrix): + check_typifications = True + log.append({ + 'sample': sample.id, + 'field': 'matrix', + 'initial_value': sample.matrix.rec_name, + 'final_value': ( + self.start.comercial_product.matrix.rec_name), + }) + sample.matrix = self.start.comercial_product.matrix.id + + if check_typifications: + self.check_typifications(sample) + sample.save() + if log: + SampleEditionLog.create(log) + + for sample in samples_to_edit_party: + self.edit_party(sample, samples) + return 'end' - def _new_entry(self): + def edit_party(self, sample, samples): + self._edit_entry_party(sample, samples) + self._edit_results_report_party(sample, samples) + + def _edit_entry_party(self, sample, samples): pool = Pool() + Sample = pool.get('lims.sample') Entry = pool.get('lims.entry') - entry = Entry() + + if Sample.search_count([ + ('entry', '=', sample.entry.id), + ('id', 'not in', [s.id for s in samples]), + ]) > 0: + raise UserError(gettext('lims_industry.msg_edit_entry_party')) + + entry = Entry(sample.entry.id) entry.party = self.start.party.id entry.invoice_party = self.start.party.id - entry.state = 'draft' + entry.ack_report_format = None + entry.ack_report_cache = None entry.save() - return entry + + def _edit_results_report_party(self, sample, samples): + cursor = Transaction().connection.cursor() + pool = Pool() + Fraction = pool.get('lims.fraction') + Notebook = pool.get('lims.notebook') + ResultsSample = pool.get('lims.results_report.version.detail.sample') + ResultsDetail = pool.get('lims.results_report.version.detail') + ResultsVersion = pool.get('lims.results_report.version') + ResultsReport = pool.get('lims.results_report') + + if sample.has_results_report: + raise UserError(gettext( + 'lims_industry.msg_edit_results_report_party', + sample=sample.rec_name)) + + cursor.execute('SELECT rv.results_report ' + 'FROM "' + ResultsVersion._table + '" rv ' + 'INNER JOIN "' + ResultsDetail._table + '" rd ' + 'ON rv.id = rd.report_version ' + 'INNER JOIN "' + ResultsSample._table + '" rs ' + 'ON rd.id = rs.version_detail ' + 'INNER JOIN "' + Notebook._table + '" n ' + 'ON n.id = rs.notebook ' + 'INNER JOIN "' + Fraction._table + '" f ' + 'ON f.id = n.fraction ' + 'WHERE f.sample = %s ' + 'AND rd.state NOT IN (\'released\', \'annulled\')', + (str(sample.id),)) + reports_ids = [x[0] for x in cursor.fetchall()] + if not reports_ids: + return + reports = ResultsReport.browse(reports_ids) + ResultsReport.write(reports, {'party': self.start.party.id}) + + def check_typifications(self, sample): + analysis_domain_ids = sample.on_change_with_analysis_domain() + for f in sample.fractions: + for s in f.services: + if s.analysis.id not in analysis_domain_ids: + raise UserError(gettext('lims.msg_not_typified', + analysis=s.analysis.rec_name, + product_type=sample.product_type.rec_name, + matrix=sample.matrix.rec_name)) diff --git a/lims_industry/sample.xml b/lims_industry/sample.xml index e4bb50b9..e69163da 100644 --- a/lims_industry/sample.xml +++ b/lims_industry/sample.xml @@ -15,6 +15,19 @@ sample_list + + + + lims.sample.edition.log + tree + sample_edition_log_list + + + lims.sample.edition.log + form + sample_edition_log_form + + diff --git a/lims_industry/view/sample_edit_form.xml b/lims_industry/view/sample_edit_form.xml index 83fa2172..614be01e 100644 --- a/lims_industry/view/sample_edit_form.xml +++ b/lims_industry/view/sample_edit_form.xml @@ -8,4 +8,6 @@