diff --git a/lims/__init__.py b/lims/__init__.py index 9dc9a484..be792774 100644 --- a/lims/__init__.py +++ b/lims/__init__.py @@ -42,6 +42,7 @@ def register(): laboratory.LaboratoryProfessional, laboratory.Laboratory, laboratory.LabMethod, + laboratory.LabMethodVersion, laboratory.LabMethodWaitingTime, laboratory.LabDeviceType, laboratory.LabDevice, @@ -309,6 +310,7 @@ def register(): analysis.CreateAnalysisProduct, analysis.OpenAnalysisNotTypified, analysis.UpdateCalculatedTypification, + laboratory.NewLabMethodVersion, laboratory.LabDeviceRelateAnalysis, sample.ManageServices, sample.CompleteServices, diff --git a/lims/entry.py b/lims/entry.py index ddf63065..55d43f3e 100644 --- a/lims/entry.py +++ b/lims/entry.py @@ -1084,6 +1084,8 @@ class EntryDetailAnalysis(ModelSQL, ModelView): states={'readonly': True}) method = fields.Many2One('lims.lab.method', 'Method', states={'readonly': True}) + method_version = fields.Many2One('lims.lab.method.version', + 'Method version', readonly=True) device = fields.Many2One('lims.lab.device', 'Device', states={'readonly': True}) analysis_origin = fields.Char('Analysis origin', @@ -1226,10 +1228,19 @@ class EntryDetailAnalysis(ModelSQL, ModelView): @classmethod def create(cls, vlist): + pool = Pool() + LabMethod = pool.get('lims.lab.method') + vlist = [x.copy() for x in vlist] for values in vlist: values['plannable'] = cls._get_plannable(values) + # set method version + if 'method' in values and values['method'] is not None: + values['method_version'] = LabMethod( + values['method']).get_current_version() + details = super().create(vlist) + cls._set_referable(details) return details @@ -1563,7 +1574,20 @@ class EntryDetailAnalysis(ModelSQL, ModelView): @classmethod def write(cls, *args): + pool = Pool() + LabMethod = pool.get('lims.lab.method') + + actions = iter(args) + args = [] + for details, values in zip(actions, actions): + # set method version + if 'method' in values and values['method'] is not None: + values['method_version'] = LabMethod( + values['method']).get_current_version() + args.extend((details, values)) + super().write(*args) + actions = iter(args) for details, vals in zip(actions, actions): change_cie_data = False diff --git a/lims/laboratory.py b/lims/laboratory.py index d42f5970..b3d088a9 100644 --- a/lims/laboratory.py +++ b/lims/laboratory.py @@ -6,13 +6,15 @@ from datetime import datetime import operator from sql import Cast -from trytond.model import ModelView, ModelSQL, DeactivableMixin, fields, Unique +from trytond.model import Workflow, ModelView, ModelSQL, DeactivableMixin, \ + fields, Unique from trytond.wizard import Wizard, StateTransition, StateView, Button from trytond.pool import Pool from trytond.transaction import Transaction from trytond.pyson import Eval, Bool from trytond.exceptions import UserError from trytond.i18n import gettext +from trytond.rpc import RPC from .formula_parser import FormulaParser @@ -139,24 +141,44 @@ class LaboratoryProfessional(DeactivableMixin, ModelSQL, ModelView): return None -class LabMethod(ModelSQL, ModelView): +class LabMethod(Workflow, ModelSQL, ModelView): 'Laboratory Method' __name__ = 'lims.lab.method' - code = fields.Char('Code', required=True) - name = fields.Char('Name', required=True, translate=True) - reference = fields.Char('Reference') - determination = fields.Char('Determination', required=True) + _states = {'readonly': Eval('state') != 'draft'} + _depends = ['state'] + + code = fields.Char('Code', required=True, + states=_states, depends=_depends) + name = fields.Char('Name', required=True, translate=True, + states=_states, depends=_depends) + version = fields.Char('Version', states=_states, depends=_depends) + reference = fields.Char('Reference', states=_states, depends=_depends) + determination = fields.Char('Determination', required=True, + states=_states, depends=_depends) requalification_months = fields.Integer('Requalification months', - required=True) - supervised_requalification = fields.Boolean('Supervised requalification') - deprecated_since = fields.Date('Deprecated since') - pnt = fields.Char('PNT') + required=True, states=_states, depends=_depends) + supervised_requalification = fields.Boolean('Supervised requalification', + states=_states, depends=_depends) + deprecated_since = fields.Date('Deprecated since', + states=_states, depends=_depends) + pnt = fields.Char('PNT', states=_states, depends=_depends) results_estimated_waiting = fields.Integer( - 'Estimated number of days for results') + 'Estimated number of days for results', + states=_states, depends=_depends) results_waiting = fields.One2Many('lims.lab.method.results_waiting', 'method', 'Waiting times per client') - equivalence_code = fields.Char('Equivalence Code') + equivalence_code = fields.Char('Equivalence Code', + states=_states, depends=_depends) + versions = fields.One2Many('lims.lab.method.version', + 'method', 'Versions', readonly=True) + state = fields.Selection([ + ('draft', 'Draft'), + ('active', 'Active'), + ('disabled', 'Disabled'), + ], 'State', required=True, readonly=True) + + del _states, _depends @classmethod def __setup__(cls): @@ -166,12 +188,33 @@ class LabMethod(ModelSQL, ModelView): ('code_uniq', Unique(t, t.code), 'lims.msg_method_code_unique_id'), ] + cls._transitions |= set(( + ('draft', 'active'), + ('active', 'disabled'), + )) + cls._buttons.update({ + 'activate': { + 'invisible': (Eval('state') != 'draft'), + }, + 'disable': { + 'invisible': (Eval('state') != 'active'), + }, + 'new_version': { + 'invisible': (Eval('state') != 'active'), + }, + }) + cls.__rpc__.update({ + 'activate': RPC(readonly=False, instantiate=0), + }) + + @staticmethod + def default_state(): + return 'draft' def get_rec_name(self, name): if self.code: return self.code + ' - ' + self.name - else: - return self.name + return self.name @classmethod def search_rec_name(cls, name, clause): @@ -223,6 +266,45 @@ class LabMethod(ModelSQL, ModelView): method.results_estimated_waiting), }) + def _get_new_version_fields(self): + return ['code', 'name', 'version', 'reference', 'determination', + 'requalification_months', 'supervised_requalification', + 'deprecated_since', 'pnt', 'results_estimated_waiting', + 'equivalence_code'] + + @classmethod + @ModelView.button_action('lims.wiz_method_new_version') + def new_version(cls, methods): + pass + + @classmethod + @ModelView.button + @Workflow.transition('active') + def activate(cls, methods): + for method in methods: + method.create_new_version() + + @classmethod + @ModelView.button + @Workflow.transition('disabled') + def disable(cls, methods): + pass + + def create_new_version(self): + pool = Pool() + LabMethodVersion = pool.get('lims.lab.method.version') + + version = LabMethodVersion() + version.method = self.id + for field in self._get_new_version_fields(): + setattr(version, field, getattr(self, field)) + version.save() + + def get_current_version(self): + if self.versions: + return self.versions[0].id + return None + class LabMethodWaitingTime(ModelSQL, ModelView): 'Waiting Time per Client' @@ -283,6 +365,76 @@ class LabMethodWaitingTime(ModelSQL, ModelView): super().delete(waiting_times) +class LabMethodVersion(ModelSQL, ModelView): + 'Method Version' + __name__ = 'lims.lab.method.version' + + method = fields.Many2One('lims.lab.method', 'Method', + ondelete='CASCADE', select=True) + code = fields.Char('Code', readonly=True) + name = fields.Char('Name', translate=True, readonly=True) + version = fields.Char('Version', readonly=True) + reference = fields.Char('Reference', readonly=True) + determination = fields.Char('Determination', readonly=True) + requalification_months = fields.Integer('Requalification months', + readonly=True) + supervised_requalification = fields.Boolean('Supervised requalification', + readonly=True) + deprecated_since = fields.Date('Deprecated since', readonly=True) + pnt = fields.Char('PNT', readonly=True) + results_estimated_waiting = fields.Integer( + 'Estimated number of days for results', readonly=True) + equivalence_code = fields.Char('Equivalence Code', readonly=True) + + @classmethod + def __register__(cls, module_name): + LabMethod = Pool().get('lims.lab.method') + super().__register__(module_name) + methods = LabMethod.search([('state', '=', 'draft')]) + LabMethod.activate(methods) + + @classmethod + def __setup__(cls): + super().__setup__() + cls._order.insert(0, ('id', 'DESC')) + + def get_rec_name(self, name): + return self.version + + +class NewLabMethodVersion(Wizard): + 'New Method Version' + __name__ = 'lims.lab.method.new_version' + + start = StateView('lims.lab.method', + 'lims.method_new_version_start_form', [ + Button('Cancel', 'end', 'tryton-cancel'), + Button('Confirm', 'confirm', 'tryton-ok', default=True), + ]) + confirm = StateTransition() + + def default_start(self, fields): + pool = Pool() + LabMethod = pool.get('lims.lab.method') + + method = LabMethod(Transaction().context['active_id']) + default = {'state': 'draft'} + for field in method._get_new_version_fields(): + default[field] = getattr(method, field) + return default + + def transition_confirm(self): + pool = Pool() + LabMethod = pool.get('lims.lab.method') + + method = LabMethod(Transaction().context['active_id']) + for field in method._get_new_version_fields(): + setattr(method, field, getattr(self.start, field)) + method.save() + method.create_new_version() + return 'end' + + class LabDevice(DeactivableMixin, ModelSQL, ModelView): 'Laboratory Device' __name__ = 'lims.lab.device' diff --git a/lims/laboratory.xml b/lims/laboratory.xml index 603ac23a..04ec6421 100644 --- a/lims/laboratory.xml +++ b/lims/laboratory.xml @@ -142,6 +142,20 @@ id="lims_lab_method_menu" parent="lims_config_base_tables" sequence="30"/> + + activate + + + + disable + Are you sure you want to disable the method? + + + + new_version + + + @@ -171,6 +185,32 @@ lab_method_results_waiting_list + + + + lims.lab.method.version + form + lab_method_version_form + + + lims.lab.method.version + tree + lab_method_version_list + + + + + + lims.lab.method + form + lab_method_new_version_form + + + + New Method Version + lims.lab.method.new_version + + diff --git a/lims/locale/es.po b/lims/locale/es.po index 75834f41..00780c17 100644 --- a/lims/locale/es.po +++ b/lims/locale/es.po @@ -1629,6 +1629,10 @@ msgctxt "field:lims.entry.detail.analysis,method:" msgid "Method" msgstr "Método" +msgctxt "field:lims.entry.detail.analysis,method_version:" +msgid "Method version" +msgstr "Versión Método" + msgctxt "field:lims.entry.detail.analysis,party:" msgid "Party" msgstr "Entidad" @@ -2285,10 +2289,22 @@ msgctxt "field:lims.lab.method,results_waiting:" msgid "Waiting times per client" msgstr "Tiempos de respuesta por cliente" +msgctxt "field:lims.lab.method,state:" +msgid "State" +msgstr "Estado" + msgctxt "field:lims.lab.method,supervised_requalification:" msgid "Supervised requalification" msgstr "Recualificación supervisada" +msgctxt "field:lims.lab.method,version:" +msgid "Version" +msgstr "Versión" + +msgctxt "field:lims.lab.method,versions:" +msgid "Versions" +msgstr "Versiones" + msgctxt "field:lims.lab.method.results_waiting,method:" msgid "Method" msgstr "Método" @@ -2301,6 +2317,54 @@ msgctxt "field:lims.lab.method.results_waiting,results_estimated_waiting:" msgid "Estimated number of days for results" msgstr "Cantidad estimada de días para resultados" +msgctxt "field:lims.lab.method.version,code:" +msgid "Code" +msgstr "Código" + +msgctxt "field:lims.lab.method.version,deprecated_since:" +msgid "Deprecated since" +msgstr "Obsoleto desde" + +msgctxt "field:lims.lab.method.version,determination:" +msgid "Determination" +msgstr "Determinación" + +msgctxt "field:lims.lab.method.version,equivalence_code:" +msgid "Equivalence Code" +msgstr "Código de equivalencia" + +msgctxt "field:lims.lab.method.version,method:" +msgid "Method" +msgstr "Método" + +msgctxt "field:lims.lab.method.version,name:" +msgid "Name" +msgstr "Nombre" + +msgctxt "field:lims.lab.method.version,pnt:" +msgid "PNT" +msgstr "PNT" + +msgctxt "field:lims.lab.method.version,reference:" +msgid "Reference" +msgstr "Referencia" + +msgctxt "field:lims.lab.method.version,requalification_months:" +msgid "Requalification months" +msgstr "Meses para recualificación" + +msgctxt "field:lims.lab.method.version,results_estimated_waiting:" +msgid "Estimated number of days for results" +msgstr "Cantidad estimada de días para resultados" + +msgctxt "field:lims.lab.method.version,supervised_requalification:" +msgid "Supervised requalification" +msgstr "Recualificación supervisada" + +msgctxt "field:lims.lab.method.version,version:" +msgid "Version" +msgstr "Versión" + msgctxt "field:lims.lab.professional.method,determination:" msgid "Determination" msgstr "Determinación" @@ -3047,6 +3111,10 @@ msgctxt "field:lims.notebook.line,method_domain:" msgid "Method domain" msgstr "Dominio para Método" +msgctxt "field:lims.notebook.line,method_version:" +msgid "Method version" +msgstr "Versión Método" + msgctxt "field:lims.notebook.line,method_view:" msgid "Method" msgstr "Método" @@ -6036,6 +6104,10 @@ msgctxt "field:lims.service,method_domain:" msgid "Method domain" msgstr "Dominio para Método" +msgctxt "field:lims.service,method_version:" +msgid "Method version" +msgstr "Versión Método" + msgctxt "field:lims.service,method_view:" msgid "Method" msgstr "Método" @@ -7775,6 +7847,10 @@ msgctxt "model:ir.action,name:wiz_lims_unlink_technicians" msgid "Unlink Technicians" msgstr "Desvincular técnicos" +msgctxt "model:ir.action,name:wiz_method_new_version" +msgid "New Method Version" +msgstr "Nueva versión de Método" + msgctxt "model:ir.action,name:wiz_notebook_evaluate_rules" msgid "Evaluate Rules" msgstr "11) Evaluar Reglas de cuaderno" @@ -8845,6 +8921,10 @@ msgctxt "model:ir.model.button,confirm:entry_confirm_button" msgid "Are you sure you want to confirm the entry?" msgstr "¿Está seguro que desea confirmar el ingreso?" +msgctxt "model:ir.model.button,confirm:method_disable_button" +msgid "Are you sure you want to disable the method?" +msgstr "¿Está seguro que desea deshabilitar el método?" + msgctxt "model:ir.model.button,string:entry_pre_assign_sample_button" msgid "Pre-assign sample numbers" msgstr "Preasignar números de muestras" @@ -9681,6 +9761,10 @@ msgctxt "model:lims.lab.method.results_waiting,name:" msgid "Waiting Time per Client" msgstr "Tiempo de respuesta por cliente" +msgctxt "model:lims.lab.method.version,name:" +msgid "Method Version" +msgstr "Versión de Método" + msgctxt "model:lims.lab.professional.method,name:" msgid "Laboratory Professional Method" msgstr "Método de Profesional" @@ -13140,6 +13224,18 @@ msgctxt "selection:lims.fraction,rm_type:" msgid "SLA" msgstr "SLA" +msgctxt "selection:lims.lab.method,state:" +msgid "Active" +msgstr "Activo" + +msgctxt "selection:lims.lab.method,state:" +msgid "Disabled" +msgstr "Deshabilitado" + +msgctxt "selection:lims.lab.method,state:" +msgid "Draft" +msgstr "Borrador" + msgctxt "selection:lims.lab.professional.method,state:" msgid "Qualified" msgstr "Cualificado" @@ -14171,6 +14267,18 @@ msgctxt "view:lims.lab.device:" msgid "Laboratories" msgstr "Laboratorios" +msgctxt "view:lims.lab.method:" +msgid "Activate" +msgstr "Activar" + +msgctxt "view:lims.lab.method:" +msgid "Disable" +msgstr "Dar de baja" + +msgctxt "view:lims.lab.method:" +msgid "New Version" +msgstr "Nueva versión" + msgctxt "view:lims.lab.method:" msgid "Times" msgstr "Tiempos" @@ -15268,6 +15376,14 @@ msgctxt "wizard_button:lims.lab.device.relate_analysis,start,relate:" msgid "Relate" msgstr "Relacionar" +msgctxt "wizard_button:lims.lab.method.new_version,start,confirm:" +msgid "Confirm" +msgstr "Confirmar" + +msgctxt "wizard_button:lims.lab.method.new_version,start,end:" +msgid "Cancel" +msgstr "Cancelar" + msgctxt "wizard_button:lims.manage_services,send_ack_of_receipt,end:" msgid "Cancel" msgstr "Cancelar" diff --git a/lims/notebook.py b/lims/notebook.py index 5abaf44c..6396ddb8 100644 --- a/lims/notebook.py +++ b/lims/notebook.py @@ -844,6 +844,8 @@ class NotebookLine(ModelSQL, ModelView): method_domain = fields.Function(fields.Many2Many('lims.lab.method', None, None, 'Method domain'), 'on_change_with_method_domain') + method_version = fields.Many2One('lims.lab.method.version', + 'Method version', readonly=True) device = fields.Many2One('lims.lab.device', 'Device', states=_states, domain=['OR', ('id', '=', Eval('device')), @@ -1088,8 +1090,19 @@ class NotebookLine(ModelSQL, ModelView): @classmethod def create(cls, vlist): - Sample = Pool().get('lims.sample') + pool = Pool() + LabMethod = pool.get('lims.lab.method') + Sample = pool.get('lims.sample') + + vlist = [x.copy() for x in vlist] + for values in vlist: + # set method version + if 'method' in values and values['method'] is not None: + values['method_version'] = LabMethod( + values['method']).get_current_version() + lines = super().create(vlist) + cls.update_detail_report(lines) sample_ids = list(set(nl.sample.id for nl in lines)) Sample.update_samples_state(sample_ids) @@ -1097,8 +1110,21 @@ class NotebookLine(ModelSQL, ModelView): @classmethod def write(cls, *args): - Sample = Pool().get('lims.sample') + pool = Pool() + LabMethod = pool.get('lims.lab.method') + Sample = pool.get('lims.sample') + + actions = iter(args) + args = [] + for lines, values in zip(actions, actions): + # set method version + if 'method' in values and values['method'] is not None: + values['method_version'] = LabMethod( + values['method']).get_current_version() + args.extend((lines, values)) + super().write(*args) + actions = iter(args) for lines, vals in zip(actions, actions): if vals.get('not_accepted_message'): diff --git a/lims/sample.py b/lims/sample.py index 26ab8947..a978e42b 100644 --- a/lims/sample.py +++ b/lims/sample.py @@ -384,6 +384,8 @@ class Service(ModelSQL, ModelView): 'Method'), 'get_views_field') method_domain = fields.Function(fields.Many2Many('lims.lab.method', None, None, 'Method domain'), 'on_change_with_method_domain') + method_version = fields.Many2One('lims.lab.method.version', + 'Method version', readonly=True) device = fields.Many2One('lims.lab.device', 'Device', domain=['OR', ('id', '=', Eval('device')), ('id', 'in', Eval('device_domain'))], @@ -519,6 +521,8 @@ class Service(ModelSQL, ModelView): def create(cls, vlist): pool = Pool() LabWorkYear = pool.get('lims.lab.workyear') + Sequence = pool.get('ir.sequence') + LabMethod = pool.get('lims.lab.method') EntryDetailAnalysis = pool.get('lims.entry.detail.analysis') Sample = pool.get('lims.sample') @@ -533,6 +537,11 @@ class Service(ModelSQL, ModelView): cls.check_duplicated_analysis(vlist) for values in vlist: values['number'] = sequence.get() + # set method version + if 'method' in values and values['method'] is not None: + values['method_version'] = LabMethod( + values['method']).get_current_version() + services = super().create(vlist) if not Transaction().context.get('copying', False): @@ -567,8 +576,21 @@ class Service(ModelSQL, ModelView): @classmethod def write(cls, *args): - Sample = Pool().get('lims.sample') + pool = Pool() + LabMethod = pool.get('lims.lab.method') + Sample = pool.get('lims.sample') + + actions = iter(args) + args = [] + for services, values in zip(actions, actions): + # set method version + if 'method' in values and values['method'] is not None: + values['method_version'] = LabMethod( + values['method']).get_current_version() + args.extend((services, values)) + super().write(*args) + actions = iter(args) for services, vals in zip(actions, actions): if vals.get('not_divided_message'): diff --git a/lims/view/lab_method_form.xml b/lims/view/lab_method_form.xml index b659922a..1a3f5d4a 100644 --- a/lims/view/lab_method_form.xml +++ b/lims/view/lab_method_form.xml @@ -3,7 +3,9 @@