lims, lims_administrative_task, lims_diagnosis: multiple signatures on

results reports
This commit is contained in:
Adrián Bernardi 2022-08-12 10:16:47 -03:00
parent 76a5984fa6
commit 71028e260c
24 changed files with 1181 additions and 1022 deletions

View File

@ -93,6 +93,7 @@ def register():
results_report.ResultsReportVersionDetail,
results_report.ResultsReportCachedReport,
results_report.ResultsReportComment,
results_report.ResultsReportVersionDetailSigner,
results_report.ResultsReportVersionDetailSample,
results_report.ResultsReportVersionDetailLine,
certification.AnalysisFamily,

View File

@ -4,6 +4,8 @@
# the full copyright notices and license terms.
from trytond.model import ModelView, ModelSQL, fields
from trytond.pool import Pool
from trytond.transaction import Transaction
from trytond.exceptions import UserError
from trytond.i18n import gettext
@ -24,6 +26,35 @@ class Department(ModelSQL, ModelView):
headquarters = fields.Many2One('company.headquarters', 'Headquarters')
default_location = fields.Many2One('stock.location', 'Default Location',
domain=[('type', '=', 'storage')])
responsible = fields.Many2One('res.user', 'Responsible User')
laboratory_professional = fields.Function(fields.Many2One(
'lims.laboratory.professional', 'Laboratory professional'),
'on_change_with_laboratory_professional')
@fields.depends('responsible')
def on_change_with_laboratory_professional(self, name=None):
cursor = Transaction().connection.cursor()
pool = Pool()
LaboratoryProfessional = pool.get('lims.laboratory.professional')
if not self.responsible:
return None
cursor.execute('SELECT id '
'FROM party_party '
'WHERE is_lab_professional = true '
'AND lims_user = %s '
'LIMIT 1', (self.responsible.id,))
party_id = cursor.fetchone()
if not party_id:
return None
cursor.execute('SELECT id '
'FROM "' + LaboratoryProfessional._table + '" '
'WHERE party = %s '
'LIMIT 1', (party_id[0],))
lab_professional_id = cursor.fetchone()
if not lab_professional_id:
return None
return lab_professional_id[0]
class UserDepartment(ModelSQL, ModelView):

View File

@ -27,6 +27,8 @@ class Laboratory(ModelSQL, ModelView):
'lims.laboratory.professional', 'Default professional')
default_signer = fields.Many2One('lims.laboratory.professional',
'Default signer', required=True)
default_manager = fields.Many2One('lims.laboratory.professional',
'Default manager')
related_location = fields.Many2One('stock.location', 'Related location',
required=True, domain=[('type', '=', 'storage')])
cv_corrections = fields.One2Many('lims.laboratory.cv_correction',

View File

@ -22,10 +22,18 @@ msgctxt "field:company.department,headquarters:"
msgid "Headquarters"
msgstr "Sede"
msgctxt "field:company.department,laboratory_professional:"
msgid "Laboratory professional"
msgstr "Profesional de laboratorio"
msgctxt "field:company.department,name:"
msgid "Name"
msgstr "Nombre"
msgctxt "field:company.department,responsible:"
msgid "Responsible User"
msgstr "Usuario responsable"
msgctxt "field:company.headquarters,name:"
msgid "Name"
msgstr "Nombre"
@ -2418,6 +2426,10 @@ msgctxt "field:lims.laboratory,default_laboratory_professional:"
msgid "Default professional"
msgstr "Profesional por defecto"
msgctxt "field:lims.laboratory,default_manager:"
msgid "Default manager"
msgstr "Gerente por defecto"
msgctxt "field:lims.laboratory,default_signer:"
msgid "Default signer"
msgstr "Firmante por defecto"
@ -5302,13 +5314,9 @@ msgctxt "field:lims.results_report.version.detail,samples_list:"
msgid "Samples"
msgstr "Muestras"
msgctxt "field:lims.results_report.version.detail,signer:"
msgid "Signer"
msgstr "Firmante"
msgctxt "field:lims.results_report.version.detail,signer_domain:"
msgid "Signer domain"
msgstr "Dominio para Firmante"
msgctxt "field:lims.results_report.version.detail,signatories:"
msgid "Signatories"
msgstr "Firmantes"
msgctxt "field:lims.results_report.version.detail,state:"
msgid "State"
@ -5491,6 +5499,22 @@ msgctxt "field:lims.results_report.version.detail.sample,version_detail:"
msgid "Report Detail"
msgstr "Detalle de informe"
msgctxt "field:lims.results_report.version.detail.signer,professional:"
msgid "Professional"
msgstr "Profesional"
msgctxt "field:lims.results_report.version.detail.signer,professional_domain:"
msgid "Professional domain"
msgstr "Dominio para Profesional"
msgctxt "field:lims.results_report.version.detail.signer,type:"
msgid "Type"
msgstr "Tipo"
msgctxt "field:lims.results_report.version.detail.signer,version_detail:"
msgid "Report Detail"
msgstr "Detalle de informe"
msgctxt "field:lims.results_report_annulation.start,annulment_reason:"
msgid "Annulment reason"
msgstr "Motivo de anulación"
@ -10041,6 +10065,10 @@ msgctxt "model:lims.results_report.version.detail.sample,name:"
msgid "Results Report Version Detail Sample"
msgstr "Muestra de detalle de versión de informe de resultados"
msgctxt "model:lims.results_report.version.detail.signer,name:"
msgid "Results Report Version Detail Signer"
msgstr "Firmante de detalle de versión de informe de resultados"
msgctxt "model:lims.results_report_annulation.start,name:"
msgid "Report Annulation"
msgstr "Anulación de Informe"
@ -13649,6 +13677,18 @@ msgctxt "selection:lims.results_report.version.detail.new_version.start,type:"
msgid "Preliminary"
msgstr "Preliminar"
msgctxt "selection:lims.results_report.version.detail.signer,type:"
msgid "Manager"
msgstr "Gerente"
msgctxt "selection:lims.results_report.version.detail.signer,type:"
msgid "Responsible"
msgstr "Responsable"
msgctxt "selection:lims.results_report.version.detail.signer,type:"
msgid "Signer"
msgstr "Firmante"
msgctxt "selection:lims.rule,action:"
msgid "Add Analysis"
msgstr "Añadir análisis"

File diff suppressed because it is too large Load Diff

View File

@ -5,10 +5,10 @@
from io import BytesIO
from datetime import datetime
from PyPDF2 import PdfFileMerger
from sql import Literal
from sql import Literal, Null
from trytond.model import Workflow, ModelView, ModelSQL, fields, \
sequence_ordered, Unique
from trytond.model import (Workflow, ModelView, ModelSQL, Unique, fields,
sequence_ordered)
from trytond.wizard import Wizard, StateTransition, StateView, StateAction, \
StateReport, Button
from trytond.pool import Pool
@ -639,12 +639,8 @@ class ResultsReportVersionDetail(Workflow, ModelSQL, ModelView):
'get_report_field', searcher='search_report_field')
invoice_party = fields.Function(fields.Many2One('party.party',
'Invoice party'), 'get_entry_field', searcher='search_entry_field')
signer = fields.Many2One('lims.laboratory.professional', 'Signer',
domain=[('id', 'in', Eval('signer_domain'))],
states=_states, depends=['state', 'signer_domain'])
signer_domain = fields.Function(fields.Many2Many(
'lims.laboratory.professional', None, None, 'Signer domain'),
'on_change_with_signer_domain')
signatories = fields.One2Many('lims.results_report.version.detail.signer',
'version_detail', 'Signatories', states=_states, depends=_depends)
resultrange_origin = fields.Many2One('lims.range.type', 'Origin',
domain=['OR', ('id', '=', Eval('resultrange_origin')),
('id', 'in', Eval('resultrange_origin_domain'))],
@ -1032,27 +1028,6 @@ class ResultsReportVersionDetail(Workflow, ModelSQL, ModelView):
if ranges:
self.resultrange_origin = ranges[0].id
@fields.depends('laboratory')
def on_change_with_signer_domain(self, name=None):
pool = Pool()
UserLaboratory = pool.get('lims.user-laboratory')
LaboratoryProfessional = pool.get('lims.laboratory.professional')
if not self.laboratory:
return []
users = UserLaboratory.search([
('laboratory', '=', self.laboratory.id),
])
if not users:
return []
professionals = LaboratoryProfessional.search([
('party.lims_user', 'in', [u.user.id for u in users]),
('role', '!=', ''),
])
if not professionals:
return []
return [p.id for p in professionals]
@fields.depends('samples')
def on_change_with_resultrange_origin_domain(self, name=None):
cursor = Transaction().connection.cursor()
@ -1259,8 +1234,12 @@ class ResultsReportVersionDetail(Workflow, ModelSQL, ModelView):
detail_default['report_type_forced'] = detail.report_type_forced
detail_default['report_result_type_forced'] = (
detail.report_result_type_forced)
if detail.signer:
detail_default['signer'] = detail.signer.id
if detail.signatories:
detail_default['signatories'] = [('create', [{
'sequence': c.sequence,
'type': c.type,
'professional': c.professional.id,
} for c in detail.signatories])]
if detail.resultrange_origin:
detail_default['resultrange_origin'] = detail.resultrange_origin.id
detail_default['comments'] = str(detail.comments or '')
@ -1583,7 +1562,7 @@ class ResultsReportVersionDetail(Workflow, ModelSQL, ModelView):
@classmethod
def _get_fields_not_overwrite(cls):
fields = ['type', 'signer', 'samples']
fields = ['type', 'signatories', 'samples']
return fields
@classmethod
@ -1786,6 +1765,92 @@ class ResultsReportComment(ModelSQL):
], res))
class ResultsReportVersionDetailSigner(sequence_ordered(),
ModelSQL, ModelView):
'Results Report Version Detail Signer'
__name__ = 'lims.results_report.version.detail.signer'
version_detail = fields.Many2One('lims.results_report.version.detail',
'Report Detail', required=True, ondelete='CASCADE', select=True)
type = fields.Selection([
('signer', 'Signer'),
('manager', 'Manager'),
('responsible', 'Responsible'),
], 'Type', sort=False, required=True)
professional = fields.Many2One('lims.laboratory.professional',
'Professional', required=True)
#domain=[('id', 'in', Eval('professional_domain'))],
#depends=['professional_domain'])
professional_domain = fields.Function(fields.Many2Many(
'lims.laboratory.professional', None, None, 'Professional domain'),
'on_change_with_professional_domain')
@classmethod
def __register__(cls, module_name):
TableHandler = backend.TableHandler
ResultsDetail = Pool().get('lims.results_report.version.detail')
signer_table_exist = TableHandler.table_exist(
'lims_results_report_version_detail_signer')
detail_table_h = ResultsDetail.__table_handler__(module_name)
signer_exist = detail_table_h.column_exist('signer')
super().__register__(module_name)
if signer_exist and not signer_table_exist:
cls._migrate_signer()
@classmethod
def _migrate_signer(cls):
cursor = Transaction().connection.cursor()
pool = Pool()
ResultsDetail = pool.get('lims.results_report.version.detail')
report_table = ResultsDetail.__table__()
signer_table = cls.__table__()
cursor.execute(*report_table.select(
report_table.id,
Literal('signer').as_('type'),
report_table.signer,
where=report_table.signer != Null))
res = cursor.fetchall()
if res:
cursor.execute(*signer_table.insert([
signer_table.version_detail,
signer_table.type,
signer_table.professional,
], res))
@staticmethod
def default_type():
return 'signer'
@fields.depends('_parent_version_detail.laboratory')
def on_change_with_professional_domain(self, name=None):
pool = Pool()
UserLaboratory = pool.get('lims.user-laboratory')
LaboratoryProfessional = pool.get('lims.laboratory.professional')
laboratory = self.version_detail.laboratory
res = [laboratory.default_signer.id]
if laboratory.default_manager:
res.append(laboratory.default_manager.id)
users = UserLaboratory.search([
('laboratory', '=', laboratory.id),
])
if not users:
return res
professionals = LaboratoryProfessional.search([
('party.lims_user', 'in', [u.user.id for u in users]),
('role', '!=', ''),
])
if not professionals:
return res
return res + [p.id for p in professionals]
class ResultsReportVersionDetailSample(
sequence_ordered(), ModelSQL, ModelView):
'Results Report Version Detail Sample'
@ -2430,7 +2495,13 @@ class GenerateReport(Wizard):
laboratory_id = Transaction().context.get(
'samples_pending_reporting_laboratory', None)
signer = Laboratory(laboratory_id).default_signer.id
laboratory = Laboratory(laboratory_id)
signatories = [{'type': 'signer',
'professional': laboratory.default_signer.id}]
if laboratory.default_manager:
signatories.append({'type': 'manager',
'professional': laboratory.default_manager.id})
reports_created = []
@ -2440,6 +2511,7 @@ class GenerateReport(Wizard):
if self.start.report: # Result report selected
samples = []
extra_signers = set()
for notebook in self.start.notebooks:
lines = notebook._get_lines_for_reporting(laboratory_id,
state)
@ -2451,9 +2523,15 @@ class GenerateReport(Wizard):
'notebook': notebook.id,
'notebook_lines': [('create', notebook_lines)],
})
extra_signers.update(self._get_lines_signer(
lines, signatories))
extra_signatories = [{'type': 'responsible',
'professional': signer_id,
} for signer_id in extra_signers]
details = {
'type': self.start.type,
'signer': signer,
'signatories': [('create',
signatories + extra_signatories)],
'samples': [('create', samples)],
}
details.update(ResultsDetail._get_fields_from_samples(samples))
@ -2555,6 +2633,7 @@ class GenerateReport(Wizard):
notebooks[line.notebook.id]['lines'].append(line)
samples = []
extra_signers = set()
for notebook in notebooks.values():
notebook_lines = [{
'notebook_line': line.id,
@ -2565,9 +2644,15 @@ class GenerateReport(Wizard):
'notebook': notebook['notebook'],
'notebook_lines': [('create', notebook_lines)],
})
extra_signers.update(self._get_lines_signer(
notebook['lines'], signatories))
extra_signatories = [{'type': 'responsible',
'professional': signer_id,
} for signer_id in extra_signers]
details = {
'type': self.start.type,
'signer': signer,
'signatories': [('create',
signatories + extra_signatories)],
'samples': [('create', samples)],
}
details.update(ResultsDetail._get_fields_from_samples(
@ -2689,6 +2774,26 @@ class GenerateReport(Wizard):
reports_details = [draft_detail.id]
return reports_details
def _get_lines_signer(self, lines, excluded_signatories):
pool = Pool()
Department = pool.get('company.department')
signers = set()
departments = Department.search([
('id', 'in', list(set(l.department.id
for l in lines if l.department))),
('responsible', '!=', None),
])
for d in departments:
signers.add(d.laboratory_professional.id)
for s in excluded_signatories:
if s['professional'] in signers:
signers.remove(s['professional'])
return signers
def do_open_(self, action):
action['pyson_domain'] = PYSONEncoder().encode([
('id', 'in', [r.id for r in self.start.reports_created]),
@ -3339,16 +3444,14 @@ class ResultReport(Report):
report.report_result_type in ('both', 'both_range') else
'initial_concentration')
report_context['signer'] = ''
report_context['signer_role'] = ''
report_context['signature'] = None
report_context['headquarters'] = report.laboratory.headquarters
if report.signer:
report_context['signer'] = report.signer.rec_name
report_context['signer_role'] = report.signer.role
if report.signer.signature:
report_context['signature'] = report.signer.signature
report_context['signatories'] = []
for signer in report.signatories:
report_context['signatories'].append({
'signer': signer.professional.rec_name,
'role': signer.professional.role,
'signature': signer.professional.signature,
})
enac = False
enac_all_acredited = True

View File

@ -397,6 +397,19 @@
<field name="perm_delete" eval="True"/>
</record>
<!-- Results Report Version Detail Signer -->
<record model="ir.ui.view" id="lims_results_report_version_detail_signer_view_list">
<field name="model">lims.results_report.version.detail.signer</field>
<field name="type">tree</field>
<field name="name">results_report_version_detail_signer_list</field>
</record>
<record model="ir.ui.view" id="lims_results_report_version_detail_signer_view_form">
<field name="model">lims.results_report.version.detail.signer</field>
<field name="type">form</field>
<field name="name">results_report_version_detail_signer_form</field>
</record>
<!-- Results Report Version Detail Sample -->
<record model="ir.ui.view" id="lims_results_report_version_detail_sample_view_list">

View File

@ -8,4 +8,6 @@
<field name="headquarters"/>
<label name="default_location"/>
<field name="default_location"/>
<label name="responsible"/>
<field name="responsible"/>
</form>

View File

@ -4,4 +4,5 @@
<field name="name"/>
<field name="headquarters"/>
<field name="default_location"/>
<field name="responsible"/>
</tree>

View File

@ -8,6 +8,8 @@
<field name="default_laboratory_professional"/>
<label name="default_signer"/>
<field name="default_signer"/>
<label name="default_manager"/>
<field name="default_manager"/>
<label name="related_location"/>
<field name="related_location"/>
<label name="section"/>

View File

@ -4,6 +4,7 @@
<field name="description"/>
<field name="default_laboratory_professional"/>
<field name="default_signer"/>
<field name="default_manager"/>
<field name="related_location"/>
<field name="headquarters"/>
</tree>

View File

@ -24,8 +24,6 @@
<field name="report_type"/>
<label name="report_result_type"/>
<field name="report_result_type"/>
<label name="signer"/>
<field name="signer"/>
<label name="resultrange_origin"/>
<field name="resultrange_origin" widget="selection"/>
<label name="trace_report"/>
@ -35,6 +33,9 @@
<field name="samples" colspan="4" mode="form,tree"
view_ids="lims.lims_results_report_version_detail_sample_view_form,lims.lims_results_report_version_detail_sample_view_list"/>
</page>
<page name="signatories">
<field name="signatories" colspan="4"/>
</page>
<page name="comments">
<field name="comments" colspan="4"/>
</page>

View File

@ -12,7 +12,6 @@
<field name="entry_summary"/>
<field name="report_type"/>
<field name="report_result_type"/>
<field name="signer"/>
<field name="cie_fraction_type"/>
<field name="resultrange_origin"/>
<field name="trace_report"/>

View File

@ -0,0 +1,7 @@
<?xml version="1.0"?>
<form col="6">
<label name="type"/>
<field name="type"/>
<label name="professional"/>
<field name="professional"/>
</form>

View File

@ -0,0 +1,5 @@
<?xml version="1.0"?>
<tree sequence="sequence">
<field name="type"/>
<field name="professional"/>
</tree>

View File

@ -4,7 +4,6 @@
from trytond.pool import Pool
from . import task
from . import department
from . import user
from . import configuration
@ -16,7 +15,6 @@ def register():
task.EditAdministrativeTaskStart,
task.AdministrativeTaskProgram,
task.Cron,
department.Department,
user.User,
configuration.Configuration,
configuration.ConfigurationSequence,

View File

@ -1,12 +0,0 @@
# This file is part of lims_administrative_task 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 fields
from trytond.pool import PoolMeta
class Department(metaclass=PoolMeta):
__name__ = 'company.department'
responsible = fields.Many2One('res.user', 'Responsible User')

View File

@ -1,19 +0,0 @@
<?xml version="1.0"?>
<tryton>
<data>
<!-- Department -->
<record model="ir.ui.view" id="department_view_form">
<field name="model">company.department</field>
<field name="inherit" ref="lims.company_department_view_form"/>
<field name="name">department_form</field>
</record>
<record model="ir.ui.view" id="department_view_list">
<field name="model">company.department</field>
<field name="inherit" ref="lims.company_department_view_list"/>
<field name="name">department_list</field>
</record>
</data>
</tryton>

View File

@ -2,10 +2,6 @@
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:company.department,responsible:"
msgid "Responsible User"
msgstr "Usuario responsable"
msgctxt "field:lims.administrative.task,closing_date:"
msgid "Closing Date"
msgstr "Fecha de cierre"

View File

@ -6,6 +6,5 @@ depends:
xml:
user.xml
task.xml
department.xml
configuration.xml
message.xml

View File

@ -1,7 +0,0 @@
<?xml version="1.0"?>
<data>
<xpath expr="/form/field[@name='default_location']" position="after">
<label name="responsible"/>
<field name="responsible"/>
</xpath>
</data>

View File

@ -1,6 +0,0 @@
<?xml version="1.0"?>
<data>
<xpath expr="/tree/field[@name='default_location']" position="after">
<field name="responsible"/>
</xpath>
</data>

View File

@ -4,7 +4,7 @@
<label name="diagnosis_template"/>
<field name="diagnosis_template"/>
</xpath>
<xpath expr="/form/field[@name='signer']" position="after">
<xpath expr="/form/field[@name='party']" position="after">
<label name="diagnostician"/>
<field name="diagnostician"/>
</xpath>

View File

@ -1,6 +1,6 @@
<?xml version="1.0"?>
<data>
<xpath expr="/tree/field[@name='signer']" position="after">
<xpath expr="/tree/field[@name='party']" position="after">
<field name="diagnostician"/>
</xpath>
</data>