kalenislims/lims/planification.py

6854 lines
274 KiB
Python

# -*- coding: utf-8 -*-
# This file is part of lims module for Tryton.
# The COPYRIGHT file at the top level of this repository contains
# the full copyright notices and license terms.
import logging
from datetime import datetime, date
from dateutil.relativedelta import relativedelta
from trytond.model import Workflow, ModelView, ModelSQL, fields, Unique
from trytond.wizard import Wizard, StateTransition, StateView, StateAction, \
Button
from trytond.report import Report
from trytond.pool import Pool
from trytond.transaction import Transaction
from trytond.pyson import PYSONEncoder, Eval, Equal, Bool, Not, Or
from trytond.exceptions import UserError
from trytond.i18n import gettext
from .configuration import get_print_date
class Planification(Workflow, ModelSQL, ModelView):
'Planification'
__name__ = 'lims.planification'
_rec_name = 'code'
_states = {'readonly': Not(Bool(Equal(Eval('state'), 'draft')))}
_depends = ['state']
code = fields.Char('Code', select=True, readonly=True)
date = fields.Date('Date', readonly=True)
laboratory = fields.Many2One('lims.laboratory', 'Laboratory',
required=True, states={
'readonly': ((Eval('state') != 'draft')
| (Eval('analysis', [0]) & Eval('laboratory'))),
}, depends=['state'])
analysis = fields.Many2Many('lims.planification-analysis',
'planification', 'analysis', 'Analysis/Sets/Groups',
context={'date_from': Eval('date_from'), 'date_to': Eval('date_to'),
'calculate': Bool(Equal(Eval('state'), 'draft'))},
domain=['OR', ('id', 'in', Eval('analysis')), [
('id', 'in', Eval('analysis_domain'))]],
states=_states,
depends=['state', 'date_from', 'date_to', 'analysis_domain'])
analysis_domain = fields.Function(fields.Many2Many('lims.analysis',
None, None, 'Analysis domain'),
'on_change_with_analysis_domain')
technicians = fields.One2Many('lims.planification.technician',
'planification', 'Technicians', depends=['method_domain',
'technicians_domain'])
date_from = fields.Date('Date from', states=_states, depends=_depends)
date_to = fields.Date('Date to', states=_states, depends=_depends)
start_date = fields.Date('Start date', depends=_depends,
states={'readonly': Bool(Equal(Eval('state'), 'confirmed'))})
details = fields.One2Many('lims.planification.detail',
'planification', 'Fractions to plan',
states=_states, depends=_depends)
controls = fields.Many2Many('lims.planification-fraction',
'planification', 'fraction', 'Controls', readonly=True)
state = fields.Selection([
('draft', 'Draft'),
('preplanned', 'Pre-Planned'),
('confirmed', 'Confirmed'),
('not_executed', 'Not executed'),
], 'State', required=True, readonly=True)
waiting_process = fields.Boolean('Waiting process')
wizard_executed = fields.Boolean('Wizard executed')
method_domain = fields.Function(fields.One2Many('lims.lab.method',
None, 'Method domain'),
'on_change_with_method_domain', setter='set_method_domain')
technicians_domain = fields.Function(fields.One2Many(
'lims.laboratory.professional', None, 'Technicians domain'),
'on_change_with_technicians_domain', setter='set_technicians_domain')
comments = fields.Text('Comments')
del _states, _depends
@classmethod
def __setup__(cls):
super().__setup__()
cls._order.insert(0, ('code', 'DESC'))
cls._transitions |= set((
('draft', 'preplanned'),
('preplanned', 'confirmed'),
('confirmed', 'not_executed'),
))
cls._buttons.update({
'add_analysis': {
'readonly': (Eval('state') != 'draft'),
},
'search_fractions': {
'readonly': (Eval('state') != 'draft'),
},
'search_planned_fractions': {
'readonly': (Eval('state') != 'draft'),
},
'preplan': {
'invisible': (Eval('state') != 'draft'),
},
'confirm': {
'invisible': (Eval('state') != 'preplanned'),
},
'release_controls': {
'invisible': (Eval('state') != 'confirmed'),
},
'relate_technicians': {
'readonly': (Eval('state') != 'preplanned'),
},
'unlink_technicians': {
'readonly': (Eval('state') != 'preplanned'),
},
'replace_technician': {
'readonly': (Eval('state') != 'confirmed'),
},
'add_fraction_con': {
'readonly': (Eval('state') != 'preplanned'),
},
'add_fraction_rm_bmz': {
'readonly': (Eval('state') != 'preplanned'),
},
'add_fraction_bre': {
'readonly': (Eval('state') != 'preplanned'),
},
'add_fraction_mrt': {
'readonly': (Eval('state') != 'preplanned'),
},
'remove_control': {
'readonly': (Eval('state') != 'preplanned'),
},
})
@classmethod
def __register__(cls, module_name):
super().__register__(module_name)
table_h = cls.__table_handler__(module_name)
table_h.not_null_action('date_from', action='remove')
table_h.not_null_action('date_to', action='remove')
@staticmethod
def default_state():
return 'draft'
@staticmethod
def default_waiting_process():
return False
@staticmethod
def default_wizard_executed():
return False
@staticmethod
def default_date():
Date = Pool().get('ir.date')
return Date.today()
@staticmethod
def default_laboratory():
return Transaction().context.get('laboratory', None)
@fields.depends('laboratory', 'state',
methods=['_get_analysis_domain'])
def on_change_with_analysis_domain(self, name=None):
if not self.laboratory or self.state != 'draft':
return []
return self._get_analysis_domain(self.laboratory)
@staticmethod
def _get_analysis_domain(laboratory):
cursor = Transaction().connection.cursor()
pool = Pool()
AnalysisLaboratory = pool.get('lims.analysis-laboratory')
Analysis = pool.get('lims.analysis')
if not laboratory:
return []
cursor.execute('SELECT al.analysis '
'FROM "' + AnalysisLaboratory._table + '" al '
'INNER JOIN "' + Analysis._table + '" a '
'ON a.id = al.analysis '
'WHERE al.laboratory = %s '
'AND a.behavior != \'internal_relation\'',
(laboratory.id,))
analysis_sets_list = [a[0] for a in cursor.fetchall()]
groups_list = []
cursor.execute('SELECT id '
'FROM "' + Analysis._table + '" '
'WHERE type = \'group\'')
groups_list_ids = [g[0] for g in cursor.fetchall()]
for group_id in groups_list_ids:
if Planification._get_group_available(group_id,
analysis_sets_list):
groups_list.append(group_id)
return analysis_sets_list + groups_list
@staticmethod
def _get_group_available(group_id, analysis_sets_list):
cursor = Transaction().connection.cursor()
pool = Pool()
AnalysisIncluded = pool.get('lims.analysis.included')
Analysis = pool.get('lims.analysis')
cursor.execute('SELECT ia.included_analysis, a.type '
'FROM "' + AnalysisIncluded._table + '" ia '
'INNER JOIN "' + Analysis._table + '" a '
'ON a.id = ia.included_analysis '
'WHERE ia.analysis = %s '
'AND a.behavior != \'internal_relation\'',
(group_id,))
included_analysis = cursor.fetchall()
if not included_analysis:
return False
for analysis in included_analysis:
if (analysis[1] != 'group' and analysis[0] not in
analysis_sets_list):
return False
if (analysis[1] == 'group' and not
Planification._get_group_available(analysis[0],
analysis_sets_list)):
return False
return True
@classmethod
def create(cls, vlist):
pool = Pool()
Config = pool.get('lims.configuration')
vlist = [x.copy() for x in vlist]
config = Config(1)
for values in vlist:
values['code'] = config.planification_sequence.get()
return super().create(vlist)
@classmethod
def check_delete(cls, planifications):
for planification in planifications:
if planification.state not in ['draft', 'preplanned']:
raise UserError(gettext('lims.msg_delete_planification',
planification=planification.rec_name))
@classmethod
def delete(cls, planifications):
cls.check_delete(planifications)
super().delete(planifications)
@classmethod
def copy(cls, planifications, default=None):
raise UserError(gettext('lims.msg_copy_planification'))
@classmethod
@ModelView.button_action('lims.wiz_lims_add_analysis')
def add_analysis(cls, planifications):
pass
@classmethod
@ModelView.button_action('lims.wiz_lims_search_fractions')
def search_fractions(cls, planifications):
pass
@classmethod
@ModelView.button_action('lims.wiz_lims_search_planned_fractions')
def search_planned_fractions(cls, planifications):
pass
@classmethod
@ModelView.button
@Workflow.transition('preplanned')
def preplan(cls, planifications):
for planification in planifications:
planification.check_start_date()
@classmethod
@ModelView.button_action('lims.wiz_lims_technicians_qualification')
def confirm(cls, planifications):
for planification in planifications:
planification.check_wizard_executed()
planification.check_start_date()
planification.check_technicians()
def check_wizard_executed(self):
if self.wizard_executed:
raise UserError(gettext('lims.msg_wizard_executed'))
self.wizard_executed = True
self.save()
def check_start_date(self):
if not self.start_date:
raise UserError(gettext('lims.msg_not_start_date'))
for detail in self.details:
if detail.fraction.sample.date2 > self.start_date:
raise UserError(gettext('lims.msg_invalid_start_date',
date=detail.fraction.sample.date2,
sample=detail.fraction.sample.rec_name))
def check_technicians(self):
fractions = {}
for detail in self.details:
for service_detail in detail.details:
if not service_detail.staff_responsible:
key = (detail.fraction.id,
service_detail.notebook_line.method.id)
if key not in fractions:
fractions[key] = '%s (%s)' % (detail.fraction.rec_name,
service_detail.notebook_line.method.code)
if fractions:
sorted_fractions = sorted(
list(fractions.values()), key=lambda x: x)
raise UserError(gettext('lims.msg_no_technician',
fractions='\n' + '\n'.join(sorted_fractions) + '\n'))
@classmethod
def process_waiting_planifications(cls):
'''
Cron - Process Waiting Planifications
'''
logger = logging.getLogger('lims_planification')
planifications = cls.search([
('waiting_process', '=', True),
], order=[('id', 'ASC')])
if planifications:
logger.info('Cron - Processing planifications:INIT')
for planification in planifications:
if planification.state == 'confirmed':
cls.do_confirm([planification])
elif planification.state == 'not_executed':
cls.do_release_controls([planification])
logger.info('Cron - Processing planifications:END')
@classmethod
def do_confirm(cls, planifications):
for planification in planifications:
planification.update_laboratory_notebook()
if planification.waiting_process:
planification.waiting_process = False
planification.save()
def pre_update_laboratory_notebook(self):
cursor = Transaction().connection.cursor()
pool = Pool()
PlanificationDetail = pool.get('lims.planification.detail')
PlanificationServiceDetail = pool.get(
'lims.planification.service_detail')
NotebookLine = pool.get('lims.notebook.line')
cursor.execute('SELECT sd.notebook_line '
'FROM "' + PlanificationServiceDetail._table + '" sd '
'INNER JOIN "' + PlanificationDetail._table + '" pd '
'ON pd.id = sd.detail '
'WHERE pd.planification = %s '
'AND sd.notebook_line IS NOT NULL',
(self.id,))
notebook_lines = NotebookLine.browse(x[0]
for x in cursor.fetchall())
if notebook_lines:
NotebookLine.write(notebook_lines, {
'start_date': self.start_date,
'planification': self.id,
})
def update_laboratory_notebook(self):
cursor = Transaction().connection.cursor()
pool = Pool()
PlanificationDetail = pool.get('lims.planification.detail')
PlanificationServiceDetail = pool.get(
'lims.planification.service_detail')
ServiceDetailProfessional = pool.get(
'lims.planification.service_detail-laboratory.professional')
NotebookLineProfessional = pool.get(
'lims.notebook.line-laboratory.professional')
NotebookLineControl = pool.get('lims.notebook.line-fraction')
# Professionals
cursor.execute('SELECT sd.notebook_line, sdp.professional '
'FROM "' + ServiceDetailProfessional._table + '" sdp '
'INNER JOIN "' + PlanificationServiceDetail._table + '" sd '
'ON sd.id = sdp.detail '
'INNER JOIN "' + PlanificationDetail._table + '" pd '
'ON pd.id = sd.detail '
'WHERE pd.planification = %s '
'AND sd.notebook_line IS NOT NULL',
(self.id,))
res = cursor.fetchall()
if res:
notebook_lines_ids = ', '.join(str(nl_id)
for nl_id, sd_id in res)
cursor.execute('DELETE FROM "' +
NotebookLineProfessional._table + '" '
'WHERE notebook_line IN (' + notebook_lines_ids + ')')
to_create = []
for notebook_line, professional in res:
to_create.append({
'notebook_line': notebook_line,
'professional': professional,
})
NotebookLineProfessional.create(to_create)
# Controls
controls = [f.id for f in self.controls]
if controls:
cursor.execute('SELECT sd.notebook_line '
'FROM "' + PlanificationServiceDetail._table + '" sd '
'INNER JOIN "' + PlanificationDetail._table + '" pd '
'ON pd.id = sd.detail '
'WHERE pd.planification = %s '
'AND sd.notebook_line IS NOT NULL '
'AND sd.is_control = FALSE',
(self.id,))
res = [x[0] for x in cursor.fetchall()]
if res:
notebook_lines_ids = ', '.join(str(nl_id)
for nl_id in res)
cursor.execute('DELETE FROM "' +
NotebookLineControl._table + '" '
'WHERE notebook_line IN (' + notebook_lines_ids + ')')
to_create = []
for notebook_line in res:
for fraction in controls:
to_create.append({
'notebook_line': notebook_line,
'fraction': fraction,
})
NotebookLineControl.create(to_create)
def update_analysis_detail(self):
cursor = Transaction().connection.cursor()
pool = Pool()
PlanificationDetail = pool.get('lims.planification.detail')
PlanificationServiceDetail = pool.get(
'lims.planification.service_detail')
NotebookLine = pool.get('lims.notebook.line')
EntryDetailAnalysis = pool.get('lims.entry.detail.analysis')
cursor.execute('SELECT nl.analysis_detail '
'FROM "' + NotebookLine._table + '" nl '
'INNER JOIN "' + PlanificationServiceDetail._table + '" sd '
'ON nl.id = sd.notebook_line '
'INNER JOIN "' + PlanificationDetail._table + '" pd '
'ON pd.id = sd.detail '
'WHERE pd.planification = %s '
'AND nl.analysis_detail IS NOT NULL',
(self.id,))
analysis_details = EntryDetailAnalysis.browse(x[0]
for x in cursor.fetchall())
if analysis_details:
EntryDetailAnalysis.write(analysis_details, {'state': 'planned'})
@classmethod
@ModelView.button
@Workflow.transition('not_executed')
def release_controls(cls, planifications):
for planification in planifications:
# Check if is still waiting for confirmation
if planification.waiting_process:
raise UserError(gettext('lims.msg_waiting_process',
planification=planification.code))
cls.__queue__.do_release_controls(planifications)
@classmethod
def do_release_controls(cls, planifications):
for planification in planifications:
planification.re_update_laboratory_notebook()
planification.re_update_analysis_detail()
planification.unlink_controls()
if planification.waiting_process:
planification.waiting_process = False
planification.save()
def re_update_laboratory_notebook(self):
NotebookLine = Pool().get('lims.notebook.line')
for detail in self.details:
for service_detail in detail.details:
if service_detail.is_control and service_detail.notebook_line:
notebook_line = NotebookLine(
service_detail.notebook_line.id)
notebook_line.start_date = None
notebook_line.laboratory_professionals = []
notebook_line.planification = None
notebook_line.controls = []
notebook_line.save()
def re_update_analysis_detail(self):
EntryDetailAnalysis = Pool().get('lims.entry.detail.analysis')
analysis_detail_ids = []
for detail in self.details:
for service_detail in detail.details:
if (service_detail.is_control and
service_detail.notebook_line and
service_detail.notebook_line.analysis_detail):
analysis_detail_ids.append(
service_detail.notebook_line.analysis_detail.id)
analysis_details = EntryDetailAnalysis.search([
('id', 'in', analysis_detail_ids),
])
if analysis_details:
EntryDetailAnalysis.write(analysis_details, {
'state': 'unplanned',
})
def unlink_controls(self):
pool = Pool()
PlanificationFraction = pool.get('lims.planification-fraction')
PlanificationDetail = pool.get('lims.planification.detail')
controls = PlanificationFraction.search([
('planification', '=', self.id),
])
if controls:
PlanificationFraction.delete(controls)
controls_details = PlanificationDetail.search([
('planification', '=', self.id),
('details.is_control', '=', True),
])
if controls_details:
PlanificationDetail.delete(controls_details)
@fields.depends('analysis')
def on_change_with_method_domain(self, name=None):
methods = []
if self.analysis:
for a in self.analysis:
if a.methods:
methods.extend([m.id for m in a.methods])
return methods
@classmethod
def set_method_domain(cls, records, name, value):
return
@fields.depends('laboratory')
def on_change_with_technicians_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]),
])
if not professionals:
return []
return [p.id for p in professionals]
@classmethod
def set_technicians_domain(cls, records, name, value):
return
@classmethod
@ModelView.button_action('lims.wiz_lims_relate_technicians')
def relate_technicians(cls, planifications):
pass
@classmethod
@ModelView.button_action('lims.wiz_lims_unlink_technicians')
def unlink_technicians(cls, planifications):
pass
@classmethod
@ModelView.button_action('lims.wiz_lims_replace_technician')
def replace_technician(cls, planifications):
pass
@classmethod
@ModelView.button_action('lims.wiz_lims_add_fraction_con')
def add_fraction_con(cls, planifications):
pass
@classmethod
@ModelView.button_action('lims.wiz_lims_add_fraction_rm_bmz')
def add_fraction_rm_bmz(cls, planifications):
pass
@classmethod
@ModelView.button_action('lims.wiz_lims_add_fraction_bre')
def add_fraction_bre(cls, planifications):
pass
@classmethod
@ModelView.button_action('lims.wiz_lims_add_fraction_mrt')
def add_fraction_mrt(cls, planifications):
pass
@classmethod
@ModelView.button_action('lims.wiz_lims_remove_control')
def remove_control(cls, planifications):
pass
class PlanificationTechnician(ModelSQL, ModelView):
'Technician'
__name__ = 'lims.planification.technician'
planification = fields.Many2One('lims.planification', 'Planification',
ondelete='CASCADE', select=True, required=True)
laboratory_professional = fields.Many2One('lims.laboratory.professional',
'Laboratory professional', required=True, domain=[
('id', 'in', Eval('_parent_planification',
{}).get('technicians_domain')),
])
details = fields.Function(fields.One2Many(
'lims.planification.technician.detail', 'technician',
'Fractions to plan', readonly=True),
'get_details', setter='set_details')
@classmethod
def __setup__(cls):
super().__setup__()
t = cls.__table__()
cls._sql_constraints += [
('planification_professional_uniq',
Unique(t, t.planification, t.laboratory_professional),
'lims.msg_planification_professional_unique_id'),
]
def get_details(self, name=None):
cursor = Transaction().connection.cursor()
pool = Pool()
PlanificationDetailProfessional = pool.get(
'lims.planification.service_detail-laboratory.professional')
PlanificationServiceDetail = pool.get(
'lims.planification.service_detail')
PlanificationDetail = pool.get('lims.planification.detail')
NotebookLine = pool.get('lims.notebook.line')
Fraction = pool.get('lims.fraction')
LabMethod = pool.get('lims.lab.method')
cursor.execute('SELECT DISTINCT(f.number, nl.analysis_origin, '
'm.code||\' - \'||m.name) '
'FROM "' + PlanificationDetailProfessional._table + '" sdp '
'INNER JOIN "' + PlanificationServiceDetail._table + '" sd '
'ON sd.id = sdp.detail '
'INNER JOIN "' + PlanificationDetail._table + '" d '
'ON d.id = sd.detail '
'INNER JOIN "' + NotebookLine._table + '" nl '
'ON nl.id = sd.notebook_line '
'INNER JOIN "' + LabMethod._table + '" m '
'ON m.id = nl.method '
'INNER JOIN "' + Fraction._table + '" f '
'ON f.id = d.fraction '
'WHERE sdp.professional = %s '
'AND d.planification = %s',
(self.laboratory_professional.id, self.planification.id))
fractions = []
for d in cursor.fetchall():
r = d[0].split(',')
fractions.append({
'fraction': str(r[0][1:]),
'analysis_origin': str(r[1]).replace('"', ''),
'method': str(','.join(r[2:])[:-1].replace('"', '')),
})
return fractions
@classmethod
def set_details(cls, records, name, value):
return
class PlanificationTechnicianDetail(ModelView):
'Technician Detail'
__name__ = 'lims.planification.technician.detail'
fraction = fields.Char('Fraction')
analysis_origin = fields.Char('Analysis origin')
method = fields.Char('Method')
class PlanificationDetail(ModelSQL, ModelView):
'Fraction to Plan'
__name__ = 'lims.planification.detail'
planification = fields.Many2One('lims.planification', 'Planification',
ondelete='CASCADE', select=True, required=True)
fraction = fields.Many2One('lims.fraction', 'Fraction', required=True,
select=True)
service_analysis = fields.Many2One('lims.analysis', 'Service',
required=True, select=True)
fraction_type = fields.Function(fields.Many2One('lims.fraction.type',
'Fraction type'), 'get_fraction_field',
searcher='search_fraction_field')
label = fields.Function(fields.Char('Label'), 'get_fraction_field',
searcher='search_fraction_field')
product_type = fields.Function(fields.Many2One('lims.product.type',
'Product type'), 'get_fraction_field')
matrix = fields.Function(fields.Many2One('lims.matrix', 'Matrix'),
'get_fraction_field')
details = fields.One2Many('lims.planification.service_detail',
'detail', 'Planification detail', states={'readonly': True})
urgent = fields.Function(fields.Boolean('Urgent'), 'get_service_field',
setter='set_urgent')
priority = fields.Function(fields.Integer('Priority'), 'get_service_field')
laboratory_date = fields.Function(fields.Date('Laboratory deadline'),
'get_service_field')
report_date = fields.Function(fields.Date('Date agreed for result'),
'get_service_field')
comments = fields.Function(fields.Text('Comments'), 'get_fraction_field')
icon = fields.Function(fields.Char("Icon"), 'get_icon')
@classmethod
def __setup__(cls):
super().__setup__()
cls._order.insert(0, ('fraction', 'ASC'))
cls._order.insert(1, ('service_analysis', 'ASC'))
@classmethod
def get_fraction_field(cls, details, names):
result = {}
for name in names:
result[name] = {}
if (name == 'label' or name == 'comments'):
for d in details:
result[name][d.id] = getattr(d.fraction, name, None)
elif name == 'fraction_type':
for d in details:
field = getattr(d.fraction, 'type', None)
result[name][d.id] = field.id if field else None
else:
for d in details:
field = getattr(d.fraction, name, None)
result[name][d.id] = field.id if field else None
return result
@classmethod
def search_fraction_field(cls, name, clause):
if name == 'fraction_type':
name = 'type'
return [('fraction.' + name,) + tuple(clause[1:])]
@classmethod
def get_service_field(cls, details, names):
result = {}
for name in names:
result[name] = {}
if name == 'urgent':
for d in details:
result[name][d.id] = False
elif name == 'priority':
for d in details:
result[name][d.id] = 0
else:
for d in details:
result[name][d.id] = None
for d in details:
if d.fraction and d.service_analysis:
for service in d.fraction.services:
if service.analysis == d.service_analysis:
for name in names:
result[name][d.id] = getattr(service, name)
return result
@classmethod
def set_urgent(cls, details, name, value):
pool = Pool()
Service = pool.get('lims.service')
NotebookLine = pool.get('lims.notebook.line')
services_to_write = []
notebook_lines_to_write = []
for d in details:
if d.fraction and d.service_analysis:
for service in d.fraction.services:
if service.analysis == d.service_analysis:
services_to_write.append(service)
for sd in d.details:
notebook_lines_to_write.append(sd.notebook_line)
if services_to_write:
with Transaction().set_context(not_validate=True):
Service.write(services_to_write, {'urgent': value})
if notebook_lines_to_write and value:
NotebookLine.write(notebook_lines_to_write, {'urgent': value})
def get_icon(self, name):
if self.comments:
return 'lims-blue'
return 'lims-white'
class PlanificationServiceDetail(ModelSQL, ModelView):
'Planification Detail'
__name__ = 'lims.planification.service_detail'
detail = fields.Many2One('lims.planification.detail', 'Planification',
ondelete='CASCADE', select=True, required=True)
planification = fields.Function(fields.Many2One('lims.planification',
'Planification'), 'get_planification', searcher='search_planification')
notebook_line = fields.Many2One('lims.notebook.line', 'Notebook line',
required=True, select=True)
staff_responsible = fields.Many2Many(
'lims.planification.service_detail-laboratory.professional', 'detail',
'professional', 'Laboratory professionals')
is_control = fields.Boolean('Is Control', select=True)
is_replanned = fields.Boolean('Is Replanned', select=True)
planned_service = fields.Many2One('lims.analysis', 'Planned service')
repetition = fields.Function(fields.Integer('Repetition'),
'get_repetition')
@staticmethod
def default_is_control():
return False
@staticmethod
def default_is_replanned():
return False
def get_planification(self, name=None):
if self.detail:
if self.detail.planification:
return self.detail.planification.id
return None
@classmethod
def search_planification(cls, name, clause):
return [('detail.' + name,) + tuple(clause[1:])]
def get_repetition(self, name=None):
if self.notebook_line:
return self.notebook_line.repetition
return None
class PlanificationServiceDetailLaboratoryProfessional(ModelSQL):
'Planification Detail - Laboratory Professional'
__name__ = 'lims.planification.service_detail-laboratory.professional'
_table = 'lims_plan_service_d-laboratory_professional'
detail = fields.Many2One('lims.planification.service_detail',
'Planification detail', ondelete='CASCADE', select=True,
required=True)
professional = fields.Many2One('lims.laboratory.professional',
'Laboratory professional', ondelete='CASCADE', select=True,
required=True)
class PlanificationAnalysis(ModelSQL):
'Planification - Analysis'
__name__ = 'lims.planification-analysis'
planification = fields.Many2One('lims.planification', 'Planification',
ondelete='CASCADE', select=True, required=True)
analysis = fields.Many2One('lims.analysis', 'Analysis',
ondelete='CASCADE', select=True, required=True)
class PlanificationFraction(ModelSQL):
'Planification - Fraction'
__name__ = 'lims.planification-fraction'
planification = fields.Many2One('lims.planification', 'Planification',
ondelete='CASCADE', select=True, required=True)
fraction = fields.Many2One('lims.fraction', 'Fraction',
ondelete='CASCADE', select=True, required=True)
class NotebookLineFraction(ModelSQL):
'Laboratory Notebook Line - Fraction'
__name__ = 'lims.notebook.line-fraction'
notebook_line = fields.Many2One('lims.notebook.line', 'Notebook Line',
ondelete='CASCADE', select=True, required=True)
fraction = fields.Many2One('lims.fraction', 'Fraction',
ondelete='CASCADE', select=True, required=True)
class FractionReagent(ModelSQL, ModelView):
'Fraction Reagent'
__name__ = 'lims.fraction.reagent'
fraction = fields.Many2One('lims.fraction', 'Fraction', required=True,
ondelete='CASCADE', select=True)
product = fields.Many2One('product.product', 'Reagent', required=True,
domain=[('account_category', 'in', Eval('reagent_domain'))],
depends=['reagent_domain'])
reagent_domain = fields.Function(fields.Many2Many('product.category',
None, None, 'Reagent domain'), 'get_reagent_domain')
lot = fields.Many2One('stock.lot', 'Lot',
domain=[('product', '=', Eval('product'))],
depends=['product'])
quantity = fields.Function(fields.Float('Current quantity'),
'on_change_with_quantity')
default_uom = fields.Function(fields.Many2One('product.uom',
'Default UOM'),
'on_change_with_default_uom')
@staticmethod
def default_reagent_domain():
Config = Pool().get('lims.configuration')
config = Config(1)
return config.get_reagents()
def get_reagent_domain(self, name=None):
return self.default_reagent_domain()
@fields.depends('product', '_parent_product.quantity')
def on_change_with_quantity(self, name=None):
pool = Pool()
Location = pool.get('stock.location')
Product = pool.get('product.product')
Date = pool.get('ir.date')
if not self.product:
return 0
locations = Location.search([('type', '=', 'storage')])
with Transaction().set_context(locations=[l.id for l in locations],
stock_date_end=Date.today()):
product = Product(self.product.id)
return product.quantity or 0
@fields.depends('product', '_parent_product.default_uom')
def on_change_with_default_uom(self, name=None):
if self.product:
return self.product.default_uom.id
class LabProfessionalMethod(ModelSQL, ModelView):
'Laboratory Professional Method'
__name__ = 'lims.lab.professional.method'
professional = fields.Many2One('lims.laboratory.professional',
'Professional', required=True, select=True)
method = fields.Many2One('lims.lab.method', 'Method', required=True,
select=True)
state = fields.Selection([
('training', 'Training'),
('qualified', 'Qualified'),
('requalified', 'Requalified'),
], 'State', sort=False)
type = fields.Selection([
('preparation', 'Preparation'),
('analytical', 'Analytical'),
], 'Type', sort=False, select=True)
requalification_history = fields.One2Many(
'lims.lab.professional.method.requalification', 'professional_method',
'Trainings/Qualifications/Requalifications')
determination = fields.Function(fields.Char('Determination'),
'get_determination', searcher='search_determination')
@classmethod
def __setup__(cls):
super().__setup__()
cls._order.insert(0, ('professional', 'ASC'))
cls._order.insert(1, ('method', 'ASC'))
t = cls.__table__()
cls._sql_constraints += [
('professional_method_type_uniq',
Unique(t, t.professional, t.method, t.type),
'lims.msg_professional_method_unique_id'),
]
def get_determination(self, name=None):
if self.method:
return self.method.determination
return None
@classmethod
def search_determination(cls, name, clause):
return [('method.' + name,) + tuple(clause[1:])]
class LabProfessionalMethodRequalification(ModelSQL, ModelView):
'Laboratory Professional Method Requalification'
__name__ = 'lims.lab.professional.method.requalification'
_table = 'lims_lab_pro_method_req'
professional_method = fields.Many2One('lims.lab.professional.method',
'Professional Method', ondelete='CASCADE', select=True, required=True)
type = fields.Selection([
('training', 'Training'),
('qualification', 'Qualification'),
('requalification', 'Requalification'),
], 'Type', sort=False)
date = fields.Date('Date', required=True)
last_execution_date = fields.Date('Last execution Date')
supervisors = fields.One2Many(
'lims.lab.professional.method.requalification.supervisor',
'method_requalification', 'Supervisors')
controls = fields.One2Many(
'lims.lab.professional.method.requalification.control',
'method_requalification', 'Controls')
class LabProfessionalMethodRequalificationSupervisor(ModelSQL, ModelView):
'Laboratory Professional Method Requalification Supervisor'
__name__ = 'lims.lab.professional.method.requalification.supervisor'
_table = 'lims_lab_pro_method_req_supervisor'
method_requalification = fields.Many2One(
'lims.lab.professional.method.requalification',
'Professional method requalification', ondelete='CASCADE',
select=True, required=True)
supervisor = fields.Many2One('lims.laboratory.professional', 'Supervisor',
required=True)
class LabProfessionalMethodRequalificationControl(ModelSQL, ModelView):
'Laboratory Professional Method Requalification Control'
__name__ = 'lims.lab.professional.method.requalification.control'
_table = 'lims_lab_pro_method_req_control'
method_requalification = fields.Many2One(
'lims.lab.professional.method.requalification',
'Professional method requalification', ondelete='CASCADE',
select=True, required=True)
control = fields.Many2One('lims.fraction', 'Control',
required=True)
class BlindSample(ModelSQL, ModelView):
'Blind Sample'
__name__ = 'lims.blind_sample'
line = fields.Many2One('lims.notebook.line', 'Line', required=True,
readonly=True, ondelete='CASCADE', select=True)
entry = fields.Many2One('lims.entry', 'Entry', readonly=True)
sample = fields.Many2One('lims.sample', 'Sample', readonly=True)
fraction = fields.Many2One('lims.fraction', 'Fraction', readonly=True)
service = fields.Many2One('lims.service', 'Service', readonly=True)
analysis = fields.Many2One('lims.analysis', 'Analysis', readonly=True)
repetition = fields.Integer('Repetition', readonly=True)
date = fields.Date('Date', readonly=True)
original_line = fields.Many2One('lims.notebook.line', 'Original line')
original_sample = fields.Many2One('lims.sample', 'Original sample',
readonly=True)
original_fraction = fields.Many2One('lims.fraction', 'Original fraction',
readonly=True)
original_repetition = fields.Integer('Repetition', readonly=True)
min_value = fields.Char('Minimum value', readonly=True)
max_value = fields.Char('Maximum value', readonly=True)
class RelateTechniciansStart(ModelView):
'Relate Technicians'
__name__ = 'lims.planification.relate_technicians.start'
exclude_relateds = fields.Boolean('Exclude fractions already related')
grouping = fields.Selection([
('none', 'None'),
('origin_method', 'Analysis origin and Method'),
('origin', 'Analysis origin'),
], 'Grouping', sort=False, required=True)
class RelateTechniciansResult(ModelView):
'Relate Technicians'
__name__ = 'lims.planification.relate_technicians.result'
technicians = fields.Many2Many('lims.laboratory.professional',
None, None, 'Technicians', required=True,
domain=[('id', 'in', Eval('technicians_domain'))],
depends=['technicians_domain'])
technicians_domain = fields.One2Many('lims.laboratory.professional',
None, 'Technicians domain')
grouping = fields.Selection([
('none', 'None'),
('origin_method', 'Analysis origin and Method'),
('origin', 'Analysis origin'),
], 'Grouping', sort=False, readonly=True)
details1 = fields.Many2Many(
'lims.planification.relate_technicians.detail1', None, None,
'Fractions to plan', domain=[('id', 'in', Eval('details1_domain'))],
states={'invisible': Not(Bool(Equal(Eval('grouping'), 'none')))},
depends=['details1_domain', 'grouping'])
details1_domain = fields.One2Many(
'lims.planification.relate_technicians.detail1', None,
'Fractions domain')
details2 = fields.Many2Many(
'lims.planification.relate_technicians.detail2', None, None,
'Fractions to plan', domain=[('id', 'in', Eval('details2_domain'))],
states={
'invisible': Not(Bool(Equal(Eval('grouping'), 'origin_method'))),
}, depends=['details2_domain', 'grouping'])
details2_domain = fields.One2Many(
'lims.planification.relate_technicians.detail2', None,
'Fractions domain')
details3 = fields.Many2Many(
'lims.planification.relate_technicians.detail3', None, None,
'Fractions to plan', domain=[('id', 'in', Eval('details3_domain'))],
states={'invisible': Not(Bool(Equal(Eval('grouping'), 'origin')))},
depends=['details3_domain', 'grouping'])
details3_domain = fields.One2Many(
'lims.planification.relate_technicians.detail3', None,
'Fractions domain')
@fields.depends('grouping')
def on_change_grouping(self):
self.details1 = []
self.details2 = []
self.details3 = []
class RelateTechniciansDetail1(ModelSQL, ModelView):
'Fraction Detail'
__name__ = 'lims.planification.relate_technicians.detail1'
_table = 'lims_planification_relate_technicians_d1'
fraction = fields.Many2One('lims.fraction', 'Fraction')
service_analysis = fields.Many2One('lims.analysis', 'Service')
fraction_type = fields.Function(fields.Many2One('lims.fraction.type',
'Fraction type'), 'get_fraction_field')
label = fields.Function(fields.Char('Label'), 'get_fraction_field')
session_id = fields.Integer('Session ID')
@classmethod
def __register__(cls, module_name):
super().__register__(module_name)
cursor = Transaction().connection.cursor()
cursor.execute('DELETE FROM "' + cls._table + '"')
@classmethod
def __setup__(cls):
super().__setup__()
cls._order.insert(0, ('fraction', 'ASC'))
cls._order.insert(1, ('service_analysis', 'ASC'))
@classmethod
def get_fraction_field(cls, details, names):
result = {}
for name in names:
result[name] = {}
if name == 'label':
for d in details:
result[name][d.id] = getattr(d.fraction, name, None)
elif name == 'fraction_type':
for d in details:
field = getattr(d.fraction, 'type', None)
result[name][d.id] = field.id if field else None
else:
for d in details:
field = getattr(d.fraction, name, None)
result[name][d.id] = field.id if field else None
return result
class RelateTechniciansDetail2(ModelSQL, ModelView):
'Fraction Detail'
__name__ = 'lims.planification.relate_technicians.detail2'
_table = 'lims_planification_relate_technicians_d2'
fraction = fields.Many2One('lims.fraction', 'Fraction')
analysis_origin = fields.Char('Analysis origin')
method = fields.Many2One('lims.lab.method', 'Method')
session_id = fields.Integer('Session ID')
@classmethod
def __register__(cls, module_name):
super().__register__(module_name)
cursor = Transaction().connection.cursor()
cursor.execute('DELETE FROM "' + cls._table + '"')
@classmethod
def __setup__(cls):
super().__setup__()
cls._order.insert(0, ('fraction', 'ASC'))
cls._order.insert(1, ('analysis_origin', 'ASC'))
cls._order.insert(2, ('method', 'ASC'))
class RelateTechniciansDetail3(ModelSQL, ModelView):
'Fraction Detail'
__name__ = 'lims.planification.relate_technicians.detail3'
_table = 'lims_planification_relate_technicians_d3'
fraction = fields.Many2One('lims.fraction', 'Fraction')
analysis_origin = fields.Char('Analysis origin')
session_id = fields.Integer('Session ID')
@classmethod
def __register__(cls, module_name):
super().__register__(module_name)
cursor = Transaction().connection.cursor()
cursor.execute('DELETE FROM "' + cls._table + '"')
@classmethod
def __setup__(cls):
super().__setup__()
cls._order.insert(0, ('fraction', 'ASC'))
cls._order.insert(1, ('analysis_origin', 'ASC'))
class RelateTechnicians(Wizard):
'Relate Technicians'
__name__ = 'lims.planification.relate_technicians'
start = StateView('lims.planification.relate_technicians.start',
'lims.lims_relate_technicians_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Search', 'search', 'tryton-ok', default=True),
])
search = StateTransition()
result = StateView('lims.planification.relate_technicians.result',
'lims.lims_relate_technicians_result_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Relate', 'relate', 'tryton-ok', default=True),
])
relate = StateTransition()
def default_start(self, fields):
return {
'exclude_relateds': True,
'grouping': 'none',
}
def transition_search(self):
planification_id = Transaction().context['active_id']
self.result.grouping = self.start.grouping
self.result.details1_domain = []
self.result.details2_domain = []
self.result.details3_domain = []
if self.start.grouping == 'none':
self.result.details1_domain = self._view_details1(planification_id,
self.start.exclude_relateds)
elif self.start.grouping == 'origin_method':
self.result.details2_domain = self._view_details2(planification_id,
self.start.exclude_relateds)
elif self.start.grouping == 'origin':
self.result.details3_domain = self._view_details3(planification_id,
self.start.exclude_relateds)
return 'result'
def default_result(self, fields):
Planification = Pool().get('lims.planification')
planification = Planification(Transaction().context['active_id'])
details1_domain = []
if self.result.details1_domain:
details1_domain = [d.id for d in self.result.details1_domain]
details2_domain = []
if self.result.details2_domain:
details2_domain = [d.id for d in self.result.details2_domain]
details3_domain = []
if self.result.details3_domain:
details3_domain = [d.id for d in self.result.details3_domain]
return {
'technicians_domain': [t.laboratory_professional.id
for t in planification.technicians],
'details1_domain': details1_domain,
'details2_domain': details2_domain,
'details3_domain': details3_domain,
'grouping': self.start.grouping,
}
def _view_details1(self, planification_id, exclude_relateds):
cursor = Transaction().connection.cursor()
pool = Pool()
PlanificationDetail = pool.get(
'lims.planification.detail')
PlanificationServiceDetail = pool.get(
'lims.planification.service_detail')
ServiceDetailProfessional = pool.get(
'lims.planification.service_detail-laboratory.professional')
RelateTechniciansDetail1 = pool.get(
'lims.planification.relate_technicians.detail1')
exclude_relateds_clause = ''
if exclude_relateds:
exclude_relateds_clause = (' AND sd.id NOT IN ('
'SELECT sdp.detail '
'FROM "' + ServiceDetailProfessional._table + '" sdp '
'INNER JOIN "' + PlanificationServiceDetail._table + '" sd'
' ON sdp.detail = sd.id '
'INNER JOIN "' + PlanificationDetail._table + '" d'
' ON sd.detail = d.id '
'WHERE d.planification = %s'
')' % planification_id)
details1 = {}
cursor.execute('SELECT d.fraction, d.service_analysis, sd.id '
'FROM "' + PlanificationDetail._table + '" d '
'INNER JOIN "' + PlanificationServiceDetail._table + '" sd '
'ON sd.detail = d.id '
'WHERE d.planification = %s' +
exclude_relateds_clause,
(planification_id,))
for x in cursor.fetchall():
f, s = x[0], x[1]
if (f, s) not in details1:
details1[(f, s)] = {
'fraction': f,
'service_analysis': s,
}
to_create = []
for d in details1.values():
to_create.append({
'session_id': self._session_id,
'fraction': d['fraction'],
'service_analysis': d['service_analysis'],
})
return RelateTechniciansDetail1.create(to_create)
def _view_details2(self, planification_id, exclude_relateds):
cursor = Transaction().connection.cursor()
pool = Pool()
PlanificationDetail = pool.get(
'lims.planification.detail')
PlanificationServiceDetail = pool.get(
'lims.planification.service_detail')
ServiceDetailProfessional = pool.get(
'lims.planification.service_detail-laboratory.professional')
NotebookLine = pool.get(
'lims.notebook.line')
RelateTechniciansDetail2 = pool.get(
'lims.planification.relate_technicians.detail2')
exclude_relateds_clause = ''
if exclude_relateds:
exclude_relateds_clause = (' AND sd.id NOT IN ('
'SELECT sdp.detail '
'FROM "' + ServiceDetailProfessional._table + '" sdp '
'INNER JOIN "' + PlanificationServiceDetail._table + '" sd'
' ON sdp.detail = sd.id '
'INNER JOIN "' + PlanificationDetail._table + '" d'
' ON sd.detail = d.id '
'WHERE d.planification = %s'
')' % planification_id)
details2 = {}
cursor.execute('SELECT d.fraction, nl.analysis_origin, nl.method, '
'sd.id '
'FROM "' + PlanificationDetail._table + '" d '
'INNER JOIN "' + PlanificationServiceDetail._table + '" sd '
'ON sd.detail = d.id '
'INNER JOIN "' + NotebookLine._table + '" nl '
'ON sd.notebook_line = nl.id '
'WHERE d.planification = %s' +
exclude_relateds_clause,
(planification_id,))
for x in cursor.fetchall():
f, a, m = x[0], x[1], x[2]
if (f, a, m) not in details2:
details2[(f, a, m)] = {
'fraction': f,
'analysis_origin': a,
'method': m,
}
to_create = []
for d in details2.values():
to_create.append({
'session_id': self._session_id,
'fraction': d['fraction'],
'analysis_origin': d['analysis_origin'],
'method': d['method'],
})
return RelateTechniciansDetail2.create(to_create)
def _view_details3(self, planification_id, exclude_relateds):
cursor = Transaction().connection.cursor()
pool = Pool()
PlanificationDetail = pool.get(
'lims.planification.detail')
PlanificationServiceDetail = pool.get(
'lims.planification.service_detail')
ServiceDetailProfessional = pool.get(
'lims.planification.service_detail-laboratory.professional')
NotebookLine = pool.get(
'lims.notebook.line')
RelateTechniciansDetail3 = pool.get(
'lims.planification.relate_technicians.detail3')
exclude_relateds_clause = ''
if exclude_relateds:
exclude_relateds_clause = (' AND sd.id NOT IN ('
'SELECT sdp.detail '
'FROM "' + ServiceDetailProfessional._table + '" sdp '
'INNER JOIN "' + PlanificationServiceDetail._table + '" sd'
' ON sdp.detail = sd.id '
'INNER JOIN "' + PlanificationDetail._table + '" d'
' ON sd.detail = d.id '
'WHERE d.planification = %s'
')' % planification_id)
details3 = {}
cursor.execute('SELECT d.fraction, nl.analysis_origin, sd.id '
'FROM "' + PlanificationDetail._table + '" d '
'INNER JOIN "' + PlanificationServiceDetail._table + '" sd '
'ON sd.detail = d.id '
'INNER JOIN "' + NotebookLine._table + '" nl '
'ON sd.notebook_line = nl.id '
'WHERE d.planification = %s' +
exclude_relateds_clause,
(planification_id,))
for x in cursor.fetchall():
f, a = x[0], x[1]
if (f, a) not in details3:
details3[(f, a)] = {
'fraction': f,
'analysis_origin': a,
}
to_create = []
for d in details3.values():
to_create.append({
'session_id': self._session_id,
'fraction': d['fraction'],
'analysis_origin': d['analysis_origin'],
})
return RelateTechniciansDetail3.create(to_create)
def transition_relate(self):
PlanificationServiceDetail = Pool().get(
'lims.planification.service_detail')
planification_id = Transaction().context['active_id']
details = self._get_details(planification_id)
if not details:
return 'end'
PlanificationServiceDetail.write(details, {
'staff_responsible': [('remove',
[t.id for t in self.result.technicians])],
})
PlanificationServiceDetail.write(details, {
'staff_responsible': [('add',
[t.id for t in self.result.technicians])],
})
return 'end'
def _get_details(self, planification_id):
details = []
if self.start.grouping == 'none':
details = self._get_details1(planification_id,
self.start.exclude_relateds)
elif self.start.grouping == 'origin_method':
details = self._get_details2(planification_id,
self.start.exclude_relateds)
elif self.start.grouping == 'origin':
details = self._get_details3(planification_id,
self.start.exclude_relateds)
return details
def _get_details1(self, planification_id, exclude_relateds):
cursor = Transaction().connection.cursor()
pool = Pool()
PlanificationDetail = pool.get(
'lims.planification.detail')
PlanificationServiceDetail = pool.get(
'lims.planification.service_detail')
ServiceDetailProfessional = pool.get(
'lims.planification.service_detail-laboratory.professional')
exclude_relateds_clause = ''
if exclude_relateds:
exclude_relateds_clause = (' AND sd.id NOT IN ('
'SELECT sdp.detail '
'FROM "' + ServiceDetailProfessional._table + '" sdp '
'INNER JOIN "' + PlanificationServiceDetail._table + '" sd'
' ON sdp.detail = sd.id '
'INNER JOIN "' + PlanificationDetail._table + '" d'
' ON sd.detail = d.id '
'WHERE d.planification = %s'
')' % planification_id)
details = []
for detail in self.result.details1:
cursor.execute('SELECT sd.id '
'FROM "' + PlanificationDetail._table + '" d '
'INNER JOIN "' + PlanificationServiceDetail._table + '" sd'
' ON sd.detail = d.id '
'WHERE d.planification = %s '
'AND d.fraction = %s '
'AND d.service_analysis = %s' +
exclude_relateds_clause,
(planification_id, detail.fraction.id,
detail.service_analysis.id))
for x in cursor.fetchall():
details.append(x[0])
return PlanificationServiceDetail.browse(details)
def _get_details2(self, planification_id, exclude_relateds):
cursor = Transaction().connection.cursor()
pool = Pool()
PlanificationDetail = pool.get(
'lims.planification.detail')
PlanificationServiceDetail = pool.get(
'lims.planification.service_detail')
ServiceDetailProfessional = pool.get(
'lims.planification.service_detail-laboratory.professional')
NotebookLine = pool.get(
'lims.notebook.line')
exclude_relateds_clause = ''
if exclude_relateds:
exclude_relateds_clause = (' AND sd.id NOT IN ('
'SELECT sdp.detail '
'FROM "' + ServiceDetailProfessional._table + '" sdp '
'INNER JOIN "' + PlanificationServiceDetail._table + '" sd'
' ON sdp.detail = sd.id '
'INNER JOIN "' + PlanificationDetail._table + '" d'
' ON sd.detail = d.id '
'WHERE d.planification = %s'
')' % planification_id)
details = []
for detail in self.result.details2:
cursor.execute('SELECT sd.id '
'FROM "' + PlanificationDetail._table + '" d '
'INNER JOIN "' + PlanificationServiceDetail._table + '" sd'
' ON sd.detail = d.id '
'INNER JOIN "' + NotebookLine._table + '" nl '
'ON sd.notebook_line = nl.id '
'WHERE d.planification = %s '
'AND d.fraction = %s '
'AND nl.analysis_origin = %s '
'AND nl.method = %s' +
exclude_relateds_clause,
(planification_id, detail.fraction.id,
detail.analysis_origin, detail.method.id))
for x in cursor.fetchall():
details.append(x[0])
return PlanificationServiceDetail.browse(details)
def _get_details3(self, planification_id, exclude_relateds):
cursor = Transaction().connection.cursor()
pool = Pool()
PlanificationDetail = pool.get(
'lims.planification.detail')
PlanificationServiceDetail = pool.get(
'lims.planification.service_detail')
ServiceDetailProfessional = pool.get(
'lims.planification.service_detail-laboratory.professional')
NotebookLine = pool.get(
'lims.notebook.line')
exclude_relateds_clause = ''
if exclude_relateds:
exclude_relateds_clause = (' AND sd.id NOT IN ('
'SELECT sdp.detail '
'FROM "' + ServiceDetailProfessional._table + '" sdp '
'INNER JOIN "' + PlanificationServiceDetail._table + '" sd'
' ON sdp.detail = sd.id '
'INNER JOIN "' + PlanificationDetail._table + '" d'
' ON sd.detail = d.id '
'WHERE d.planification = %s'
')' % planification_id)
details = []
for detail in self.result.details3:
cursor.execute('SELECT sd.id '
'FROM "' + PlanificationDetail._table + '" d '
'INNER JOIN "' + PlanificationServiceDetail._table + '" sd'
' ON sd.detail = d.id '
'INNER JOIN "' + NotebookLine._table + '" nl '
'ON sd.notebook_line = nl.id '
'WHERE d.planification = %s '
'AND d.fraction = %s '
'AND nl.analysis_origin = %s' +
exclude_relateds_clause,
(planification_id, detail.fraction.id,
detail.analysis_origin))
for x in cursor.fetchall():
details.append(x[0])
return PlanificationServiceDetail.browse(details)
class UnlinkTechniciansStart(ModelView):
'Unlink Technicians Start'
__name__ = 'lims.planification.unlink_technicians.start'
technicians = fields.Many2Many('lims.laboratory.professional',
None, None, 'Technicians', required=True,
domain=[('id', 'in', Eval('technicians_domain'))],
depends=['technicians_domain'])
technicians_domain = fields.One2Many('lims.laboratory.professional',
None, 'Technicians domain')
details1 = fields.Many2Many(
'lims.planification.unlink_technicians.detail1', None, None,
'Assigned fractions', domain=[('id', 'in', Eval('details1_domain'))],
depends=['details1_domain'])
details1_domain = fields.Many2Many(
'lims.planification.unlink_technicians.detail1', None, None,
'Fractions domain')
class UnlinkTechniciansDetail1(ModelSQL, ModelView):
'Fraction Detail Unlink'
__name__ = 'lims.planification.unlink_technicians.detail1'
_table = 'lims_planification_unlink_technicians_d1'
fraction = fields.Many2One('lims.fraction', 'Fraction')
service_analysis = fields.Many2One('lims.analysis', 'Service')
fraction_type = fields.Function(fields.Many2One('lims.fraction.type',
'Fraction type'), 'get_fraction_field')
label = fields.Function(fields.Char('Label'), 'get_fraction_field')
session_id = fields.Integer('Session ID')
@classmethod
def __register__(cls, module_name):
super().__register__(module_name)
cursor = Transaction().connection.cursor()
cursor.execute('DELETE FROM "' + cls._table + '"')
@classmethod
def __setup__(cls):
super().__setup__()
cls._order.insert(0, ('fraction', 'ASC'))
cls._order.insert(1, ('service_analysis', 'ASC'))
@classmethod
def get_fraction_field(cls, details, names):
result = {}
for name in names:
result[name] = {}
if name == 'label':
for d in details:
result[name][d.id] = getattr(d.fraction, name, None)
elif name == 'fraction_type':
for d in details:
field = getattr(d.fraction, 'type', None)
result[name][d.id] = field.id if field else None
else:
for d in details:
field = getattr(d.fraction, name, None)
result[name][d.id] = field.id if field else None
return result
class UnlinkTechnicians(Wizard):
'Unlink Technicians'
__name__ = 'lims.planification.unlink_technicians'
start = StateView('lims.planification.unlink_technicians.start',
'lims.lims_unlink_technicians_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Unlink', 'unlink', 'tryton-ok', default=True),
])
unlink = StateTransition()
def default_start(self, fields):
Planification = Pool().get('lims.planification')
planification = Planification(Transaction().context['active_id'])
details_domain = self._view_details(planification.id)
return {
'technicians_domain': [t.laboratory_professional.id
for t in planification.technicians],
'details1_domain': [d.id for d in details_domain],
}
def _view_details(self, planification_id):
cursor = Transaction().connection.cursor()
pool = Pool()
PlanificationDetail = pool.get(
'lims.planification.detail')
PlanificationServiceDetail = pool.get(
'lims.planification.service_detail')
UnlinkTechniciansDetail1 = pool.get(
'lims.planification.unlink_technicians.detail1')
details1 = {}
cursor.execute('SELECT d.fraction, d.service_analysis, sd.id '
'FROM "' + PlanificationDetail._table + '" d '
'INNER JOIN "' + PlanificationServiceDetail._table + '" sd '
'ON sd.detail = d.id '
'WHERE d.planification = %s',
(planification_id,))
for x in cursor.fetchall():
f, s = x[0], x[1]
if (f, s) not in details1:
details1[(f, s)] = {
'fraction': f,
'service_analysis': s,
}
to_create = []
for d in details1.values():
to_create.append({
'session_id': self._session_id,
'fraction': d['fraction'],
'service_analysis': d['service_analysis'],
})
return UnlinkTechniciansDetail1.create(to_create)
def transition_unlink(self):
pool = Pool()
Planification = pool.get('lims.planification')
PlanificationServiceDetail = pool.get(
'lims.planification.service_detail')
planification = Planification(Transaction().context['active_id'])
details = self._get_details(planification.id)
PlanificationServiceDetail.write(details, {
'staff_responsible': [('remove',
[t.id for t in self.start.technicians])],
})
return 'end'
def _get_details(self, planification_id):
cursor = Transaction().connection.cursor()
pool = Pool()
PlanificationDetail = pool.get(
'lims.planification.detail')
PlanificationServiceDetail = pool.get(
'lims.planification.service_detail')
details = []
for detail in self.start.details1:
cursor.execute('SELECT sd.id '
'FROM "' + PlanificationDetail._table + '" d '
'INNER JOIN "' + PlanificationServiceDetail._table + '" sd'
' ON sd.detail = d.id '
'WHERE d.planification = %s '
'AND d.fraction = %s '
'AND d.service_analysis = %s',
(planification_id, detail.fraction.id,
detail.service_analysis.id))
for x in cursor.fetchall():
details.append(x[0])
return PlanificationServiceDetail.browse(details)
class AddFractionControlStart(ModelView):
'Add Fraction Control'
__name__ = 'lims.planification.add_fraction_con.start'
planification = fields.Many2One('lims.planification', 'Planification')
type = fields.Selection([
('exist', 'Existing CON'),
('coi', 'COI'),
('mrc', 'MRC'),
('sla', 'SLA'),
('itc', 'ITC'),
('itl', 'ITL'),
], 'Control type', sort=False, required=True)
original_fraction = fields.Many2One('lims.fraction', 'Original fraction',
required=True, domain=[('id', 'in', Eval('fraction_domain'))],
depends=['fraction_domain'])
fraction_domain = fields.Function(fields.One2Many('lims.fraction',
None, 'Fraction domain'), 'on_change_with_fraction_domain')
label = fields.Char('Label', depends=['type'],
states={'readonly': Eval('type') == 'exist'})
concentration_level = fields.Many2One('lims.concentration.level',
'Concentration level', states={
'invisible': Bool(Eval('concentration_level_invisible')),
}, depends=['concentration_level_invisible'])
concentration_level_invisible = fields.Boolean(
'Concentration level invisible')
generate_repetition = fields.Boolean('Generate repetition',
states={'readonly': Eval('type') == 'exist'}, depends=['type'])
@fields.depends('planification', 'type', '_parent_planification.analysis')
def on_change_with_fraction_domain(self, name=None):
cursor = Transaction().connection.cursor()
pool = Pool()
Analysis = pool.get('lims.analysis')
Fraction = pool.get('lims.fraction')
NotebookLine = pool.get('lims.notebook.line')
Notebook = pool.get('lims.notebook')
if not self.type:
return []
p_analysis_ids = []
for p_analysis in self.planification.analysis:
if p_analysis.type == 'analysis':
p_analysis_ids.append(p_analysis.id)
else:
p_analysis_ids.extend(
Analysis.get_included_analysis_analysis(p_analysis.id))
stored_fractions_ids = Fraction.get_stored_fractions()
special_type = 'con' if self.type == 'exist' else self.type
clause = [
('notebook.fraction.special_type', '=', special_type),
('notebook.fraction.id', 'in', stored_fractions_ids),
('analysis', 'in', p_analysis_ids),
]
if self.type == 'exist':
deadline = datetime.now() - relativedelta(days=5)
clause.extend([
('result', 'in', (None, '')),
('end_date', '=', None),
('annulment_date', '=', None),
('notebook.fraction.sample.date2', '>=', deadline),
])
notebook_lines = NotebookLine.search(clause)
if not notebook_lines:
return []
notebook_lines_ids = ', '.join(str(nl.id) for nl in notebook_lines)
cursor.execute('SELECT DISTINCT(n.fraction) '
'FROM "' + Notebook._table + '" n '
'INNER JOIN "' + NotebookLine._table + '" nl '
'ON nl.notebook = n.id '
'WHERE nl.id IN (' + notebook_lines_ids + ')')
return [x[0] for x in cursor.fetchall()]
@fields.depends('type', 'original_fraction', 'concentration_level',
'_parent_original_fraction.label',
'_parent_concentration_level.description')
def on_change_with_label(self, name=None):
Date = Pool().get('ir.date')
if self.type == 'exist':
return ''
label = ''
if self.original_fraction:
label += self.original_fraction.label
if self.concentration_level:
label += (' (' +
self.concentration_level.description + ')')
label += ' ' + str(Date.today())
return label
class AddFractionControl(Wizard):
'Add Fraction Control'
__name__ = 'lims.planification.add_fraction_con'
start = StateView('lims.planification.add_fraction_con.start',
'lims.lims_add_fraction_con_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Add', 'add', 'tryton-ok', default=True),
])
add = StateTransition()
def default_start(self, fields):
Config = Pool().get('lims.configuration')
config = Config(1)
defaults = {
'planification': Transaction().context['active_id'],
'concentration_level_invisible': True,
}
if (config.con_fraction_type and
config.con_fraction_type.control_charts):
defaults['concentration_level_invisible'] = False
return defaults
def transition_add(self):
fraction = self.start.original_fraction
if self.start.type != 'exist':
fraction = self.create_control()
self.add_control(fraction)
self.add_planification_detail(fraction)
return 'end'
def create_control(self):
pool = Pool()
Config = pool.get('lims.configuration')
LabWorkYear = pool.get('lims.lab.workyear')
Entry = pool.get('lims.entry')
Sample = pool.get('lims.sample')
Fraction = pool.get('lims.fraction')
Service = pool.get('lims.service')
Analysis = pool.get('lims.analysis')
NotebookLine = pool.get('lims.notebook.line')
EntryDetailAnalysis = pool.get('lims.entry.detail.analysis')
config = Config(1)
fraction_type = config.con_fraction_type
if not fraction_type:
raise UserError(gettext('lims.msg_no_con_fraction_type'))
if (fraction_type.control_charts and not
self.start.concentration_level):
raise UserError(gettext('lims.msg_no_concentration_level'))
workyear_id = LabWorkYear.find()
workyear = LabWorkYear(workyear_id)
if not workyear.default_entry_control:
raise UserError(gettext('lims.msg_no_entry_control'))
entry = Entry(workyear.default_entry_control.id)
original_fraction = self.start.original_fraction
original_sample = Sample(original_fraction.sample.id)
obj_description = self._get_obj_description(original_sample)
# new sample
new_sample, = Sample.copy([original_sample], default={
'entry': entry.id,
'date': datetime.now(),
'label': self.start.label,
'obj_description': obj_description,
'fractions': [],
})
# new fraction
new_fraction, = Fraction.copy([original_fraction], default={
'sample': new_sample.id,
'type': fraction_type.id,
'services': [],
'con_type': self.start.type,
'con_original_fraction': original_fraction.id,
})
# new services
p_analysis_ids = []
for p_analysis in self.start.planification.analysis:
if p_analysis.type == 'analysis':
p_analysis_ids.append(p_analysis.id)
else:
p_analysis_ids.extend(
Analysis.get_included_analysis_analysis(p_analysis.id))
services = Service.search([
('fraction', '=', original_fraction),
('annulled', '=', False),
])
for service in services:
if not Analysis.is_typified(service.analysis,
new_sample.product_type, new_sample.matrix):
continue
method_id = service.method and service.method.id or None
device_id = service.device and service.device.id or None
if service.analysis.type == 'analysis':
original_lines = NotebookLine.search([
('notebook.fraction', '=', original_fraction.id),
('analysis', '=', service.analysis.id),
('repetition', '=', 0),
], limit=1)
original_line = original_lines[0] if original_lines else None
if original_line:
method_id = original_line.method.id
if original_line.device:
device_id = original_line.device.id
new_service, = Service.copy([service], default={
'fraction': new_fraction.id,
'method': method_id,
'device': device_id,
})
# delete services/details not related to planification
to_delete = EntryDetailAnalysis.search([
('service', '=', new_service.id),
('analysis', 'not in', p_analysis_ids),
])
if to_delete:
with Transaction().set_user(0, set_context=True):
EntryDetailAnalysis.delete(to_delete)
if EntryDetailAnalysis.search_count([
('service', '=', new_service.id),
]) == 0:
with Transaction().set_user(0, set_context=True):
Service.delete([new_service])
# confirm fraction: new notebook and stock move
Fraction.confirm([new_fraction])
# Edit notebook lines
if fraction_type.control_charts:
notebook_lines = NotebookLine.search([
('notebook.fraction', '=', new_fraction.id),
])
if notebook_lines:
defaults = {
'concentration_level': self.start.concentration_level.id,
}
NotebookLine.write(notebook_lines, defaults)
# Generate repetition
if self.start.generate_repetition:
notebook_lines = NotebookLine.search([
('notebook.fraction', '=', new_fraction.id),
])
if notebook_lines:
self.generate_repetition(notebook_lines)
return new_fraction
def _get_obj_description(self, sample):
cursor = Transaction().connection.cursor()
ObjectiveDescription = Pool().get('lims.objective_description')
if not sample.product_type or not sample.matrix:
return None
cursor.execute('SELECT id '
'FROM "' + ObjectiveDescription._table + '" '
'WHERE product_type = %s '
'AND matrix = %s',
(sample.product_type.id, sample.matrix.id))
res = cursor.fetchone()
return res and res[0] or None
def generate_repetition(self, notebook_lines):
pool = Pool()
Analysis = pool.get('lims.analysis')
Notebook = pool.get('lims.notebook')
p_analysis_ids = []
for p_analysis in self.start.planification.analysis:
if p_analysis.type == 'analysis':
p_analysis_ids.append(p_analysis.id)
else:
p_analysis_ids.extend(
Analysis.get_included_analysis_analysis(p_analysis.id))
analysis_to_repeat = {}
for notebook_line in notebook_lines:
if notebook_line.analysis.id not in p_analysis_ids:
continue
if notebook_line.analysis.id not in analysis_to_repeat:
analysis_to_repeat[notebook_line.analysis.id] = notebook_line
elif (notebook_line.repetition >
analysis_to_repeat[notebook_line.analysis.id].repetition):
analysis_to_repeat[notebook_line.analysis.id] = notebook_line
notebook = Notebook(notebook_lines[0].notebook.id)
to_create = []
for analysis_id, nline in analysis_to_repeat.items():
to_create.append({
'analysis_detail': nline.analysis_detail.id,
'service': nline.service.id,
'analysis': analysis_id,
'analysis_origin': nline.analysis_origin,
'urgent': nline.urgent,
'repetition': nline.repetition + 1,
'laboratory': nline.laboratory.id,
'method': nline.method.id,
'device': nline.device.id if nline.device else None,
'initial_concentration': nline.initial_concentration,
'final_concentration': nline.final_concentration,
'initial_unit': (nline.initial_unit.id if
nline.initial_unit else None),
'final_unit': (nline.final_unit.id if
nline.final_unit else None),
'detection_limit': nline.detection_limit,
'quantification_limit': nline.quantification_limit,
'lower_limit': nline.lower_limit,
'upper_limit': nline.upper_limit,
'decimals': nline.decimals,
'significant_digits': nline.significant_digits,
'scientific_notation': nline.scientific_notation,
'report': nline.report,
'concentration_level': (nline.concentration_level.id if
nline.concentration_level else None),
'results_estimated_waiting': nline.results_estimated_waiting,
'department': (nline.department.id if
nline.department else None),
})
Notebook.write([notebook], {
'lines': [('create', to_create)],
})
def add_control(self, fraction):
Planification = Pool().get('lims.planification')
Planification.write([self.start.planification], {
'controls': [('add', [fraction.id])],
})
def add_planification_detail(self, fraction):
pool = Pool()
Analysis = pool.get('lims.analysis')
NotebookLine = pool.get('lims.notebook.line')
PlanificationDetail = pool.get('lims.planification.detail')
Service = pool.get('lims.service')
p_analysis_ids = []
for p_analysis in self.start.planification.analysis:
if p_analysis.type == 'analysis':
p_analysis_ids.append(p_analysis.id)
else:
p_analysis_ids.extend(
Analysis.get_included_analysis_analysis(p_analysis.id))
clause = [
('notebook.fraction', '=', fraction.id),
('analysis', 'in', p_analysis_ids),
('analysis.behavior', '!=', 'internal_relation'),
]
if self.start.type == 'exist':
clause.extend([
('result', 'in', (None, '')),
('end_date', '=', None),
('annulment_date', '=', None),
])
else:
clause.append(('planification', '=', None))
notebook_lines = NotebookLine.search(clause)
if notebook_lines:
details_to_create = {}
for nl in notebook_lines:
f = nl.notebook.fraction.id
s = nl.service.analysis.id
if (f, s) not in details_to_create:
details_to_create[(f, s)] = []
details_to_create[(f, s)].append({
'notebook_line': nl.id,
'planned_service': s,
'is_control': True,
})
if details_to_create:
for k, v in details_to_create.items():
details = PlanificationDetail.search([
('planification', '=', self.start.planification.id),
('fraction', '=', k[0]),
('service_analysis', '=', k[1]),
])
if details:
PlanificationDetail.write([details[0]], {
'details': [('create', v)],
})
else:
PlanificationDetail.create([{
'planification': self.start.planification.id,
'fraction': k[0],
'service_analysis': k[1],
'urgent': Service.is_service_urgent(k[0], k[1]),
'details': [('create', v)],
}])
class AddFractionRMBMZStart(ModelView):
'Add Fraction RM/BMZ'
__name__ = 'lims.planification.add_fraction_rm_bmz.start'
planification = fields.Many2One('lims.planification', 'Planification')
type = fields.Selection([
('rm', 'RM'),
('bmz', 'BMZ'),
], 'Control type', sort=False, required=True)
rm_bmz_type = fields.Selection([
('sla', 'SLA'),
('noref', 'No Reference'),
('exist', 'Existing RM/BMZ'),
], 'RM/BMZ type', sort=False, required=True)
reference_fraction = fields.Many2One('lims.fraction',
'Reference fraction', depends=['fraction_domain', 'rm_bmz_type'],
states={
'readonly': Bool(Equal(Eval('rm_bmz_type'), 'noref')),
'required': Not(Bool(Equal(Eval('rm_bmz_type'), 'noref'))),
}, domain=[('id', 'in', Eval('fraction_domain'))])
fraction_domain = fields.Function(fields.One2Many('lims.fraction',
None, 'Fraction domain'), 'on_change_with_fraction_domain')
product_type = fields.Many2One('lims.product.type', 'Product type',
states={
'readonly': Not(Bool(Equal(Eval('rm_bmz_type'), 'noref'))),
'required': Bool(Equal(Eval('rm_bmz_type'), 'noref'))},
domain=[('id', 'in', Eval('product_type_domain'))],
depends=['rm_bmz_type', 'product_type_domain'])
product_type_domain = fields.Function(fields.Many2Many(
'lims.product.type', None, None, 'Product type domain'),
'on_change_with_product_type_domain')
matrix = fields.Many2One('lims.matrix', 'Matrix', required=True,
states={
'readonly': Not(Bool(Equal(Eval('rm_bmz_type'), 'noref'))),
'required': Bool(Equal(Eval('rm_bmz_type'), 'noref'))},
domain=[('id', 'in', Eval('matrix_domain'))],
depends=['rm_bmz_type', 'matrix_domain'])
matrix_domain = fields.Function(fields.Many2Many('lims.matrix',
None, None, 'Matrix domain'),
'on_change_with_matrix_domain')
repetitions = fields.Integer('Repetitions',
states={'readonly': Or(Eval('type') == 'bmz',
Eval('rm_bmz_type') == 'exist')},
depends=['type', 'rm_bmz_type'])
label = fields.Char('Label', depends=['rm_bmz_type'], states={
'readonly': Eval('rm_bmz_type') == 'exist'})
concentration_level = fields.Many2One('lims.concentration.level',
'Concentration level', states={
'invisible': Bool(Eval('concentration_level_invisible')),
}, depends=['concentration_level_invisible'])
concentration_level_invisible = fields.Boolean(
'Concentration level invisible')
@fields.depends('type')
def on_change_with_concentration_level_invisible(self, name=None):
Config = Pool().get('lims.configuration')
config = Config(1)
if self.type == 'rm':
if (config.rm_fraction_type and
config.rm_fraction_type.control_charts):
return False
elif self.type == 'bmz':
if (config.bmz_fraction_type and
config.bmz_fraction_type.control_charts):
return False
return True
@fields.depends('planification', 'type', 'rm_bmz_type',
'_parent_planification.analysis')
def on_change_with_fraction_domain(self, name=None):
cursor = Transaction().connection.cursor()
pool = Pool()
Analysis = pool.get('lims.analysis')
Fraction = pool.get('lims.fraction')
NotebookLine = pool.get('lims.notebook.line')
Notebook = pool.get('lims.notebook')
if not self.type or not self.rm_bmz_type:
return []
if self.rm_bmz_type == 'noref':
return []
p_analysis_ids = []
for p_analysis in self.planification.analysis:
if p_analysis.type == 'analysis':
p_analysis_ids.append(p_analysis.id)
else:
p_analysis_ids.extend(
Analysis.get_included_analysis_analysis(p_analysis.id))
stored_fractions_ids = Fraction.get_stored_fractions()
special_type = 'sla' if self.rm_bmz_type == 'sla' else self.type
clause = [
('notebook.fraction.special_type', '=', special_type),
('notebook.fraction.id', 'in', stored_fractions_ids),
('analysis', 'in', p_analysis_ids),
]
if self.rm_bmz_type == 'exist':
deadline = datetime.now() - relativedelta(days=5)
clause.extend([
('result', 'in', (None, '')),
('end_date', '=', None),
('annulment_date', '=', None),
('notebook.fraction.sample.date2', '>=', deadline),
])
notebook_lines = NotebookLine.search(clause)
if not notebook_lines:
return []
notebook_lines_ids = ', '.join(str(nl.id) for nl in notebook_lines)
cursor.execute('SELECT DISTINCT(n.fraction) '
'FROM "' + Notebook._table + '" n '
'INNER JOIN "' + NotebookLine._table + '" nl '
'ON nl.notebook = n.id '
'WHERE nl.id IN (' + notebook_lines_ids + ')')
return [x[0] for x in cursor.fetchall()]
@staticmethod
def default_product_type_domain():
cursor = Transaction().connection.cursor()
Typification = Pool().get('lims.typification')
cursor.execute('SELECT DISTINCT(product_type) '
'FROM "' + Typification._table + '" '
'WHERE valid')
return [x[0] for x in cursor.fetchall()]
def on_change_with_product_type_domain(self, name=None):
return self.default_product_type_domain()
@fields.depends('product_type')
def on_change_product_type(self):
matrix = None
if self.product_type:
matrixs = self.on_change_with_matrix_domain()
if len(matrixs) == 1:
matrix = matrixs[0]
self.matrix = matrix
@fields.depends('product_type')
def on_change_with_matrix_domain(self, name=None):
cursor = Transaction().connection.cursor()
Typification = Pool().get('lims.typification')
if not self.product_type:
return []
cursor.execute('SELECT DISTINCT(matrix) '
'FROM "' + Typification._table + '" '
'WHERE product_type = %s '
'AND valid',
(self.product_type.id,))
return [x[0] for x in cursor.fetchall()]
@fields.depends('type', 'rm_bmz_type', 'reference_fraction',
'product_type', 'matrix', 'concentration_level',
'_parent_reference_fraction.label',
'_parent_concentration_level.description')
def on_change_with_label(self, name=None):
Date = Pool().get('ir.date')
if self.rm_bmz_type == 'exist':
return ''
label = ''
if self.type == 'rm':
label = 'RM'
if self.concentration_level:
label += (' (' +
self.concentration_level.description + ')')
elif self.type == 'bmz':
label = 'BMZ'
if self.rm_bmz_type == 'sla':
if self.reference_fraction:
label += (' ' +
self.reference_fraction.label)
label += ' ' + str(Date.today())
elif self.rm_bmz_type == 'noref':
label += ' ' + str(Date.today())
return label
class AddFractionRMBMZ(Wizard):
'Add Fraction RM/BMZ'
__name__ = 'lims.planification.add_fraction_rm_bmz'
start = StateView('lims.planification.add_fraction_rm_bmz.start',
'lims.lims_add_fraction_rm_bmz_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Add', 'add', 'tryton-ok', default=True),
])
add = StateTransition()
def default_start(self, fields):
defaults = {
'planification': Transaction().context['active_id'],
'concentration_level_invisible': True,
}
return defaults
def transition_add(self):
fraction = self.start.reference_fraction
if self.start.rm_bmz_type != 'exist':
fraction = self.create_control()
self.add_control(fraction)
self.add_planification_detail(fraction)
return 'end'
def create_control(self):
if self.start.rm_bmz_type == 'sla':
return self._create_control_sla()
if self.start.rm_bmz_type == 'noref':
return self._create_control_noref()
def _create_control_sla(self):
pool = Pool()
Config = pool.get('lims.configuration')
LabWorkYear = pool.get('lims.lab.workyear')
Entry = pool.get('lims.entry')
Sample = pool.get('lims.sample')
Fraction = pool.get('lims.fraction')
Service = pool.get('lims.service')
Analysis = pool.get('lims.analysis')
NotebookLine = pool.get('lims.notebook.line')
EntryDetailAnalysis = pool.get('lims.entry.detail.analysis')
config = Config(1)
if self.start.type == 'rm':
if not config.rm_fraction_type:
raise UserError(gettext('lims.msg_no_rm_fraction_type'))
fraction_type = config.rm_fraction_type
elif self.start.type == 'bmz':
if not config.bmz_fraction_type:
raise UserError(gettext('lims.msg_no_bmz_fraction_type'))
fraction_type = config.bmz_fraction_type
if (fraction_type.control_charts and not
self.start.concentration_level):
raise UserError(gettext('lims.msg_no_concentration_level'))
workyear_id = LabWorkYear.find()
workyear = LabWorkYear(workyear_id)
if not workyear.default_entry_control:
raise UserError(gettext('lims.msg_no_entry_control'))
entry = Entry(workyear.default_entry_control.id)
original_fraction = self.start.reference_fraction
original_sample = Sample(original_fraction.sample.id)
obj_description = self._get_obj_description(original_sample)
# new sample
new_sample, = Sample.copy([original_sample], default={
'entry': entry.id,
'date': datetime.now(),
'label': self.start.label,
'obj_description': obj_description,
'fractions': [],
})
# new fraction
fraction_default = {
'sample': new_sample.id,
'type': fraction_type.id,
'con_type': '',
'services': [],
}
if self.start.type == 'rm':
fraction_default['rm_type'] = 'sla'
fraction_default['rm_product_type'] = new_sample.product_type.id
fraction_default['rm_matrix'] = new_sample.matrix.id
fraction_default['rm_original_fraction'] = original_fraction.id
if self.start.type == 'bmz':
fraction_default['bmz_type'] = 'sla'
fraction_default['bmz_product_type'] = new_sample.product_type.id
fraction_default['bmz_matrix'] = new_sample.matrix.id
fraction_default['bmz_original_fraction'] = original_fraction.id
new_fraction, = Fraction.copy([original_fraction],
default=fraction_default)
# new services
p_analysis_ids = []
for p_analysis in self.start.planification.analysis:
if p_analysis.type == 'analysis':
p_analysis_ids.append(p_analysis.id)
else:
p_analysis_ids.extend(
Analysis.get_included_analysis_analysis(p_analysis.id))
services = Service.search([
('fraction', '=', original_fraction),
('annulled', '=', False),
])
for service in services:
if not Analysis.is_typified(service.analysis,
new_sample.product_type, new_sample.matrix):
continue
method_id = service.method and service.method.id or None
device_id = service.device and service.device.id or None
if service.analysis.type == 'analysis':
original_lines = NotebookLine.search([
('notebook.fraction', '=', original_fraction.id),
('analysis', '=', service.analysis.id),
('repetition', '=', 0),
], limit=1)
original_line = original_lines[0] if original_lines else None
if original_line:
method_id = original_line.method.id
if original_line.device:
device_id = original_line.device.id
new_service, = Service.copy([service], default={
'fraction': new_fraction.id,
'method': method_id,
'device': device_id,
})
# delete services/details not related to planification
to_delete = EntryDetailAnalysis.search([
('service', '=', new_service.id),
('analysis', 'not in', p_analysis_ids),
])
if to_delete:
with Transaction().set_user(0, set_context=True):
EntryDetailAnalysis.delete(to_delete)
if EntryDetailAnalysis.search_count([
('service', '=', new_service.id),
]) == 0:
with Transaction().set_user(0, set_context=True):
Service.delete([new_service])
# confirm fraction: new notebook and stock move
Fraction.confirm([new_fraction])
# Edit notebook lines
if fraction_type.control_charts:
notebook_lines = NotebookLine.search([
('notebook.fraction', '=', new_fraction.id),
])
if notebook_lines:
defaults = {
'concentration_level': self.start.concentration_level.id,
}
NotebookLine.write(notebook_lines, defaults)
if self.start.type == 'rm':
notebook_lines = NotebookLine.search([
('notebook.fraction', '=', new_fraction.id),
])
if notebook_lines:
defaults = {
'final_concentration': None,
'final_unit': None,
'detection_limit': None,
'quantification_limit': None,
'lower_limit': None,
'upper_limit': None,
}
if config.rm_start_uom:
defaults['initial_unit'] = config.rm_start_uom.id
NotebookLine.write(notebook_lines, defaults)
# Generate repetition
if self.start.repetitions and self.start.repetitions > 0:
notebook_lines = NotebookLine.search([
('notebook.fraction', '=', new_fraction.id),
])
if notebook_lines:
self.generate_repetition(notebook_lines,
self.start.repetitions)
return new_fraction
def _get_obj_description(self, sample):
cursor = Transaction().connection.cursor()
ObjectiveDescription = Pool().get('lims.objective_description')
if not sample.product_type or not sample.matrix:
return None
cursor.execute('SELECT id '
'FROM "' + ObjectiveDescription._table + '" '
'WHERE product_type = %s '
'AND matrix = %s',
(sample.product_type.id, sample.matrix.id))
res = cursor.fetchone()
return res and res[0] or None
def _create_control_noref(self):
pool = Pool()
Config = pool.get('lims.configuration')
LabWorkYear = pool.get('lims.lab.workyear')
Entry = pool.get('lims.entry')
Sample = pool.get('lims.sample')
Fraction = pool.get('lims.fraction')
Service = pool.get('lims.service')
Analysis = pool.get('lims.analysis')
NotebookLine = pool.get('lims.notebook.line')
config = Config(1)
if self.start.type == 'rm':
if not config.rm_fraction_type:
raise UserError(gettext('lims.msg_no_rm_fraction_type'))
fraction_type = config.rm_fraction_type
if (not fraction_type.default_package_type or
not fraction_type.default_fraction_state):
raise UserError(gettext(
'lims.msg_no_rm_default_configuration'))
elif self.start.type == 'bmz':
if not config.bmz_fraction_type:
raise UserError(gettext('lims.msg_no_bmz_fraction_type'))
fraction_type = config.bmz_fraction_type
if (not fraction_type.default_package_type or
not fraction_type.default_fraction_state):
raise UserError(gettext(
'lims.msg_no_bmz_default_configuration'))
if (fraction_type.control_charts and not
self.start.concentration_level):
raise UserError(gettext('lims.msg_no_concentration_level'))
workyear_id = LabWorkYear.find()
workyear = LabWorkYear(workyear_id)
if not workyear.default_entry_control:
raise UserError(gettext('lims.msg_no_entry_control'))
entry = Entry(workyear.default_entry_control.id)
if not entry.party.entry_zone and config.zone_required:
raise UserError(gettext('lims.msg_no_party_zone',
party=entry.party.rec_name))
zone_id = entry.party.entry_zone and entry.party.entry_zone.id or None
laboratory = self.start.planification.laboratory
obj_description = self._get_obj_description(self.start)
# new sample
new_sample, = Sample.create([{
'entry': entry.id,
'party': entry.party.id,
'date': datetime.now(),
'product_type': self.start.product_type.id,
'matrix': self.start.matrix.id,
'zone': zone_id,
'label': self.start.label,
'obj_description': obj_description,
'packages_quantity': 1,
'fractions': [],
}])
# new fraction
fraction_default = {
'sample': new_sample.id,
'type': fraction_type.id,
'storage_location': laboratory.related_location.id,
'packages_quantity': 1,
'package_type': fraction_type.default_package_type.id,
'fraction_state': fraction_type.default_fraction_state.id,
'services': [],
}
if fraction_type.max_storage_time:
fraction_default['storage_time'] = fraction_type.max_storage_time
elif laboratory.related_location.storage_time:
fraction_default['storage_time'] = (
laboratory.related_location.storage_time)
else:
fraction_default['storage_time'] = 3
if self.start.type == 'rm':
fraction_default['rm_type'] = 'noinitialrm'
fraction_default['rm_product_type'] = new_sample.product_type.id
fraction_default['rm_matrix'] = new_sample.matrix.id
if self.start.type == 'bmz':
fraction_default['bmz_type'] = 'noinitialbmz'
fraction_default['bmz_product_type'] = new_sample.product_type.id
fraction_default['bmz_matrix'] = new_sample.matrix.id
new_fraction, = Fraction.create([fraction_default])
# new services
services_default = []
for p_analysis in self.start.planification.analysis:
if not Analysis.is_typified(p_analysis,
new_sample.product_type, new_sample.matrix):
raise UserError(gettext('lims.msg_not_typified',
analysis=p_analysis.rec_name,
product_type=new_sample.product_type.rec_name,
matrix=new_sample.matrix.rec_name,
))
laboratory_id = (laboratory.id if p_analysis.type != 'group'
else None)
method_id = None
if new_sample.typification_domain:
for t in new_sample.typification_domain:
if (t.analysis.id == p_analysis.id and
t.by_default is True):
method_id = t.method.id
device_id = None
if p_analysis.devices:
for d in p_analysis.devices:
if (d.laboratory.id == laboratory.id and
d.by_default is True):
device_id = d.device.id
services_default.append({
'fraction': new_fraction.id,
'analysis': p_analysis.id,
'laboratory': laboratory_id,
'method': method_id,
'device': device_id,
})
for service in services_default:
new_service, = Service.create([service])
# new analysis details (on service create)
# confirm fraction: new notebook and stock move
Fraction.confirm([new_fraction])
# Edit notebook lines
if fraction_type.control_charts:
notebook_lines = NotebookLine.search([
('notebook.fraction', '=', new_fraction.id),
])
if notebook_lines:
defaults = {
'concentration_level': self.start.concentration_level.id,
}
NotebookLine.write(notebook_lines, defaults)
if self.start.type == 'rm':
notebook_lines = NotebookLine.search([
('notebook.fraction', '=', new_fraction.id),
])
if notebook_lines:
defaults = {
'final_concentration': None,
'final_unit': None,
'detection_limit': None,
'quantification_limit': None,
'lower_limit': None,
'upper_limit': None,
}
if config.rm_start_uom:
defaults['initial_unit'] = config.rm_start_uom.id
NotebookLine.write(notebook_lines, defaults)
# Generate repetition
if self.start.repetitions and self.start.repetitions > 0:
notebook_lines = NotebookLine.search([
('notebook.fraction', '=', new_fraction.id),
])
if notebook_lines:
self.generate_repetition(notebook_lines,
self.start.repetitions)
return new_fraction
def generate_repetition(self, notebook_lines, repetitions):
pool = Pool()
Analysis = pool.get('lims.analysis')
Notebook = pool.get('lims.notebook')
p_analysis_ids = []
for p_analysis in self.start.planification.analysis:
if p_analysis.type == 'analysis':
p_analysis_ids.append(p_analysis.id)
else:
p_analysis_ids.extend(
Analysis.get_included_analysis_analysis(p_analysis.id))
analysis_to_repeat = {}
for notebook_line in notebook_lines:
if notebook_line.analysis.id not in p_analysis_ids:
continue
if notebook_line.analysis.id not in analysis_to_repeat:
analysis_to_repeat[notebook_line.analysis.id] = notebook_line
elif (notebook_line.repetition >
analysis_to_repeat[notebook_line.analysis.id].repetition):
analysis_to_repeat[notebook_line.analysis.id] = notebook_line
notebook = Notebook(notebook_lines[0].notebook.id)
to_create = []
for nline in analysis_to_repeat.values():
for i in range(1, repetitions + 1):
to_create.append({
'analysis_detail': nline.analysis_detail.id,
'service': nline.service.id,
'analysis': nline.analysis.id,
'analysis_origin': nline.analysis_origin,
'urgent': nline.urgent,
'repetition': nline.repetition + i,
'laboratory': nline.laboratory.id,
'method': nline.method.id,
'device': nline.device.id if nline.device else None,
'initial_concentration': nline.initial_concentration,
'final_concentration': nline.final_concentration,
'initial_unit': (nline.initial_unit.id if
nline.initial_unit else None),
'final_unit': (nline.final_unit.id if
nline.final_unit else None),
'detection_limit': nline.detection_limit,
'quantification_limit': nline.quantification_limit,
'lower_limit': nline.lower_limit,
'upper_limit': nline.upper_limit,
'decimals': nline.decimals,
'significant_digits': nline.significant_digits,
'scientific_notation': nline.scientific_notation,
'report': nline.report,
'concentration_level': (nline.concentration_level.id if
nline.concentration_level else None),
'results_estimated_waiting': (
nline.results_estimated_waiting),
'department': (nline.department.id if
nline.department else None),
})
Notebook.write([notebook], {
'lines': [('create', to_create)],
})
def add_control(self, fraction):
Planification = Pool().get('lims.planification')
Planification.write([self.start.planification], {
'controls': [('add', [fraction.id])],
})
def add_planification_detail(self, fraction):
pool = Pool()
Analysis = pool.get('lims.analysis')
NotebookLine = pool.get('lims.notebook.line')
PlanificationDetail = pool.get('lims.planification.detail')
Service = pool.get('lims.service')
p_analysis_ids = []
for p_analysis in self.start.planification.analysis:
if p_analysis.type == 'analysis':
p_analysis_ids.append(p_analysis.id)
else:
p_analysis_ids.extend(
Analysis.get_included_analysis_analysis(p_analysis.id))
clause = [
('notebook.fraction', '=', fraction.id),
('analysis', 'in', p_analysis_ids),
('analysis.behavior', '!=', 'internal_relation'),
]
if self.start.rm_bmz_type == 'exist':
clause.extend([
('result', 'in', (None, '')),
('end_date', '=', None),
('annulment_date', '=', None),
])
else:
clause.append(('planification', '=', None))
notebook_lines = NotebookLine.search(clause)
if notebook_lines:
details_to_create = {}
for nl in notebook_lines:
f = nl.notebook.fraction.id
s = nl.service.analysis.id
if (f, s) not in details_to_create:
details_to_create[(f, s)] = []
details_to_create[(f, s)].append({
'notebook_line': nl.id,
'planned_service': s,
'is_control': True,
})
if details_to_create:
for k, v in details_to_create.items():
details = PlanificationDetail.search([
('planification', '=', self.start.planification.id),
('fraction', '=', k[0]),
('service_analysis', '=', k[1]),
])
if details:
PlanificationDetail.write([details[0]], {
'details': [('create', v)],
})
else:
PlanificationDetail.create([{
'planification': self.start.planification.id,
'fraction': k[0],
'service_analysis': k[1],
'urgent': Service.is_service_urgent(k[0], k[1]),
'details': [('create', v)],
}])
class AddFractionBREStart(ModelView):
'Add Fraction BRE'
__name__ = 'lims.planification.add_fraction_bre.start'
planification = fields.Many2One('lims.planification', 'Planification')
type = fields.Selection([
('new', 'New BRE'),
('exist', 'Existing BRE'),
], 'BRE Type', sort=False, required=True)
bre_fraction = fields.Many2One('lims.fraction',
'BRE fraction', depends=['fraction_domain', 'type'],
states={
'readonly': Bool(Equal(Eval('type'), 'new')),
'required': Bool(Equal(Eval('type'), 'exist')),
}, domain=[('id', 'in', Eval('fraction_domain'))])
fraction_domain = fields.Function(fields.One2Many('lims.fraction',
None, 'Fraction domain'), 'on_change_with_fraction_domain')
product_type = fields.Many2One('lims.product.type', 'Product type',
states={'required': Bool(Equal(Eval('type'), 'new'))},
domain=[('id', 'in', Eval('product_type_domain'))],
depends=['type', 'product_type_domain'])
product_type_domain = fields.Function(fields.Many2Many(
'lims.product.type', None, None, 'Product type domain'),
'on_change_with_product_type_domain')
matrix = fields.Many2One('lims.matrix', 'Matrix', required=True,
states={'required': Bool(Equal(Eval('type'), 'new'))},
domain=[('id', 'in', Eval('matrix_domain'))],
depends=['type', 'matrix_domain'])
matrix_domain = fields.Function(fields.Many2Many('lims.matrix',
None, None, 'Matrix domain'),
'on_change_with_matrix_domain')
reagents = fields.One2Many('lims.fraction.reagent', None,
'Reagents', states={
'readonly': Bool(Equal(Eval('type'), 'exist')),
}, depends=['type'], required=True)
label = fields.Char('Label', depends=['type'], states={
'readonly': Eval('type') == 'exist'})
concentration_level = fields.Many2One('lims.concentration.level',
'Concentration level', states={
'invisible': Bool(Eval('concentration_level_invisible')),
}, depends=['concentration_level_invisible'])
concentration_level_invisible = fields.Boolean(
'Concentration level invisible')
@fields.depends('planification', 'type', '_parent_planification.analysis')
def on_change_with_fraction_domain(self, name=None):
pool = Pool()
Analysis = pool.get('lims.analysis')
Fraction = pool.get('lims.fraction')
NotebookLine = pool.get('lims.notebook.line')
if self.type != 'exist':
return []
p_analysis_ids = []
for p_analysis in self.planification.analysis:
if p_analysis.type == 'analysis':
p_analysis_ids.append(p_analysis.id)
else:
p_analysis_ids.extend(
Analysis.get_included_analysis_analysis(p_analysis.id))
stored_fractions_ids = Fraction.get_stored_fractions()
deadline = datetime.now() - relativedelta(days=5)
clause = [
('notebook.fraction.special_type', '=', 'bre'),
('notebook.fraction.id', 'in', stored_fractions_ids),
('analysis', 'in', p_analysis_ids),
('result', 'in', (None, '')),
('end_date', '=', None),
('annulment_date', '=', None),
('notebook.fraction.sample.date2', '>=', deadline),
]
notebook_lines = NotebookLine.search(clause)
fractions = [nl.notebook.fraction.id for nl in notebook_lines]
return list(set(fractions))
@staticmethod
def default_product_type_domain():
cursor = Transaction().connection.cursor()
Typification = Pool().get('lims.typification')
cursor.execute('SELECT DISTINCT(product_type) '
'FROM "' + Typification._table + '" '
'WHERE valid')
return [x[0] for x in cursor.fetchall()]
def on_change_with_product_type_domain(self, name=None):
return self.default_product_type_domain()
@fields.depends('product_type')
def on_change_product_type(self):
matrix = None
if self.product_type:
matrixs = self.on_change_with_matrix_domain()
if len(matrixs) == 1:
matrix = matrixs[0]
self.matrix = matrix
@fields.depends('product_type')
def on_change_with_matrix_domain(self, name=None):
cursor = Transaction().connection.cursor()
Typification = Pool().get('lims.typification')
if not self.product_type:
return []
cursor.execute('SELECT DISTINCT(matrix) '
'FROM "' + Typification._table + '" '
'WHERE product_type = %s '
'AND valid',
(self.product_type.id,))
return [x[0] for x in cursor.fetchall()]
@fields.depends('type', 'product_type', 'matrix')
def on_change_with_label(self, name=None):
Date = Pool().get('ir.date')
if self.type == 'exist':
return ''
label = 'BRE'
label += ' ' + str(Date.today())
return label
class AddFractionBRE(Wizard):
'Add Fraction BRE'
__name__ = 'lims.planification.add_fraction_bre'
start = StateView('lims.planification.add_fraction_bre.start',
'lims.lims_add_fraction_bre_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Add', 'add', 'tryton-ok', default=True),
])
add = StateTransition()
def default_start(self, fields):
Config = Pool().get('lims.configuration')
config = Config(1)
defaults = {
'planification': Transaction().context['active_id'],
'concentration_level_invisible': True,
}
if (config.bre_fraction_type and
config.bre_fraction_type.control_charts):
defaults['concentration_level_invisible'] = False
return defaults
def transition_add(self):
fraction = self.start.bre_fraction
if self.start.type == 'new':
fraction = self.create_control()
self.add_control(fraction)
self.add_planification_detail(fraction)
return 'end'
def create_control(self):
pool = Pool()
Config = pool.get('lims.configuration')
LabWorkYear = pool.get('lims.lab.workyear')
Entry = pool.get('lims.entry')
Sample = pool.get('lims.sample')
Fraction = pool.get('lims.fraction')
Service = pool.get('lims.service')
Analysis = pool.get('lims.analysis')
NotebookLine = pool.get('lims.notebook.line')
config = Config(1)
if not config.bre_fraction_type:
raise UserError(gettext('lims.msg_no_bre_fraction_type'))
fraction_type = config.bre_fraction_type
if (not fraction_type.default_package_type or
not fraction_type.default_fraction_state):
raise UserError(gettext('lims.msg_no_bre_default_configuration'))
if (fraction_type.control_charts and not
self.start.concentration_level):
raise UserError(gettext('lims.msg_no_concentration_level'))
workyear_id = LabWorkYear.find()
workyear = LabWorkYear(workyear_id)
if not workyear.default_entry_control:
raise UserError(gettext('lims.msg_no_entry_control'))
entry = Entry(workyear.default_entry_control.id)
if not entry.party.entry_zone and config.zone_required:
raise UserError(gettext('lims.msg_no_party_zone',
party=entry.party.rec_name))
zone_id = entry.party.entry_zone and entry.party.entry_zone.id or None
laboratory = self.start.planification.laboratory
obj_description = self._get_obj_description(self.start)
# new sample
new_sample, = Sample.create([{
'entry': entry.id,
'party': entry.party.id,
'date': datetime.now(),
'product_type': self.start.product_type.id,
'matrix': self.start.matrix.id,
'zone': zone_id,
'label': self.start.label,
'obj_description': obj_description,
'packages_quantity': 1,
'fractions': [],
}])
# new fraction
bre_reagents = [{
'product': r.product.id,
'lot': r.lot.id if r.lot else None,
} for r in self.start.reagents]
fraction_default = {
'sample': new_sample.id,
'type': fraction_type.id,
'storage_location': laboratory.related_location.id,
'packages_quantity': 1,
'package_type': fraction_type.default_package_type.id,
'fraction_state': fraction_type.default_fraction_state.id,
'services': [],
'bre_product_type': new_sample.product_type.id,
'bre_matrix': new_sample.matrix.id,
'bre_reagents': [('create', bre_reagents)],
}
if fraction_type.max_storage_time:
fraction_default['storage_time'] = fraction_type.max_storage_time
elif laboratory.related_location.storage_time:
fraction_default['storage_time'] = (
laboratory.related_location.storage_time)
else:
fraction_default['storage_time'] = 3
new_fraction, = Fraction.create([fraction_default])
# new services
services_default = []
for p_analysis in self.start.planification.analysis:
if not Analysis.is_typified(p_analysis,
new_sample.product_type, new_sample.matrix):
raise UserError(gettext('lims.msg_not_typified',
analysis=p_analysis.rec_name,
product_type=new_sample.product_type.rec_name,
matrix=new_sample.matrix.rec_name,
))
laboratory_id = (laboratory.id if p_analysis.type != 'group'
else None)
method_id = None
if new_sample.typification_domain:
for t in new_sample.typification_domain:
if (t.analysis.id == p_analysis.id and
t.by_default is True):
method_id = t.method.id
device_id = None
if p_analysis.devices:
for d in p_analysis.devices:
if (d.laboratory.id == laboratory.id and
d.by_default is True):
device_id = d.device.id
services_default.append({
'fraction': new_fraction.id,
'analysis': p_analysis.id,
'laboratory': laboratory_id,
'method': method_id,
'device': device_id,
})
for service in services_default:
new_service, = Service.create([service])
# new analysis details (on service create)
# confirm fraction: new notebook and stock move
Fraction.confirm([new_fraction])
# Edit notebook lines
if fraction_type.control_charts:
notebook_lines = NotebookLine.search([
('notebook.fraction', '=', new_fraction.id),
])
if notebook_lines:
defaults = {
'concentration_level': self.start.concentration_level.id,
}
NotebookLine.write(notebook_lines, defaults)
return new_fraction
def _get_obj_description(self, sample):
cursor = Transaction().connection.cursor()
ObjectiveDescription = Pool().get('lims.objective_description')
if not sample.product_type or not sample.matrix:
return None
cursor.execute('SELECT id '
'FROM "' + ObjectiveDescription._table + '" '
'WHERE product_type = %s '
'AND matrix = %s',
(sample.product_type.id, sample.matrix.id))
res = cursor.fetchone()
return res and res[0] or None
def add_control(self, fraction):
Planification = Pool().get('lims.planification')
Planification.write([self.start.planification], {
'controls': [('add', [fraction.id])],
})
def add_planification_detail(self, fraction):
pool = Pool()
Analysis = pool.get('lims.analysis')
NotebookLine = pool.get('lims.notebook.line')
PlanificationDetail = pool.get('lims.planification.detail')
Service = pool.get('lims.service')
p_analysis_ids = []
for p_analysis in self.start.planification.analysis:
if p_analysis.type == 'analysis':
p_analysis_ids.append(p_analysis.id)
else:
p_analysis_ids.extend(
Analysis.get_included_analysis_analysis(p_analysis.id))
clause = [
('notebook.fraction', '=', fraction.id),
('analysis', 'in', p_analysis_ids),
('analysis.behavior', '!=', 'internal_relation'),
]
if self.start.type == 'exist':
clause.extend([
('result', 'in', (None, '')),
('end_date', '=', None),
('annulment_date', '=', None),
])
else:
clause.append(('planification', '=', None))
notebook_lines = NotebookLine.search(clause)
if notebook_lines:
details_to_create = {}
for nl in notebook_lines:
f = nl.notebook.fraction.id
s = nl.service.analysis.id
if (f, s) not in details_to_create:
details_to_create[(f, s)] = []
details_to_create[(f, s)].append({
'notebook_line': nl.id,
'planned_service': s,
'is_control': True,
})
if details_to_create:
for k, v in details_to_create.items():
details = PlanificationDetail.search([
('planification', '=', self.start.planification.id),
('fraction', '=', k[0]),
('service_analysis', '=', k[1]),
])
if details:
PlanificationDetail.write([details[0]], {
'details': [('create', v)],
})
else:
PlanificationDetail.create([{
'planification': self.start.planification.id,
'fraction': k[0],
'service_analysis': k[1],
'urgent': Service.is_service_urgent(k[0], k[1]),
'details': [('create', v)],
}])
class AddFractionMRTStart(ModelView):
'Add Fraction MRT'
__name__ = 'lims.planification.add_fraction_mrt.start'
planification = fields.Many2One('lims.planification', 'Planification')
type = fields.Selection([
('new', 'New MRT'),
('exist', 'Existing MRT'),
], 'MRT Type', sort=False, required=True)
mrt_fraction = fields.Many2One('lims.fraction',
'MRT fraction', depends=['fraction_domain', 'type'],
states={
'readonly': Bool(Equal(Eval('type'), 'new')),
'required': Bool(Equal(Eval('type'), 'exist')),
}, domain=[('id', 'in', Eval('fraction_domain'))])
fraction_domain = fields.Function(fields.One2Many('lims.fraction',
None, 'Fraction domain'), 'on_change_with_fraction_domain')
product_type = fields.Many2One('lims.product.type', 'Product type',
states={'required': Bool(Equal(Eval('type'), 'new'))},
domain=[('id', 'in', Eval('product_type_domain'))],
depends=['type', 'product_type_domain'])
product_type_domain = fields.Function(fields.Many2Many(
'lims.product.type', None, None, 'Product type domain'),
'on_change_with_product_type_domain')
matrix = fields.Many2One('lims.matrix', 'Matrix', required=True,
states={'required': Bool(Equal(Eval('type'), 'new'))},
domain=[('id', 'in', Eval('matrix_domain'))],
depends=['type', 'matrix_domain'])
matrix_domain = fields.Function(fields.Many2Many('lims.matrix',
None, None, 'Matrix domain'),
'on_change_with_matrix_domain')
repetitions = fields.Integer('Repetitions')
label = fields.Char('Label', depends=['type'], states={
'readonly': Eval('type') == 'exist'})
concentration_level = fields.Many2One('lims.concentration.level',
'Concentration level', states={
'invisible': Bool(Eval('concentration_level_invisible')),
}, depends=['concentration_level_invisible'])
concentration_level_invisible = fields.Boolean(
'Concentration level invisible')
@fields.depends('planification', 'type', '_parent_planification.analysis')
def on_change_with_fraction_domain(self, name=None):
pool = Pool()
Analysis = pool.get('lims.analysis')
Fraction = pool.get('lims.fraction')
NotebookLine = pool.get('lims.notebook.line')
if self.type != 'exist':
return []
p_analysis_ids = []
for p_analysis in self.planification.analysis:
if p_analysis.type == 'analysis':
p_analysis_ids.append(p_analysis.id)
else:
p_analysis_ids.extend(
Analysis.get_included_analysis_analysis(p_analysis.id))
stored_fractions_ids = Fraction.get_stored_fractions()
deadline = datetime.now() - relativedelta(days=5)
clause = [
('notebook.fraction.special_type', '=', 'mrt'),
('notebook.fraction.id', 'in', stored_fractions_ids),
('analysis', 'in', p_analysis_ids),
('result', 'in', (None, '')),
('end_date', '=', None),
('annulment_date', '=', None),
('notebook.fraction.sample.date2', '>=', deadline),
]
notebook_lines = NotebookLine.search(clause)
fractions = [nl.notebook.fraction.id for nl in notebook_lines]
return list(set(fractions))
@staticmethod
def default_product_type_domain():
cursor = Transaction().connection.cursor()
Typification = Pool().get('lims.typification')
cursor.execute('SELECT DISTINCT(product_type) '
'FROM "' + Typification._table + '" '
'WHERE valid')
return [x[0] for x in cursor.fetchall()]
def on_change_with_product_type_domain(self, name=None):
return self.default_product_type_domain()
@fields.depends('product_type')
def on_change_product_type(self):
matrix = None
if self.product_type:
matrixs = self.on_change_with_matrix_domain()
if len(matrixs) == 1:
matrix = matrixs[0]
self.matrix = matrix
@fields.depends('product_type')
def on_change_with_matrix_domain(self, name=None):
cursor = Transaction().connection.cursor()
Typification = Pool().get('lims.typification')
if not self.product_type:
return []
cursor.execute('SELECT DISTINCT(matrix) '
'FROM "' + Typification._table + '" '
'WHERE product_type = %s '
'AND valid',
(self.product_type.id,))
return [x[0] for x in cursor.fetchall()]
@fields.depends('type', 'product_type', 'matrix')
def on_change_with_label(self, name=None):
Date = Pool().get('ir.date')
if self.type == 'exist':
return ''
label = 'MRT'
label += ' ' + str(Date.today())
return label
class AddFractionMRT(Wizard):
'Add Fraction MRT'
__name__ = 'lims.planification.add_fraction_mrt'
start = StateView('lims.planification.add_fraction_mrt.start',
'lims.lims_add_fraction_mrt_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Add', 'add', 'tryton-ok', default=True),
])
add = StateTransition()
def default_start(self, fields):
Config = Pool().get('lims.configuration')
config = Config(1)
defaults = {
'planification': Transaction().context['active_id'],
'concentration_level_invisible': True,
}
if (config.mrt_fraction_type and
config.mrt_fraction_type.control_charts):
defaults['concentration_level_invisible'] = False
return defaults
def transition_add(self):
fraction = self.start.mrt_fraction
if self.start.type == 'new':
fraction = self.create_control()
self.add_control(fraction)
self.add_planification_detail(fraction)
return 'end'
def create_control(self):
pool = Pool()
Config = pool.get('lims.configuration')
LabWorkYear = pool.get('lims.lab.workyear')
Entry = pool.get('lims.entry')
Sample = pool.get('lims.sample')
Fraction = pool.get('lims.fraction')
Service = pool.get('lims.service')
Analysis = pool.get('lims.analysis')
NotebookLine = pool.get('lims.notebook.line')
config = Config(1)
if not config.mrt_fraction_type:
raise UserError(gettext('lims.msg_no_mrt_fraction_type'))
fraction_type = config.mrt_fraction_type
if (not fraction_type.default_package_type or
not fraction_type.default_fraction_state):
raise UserError(gettext('lims.msg_no_mrt_default_configuration'))
if (fraction_type.control_charts and not
self.start.concentration_level):
raise UserError(gettext('lims.msg_no_concentration_level'))
workyear_id = LabWorkYear.find()
workyear = LabWorkYear(workyear_id)
if not workyear.default_entry_control:
raise UserError(gettext('lims.msg_no_entry_control'))
entry = Entry(workyear.default_entry_control.id)
if not entry.party.entry_zone and config.zone_required:
raise UserError(gettext('lims.msg_no_party_zone',
party=entry.party.rec_name))
zone_id = entry.party.entry_zone and entry.party.entry_zone.id or None
laboratory = self.start.planification.laboratory
obj_description = self._get_obj_description(self.start)
# new sample
new_sample, = Sample.create([{
'entry': entry.id,
'party': entry.party.id,
'date': datetime.now(),
'product_type': self.start.product_type.id,
'matrix': self.start.matrix.id,
'zone': zone_id,
'label': self.start.label,
'obj_description': obj_description,
'packages_quantity': 1,
'fractions': [],
}])
# new fraction
fraction_default = {
'sample': new_sample.id,
'type': fraction_type.id,
'storage_location': laboratory.related_location.id,
'packages_quantity': 1,
'package_type': fraction_type.default_package_type.id,
'fraction_state': fraction_type.default_fraction_state.id,
'services': [],
'mrt_product_type': new_sample.product_type.id,
'mrt_matrix': new_sample.matrix.id,
}
if fraction_type.max_storage_time:
fraction_default['storage_time'] = fraction_type.max_storage_time
elif laboratory.related_location.storage_time:
fraction_default['storage_time'] = (
laboratory.related_location.storage_time)
else:
fraction_default['storage_time'] = 3
new_fraction, = Fraction.create([fraction_default])
# new services
services_default = []
for p_analysis in self.start.planification.analysis:
if not Analysis.is_typified(p_analysis,
new_sample.product_type, new_sample.matrix):
raise UserError(gettext('lims.msg_not_typified',
analysis=p_analysis.rec_name,
product_type=new_sample.product_type.rec_name,
matrix=new_sample.matrix.rec_name,
))
laboratory_id = (laboratory.id if p_analysis.type != 'group'
else None)
method_id = None
if new_sample.typification_domain:
for t in new_sample.typification_domain:
if (t.analysis.id == p_analysis.id and
t.by_default is True):
method_id = t.method.id
device_id = None
if p_analysis.devices:
for d in p_analysis.devices:
if (d.laboratory.id == laboratory.id and
d.by_default is True):
device_id = d.device.id
services_default.append({
'fraction': new_fraction.id,
'analysis': p_analysis.id,
'laboratory': laboratory_id,
'method': method_id,
'device': device_id,
})
for service in services_default:
new_service, = Service.create([service])
# new analysis details (on service create)
# confirm fraction: new notebook and stock move
Fraction.confirm([new_fraction])
# Edit notebook lines
if fraction_type.control_charts:
notebook_lines = NotebookLine.search([
('notebook.fraction', '=', new_fraction.id),
])
if notebook_lines:
defaults = {
'concentration_level': self.start.concentration_level.id,
}
NotebookLine.write(notebook_lines, defaults)
# Generate repetition
if self.start.repetitions and self.start.repetitions > 0:
notebook_lines = NotebookLine.search([
('notebook.fraction', '=', new_fraction.id),
])
if notebook_lines:
self.generate_repetition(notebook_lines,
self.start.repetitions)
return new_fraction
def _get_obj_description(self, sample):
cursor = Transaction().connection.cursor()
ObjectiveDescription = Pool().get('lims.objective_description')
if not sample.product_type or not sample.matrix:
return None
cursor.execute('SELECT id '
'FROM "' + ObjectiveDescription._table + '" '
'WHERE product_type = %s '
'AND matrix = %s',
(sample.product_type.id, sample.matrix.id))
res = cursor.fetchone()
return res and res[0] or None
def generate_repetition(self, notebook_lines, repetitions):
pool = Pool()
Analysis = pool.get('lims.analysis')
Notebook = pool.get('lims.notebook')
p_analysis_ids = []
for p_analysis in self.start.planification.analysis:
if p_analysis.type == 'analysis':
p_analysis_ids.append(p_analysis.id)
else:
p_analysis_ids.extend(
Analysis.get_included_analysis_analysis(p_analysis.id))
analysis_to_repeat = {}
for notebook_line in notebook_lines:
if notebook_line.analysis.id not in p_analysis_ids:
continue
if notebook_line.analysis.id not in analysis_to_repeat:
analysis_to_repeat[notebook_line.analysis.id] = notebook_line
elif (notebook_line.repetition >
analysis_to_repeat[notebook_line.analysis.id].repetition):
analysis_to_repeat[notebook_line.analysis.id] = notebook_line
notebook = Notebook(notebook_lines[0].notebook.id)
to_create = []
for nline in analysis_to_repeat.values():
for i in range(1, repetitions + 1):
to_create.append({
'analysis_detail': nline.analysis_detail.id,
'service': nline.service.id,
'analysis': nline.analysis.id,
'analysis_origin': nline.analysis_origin,
'urgent': nline.urgent,
'repetition': nline.repetition + i,
'laboratory': nline.laboratory.id,
'method': nline.method.id,
'device': nline.device.id if nline.device else None,
'initial_concentration': nline.initial_concentration,
'final_concentration': nline.final_concentration,
'initial_unit': (nline.initial_unit.id if
nline.initial_unit else None),
'final_unit': (nline.final_unit.id if
nline.final_unit else None),
'detection_limit': nline.detection_limit,
'quantification_limit': nline.quantification_limit,
'lower_limit': nline.lower_limit,
'upper_limit': nline.upper_limit,
'decimals': nline.decimals,
'significant_digits': nline.significant_digits,
'scientific_notation': nline.scientific_notation,
'report': nline.report,
'concentration_level': (nline.concentration_level.id if
nline.concentration_level else None),
'results_estimated_waiting': (
nline.results_estimated_waiting),
'department': (nline.department.id if
nline.department else None),
})
Notebook.write([notebook], {
'lines': [('create', to_create)],
})
def add_control(self, fraction):
Planification = Pool().get('lims.planification')
Planification.write([self.start.planification], {
'controls': [('add', [fraction.id])],
})
def add_planification_detail(self, fraction):
pool = Pool()
Analysis = pool.get('lims.analysis')
NotebookLine = pool.get('lims.notebook.line')
PlanificationDetail = pool.get('lims.planification.detail')
Service = pool.get('lims.service')
p_analysis_ids = []
for p_analysis in self.start.planification.analysis:
if p_analysis.type == 'analysis':
p_analysis_ids.append(p_analysis.id)
else:
p_analysis_ids.extend(
Analysis.get_included_analysis_analysis(p_analysis.id))
clause = [
('notebook.fraction', '=', fraction.id),
('analysis', 'in', p_analysis_ids),
('analysis.behavior', '!=', 'internal_relation'),
]
if self.start.type == 'exist':
clause.extend([
('result', 'in', (None, '')),
('end_date', '=', None),
('annulment_date', '=', None),
])
else:
clause.append(('planification', '=', None))
notebook_lines = NotebookLine.search(clause)
if notebook_lines:
details_to_create = {}
for nl in notebook_lines:
f = nl.notebook.fraction.id
s = nl.service.analysis.id
if (f, s) not in details_to_create:
details_to_create[(f, s)] = []
details_to_create[(f, s)].append({
'notebook_line': nl.id,
'planned_service': s,
'is_control': True,
})
if details_to_create:
for k, v in details_to_create.items():
details = PlanificationDetail.search([
('planification', '=', self.start.planification.id),
('fraction', '=', k[0]),
('service_analysis', '=', k[1]),
])
if details:
PlanificationDetail.write([details[0]], {
'details': [('create', v)],
})
else:
PlanificationDetail.create([{
'planification': self.start.planification.id,
'fraction': k[0],
'service_analysis': k[1],
'urgent': Service.is_service_urgent(k[0], k[1]),
'details': [('create', v)],
}])
class RemoveControlStart(ModelView):
'Remove Control'
__name__ = 'lims.planification.remove_control.start'
controls = fields.Many2Many('lims.fraction', None, None, 'Controls',
required=True, domain=[('id', 'in', Eval('controls_domain'))],
depends=['controls_domain'])
controls_domain = fields.Many2Many('lims.fraction', None, None,
'Controls domain')
class RemoveControl(Wizard):
'Remove Control'
__name__ = 'lims.planification.remove_control'
start = StateView('lims.planification.remove_control.start',
'lims.lims_remove_control_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Remove', 'remove', 'tryton-ok', default=True),
])
remove = StateTransition()
def default_start(self, fields):
Planification = Pool().get('lims.planification')
planification = Planification(Transaction().context['active_id'])
controls_domain = []
for c in planification.controls:
controls_domain.append(c.id)
return {
'controls_domain': controls_domain,
}
def transition_remove(self):
planification_id = Transaction().context['active_id']
control_ids = [c.id for c in self.start.controls]
self._unlink_controls(planification_id, control_ids)
return 'end'
def _unlink_controls(self, planification_id, control_ids):
pool = Pool()
PlanificationFraction = pool.get('lims.planification-fraction')
PlanificationDetail = pool.get('lims.planification.detail')
controls = PlanificationFraction.search([
('planification', '=', planification_id),
('fraction', 'in', control_ids),
])
if controls:
PlanificationFraction.delete(controls)
controls_details = PlanificationDetail.search([
('planification', '=', planification_id),
('fraction', 'in', control_ids),
])
if controls_details:
PlanificationDetail.delete(controls_details)
class AddAnalysisStart(ModelView):
'Add Analysis'
__name__ = 'lims.planification.add_analysis.start'
laboratory = fields.Many2One('lims.laboratory', 'Laboratory')
date_from = fields.Date('Date from', readonly=True)
date_to = fields.Date('Date to', readonly=True)
analysis = fields.Many2Many('lims.analysis', None, None,
'Analysis/Sets/Groups', required=True,
domain=[('id', 'in', Eval('analysis_domain'))],
context={'date_from': Eval('date_from'), 'date_to': Eval('date_to')},
depends=['analysis_domain', 'date_from', 'date_to'])
analysis_domain = fields.Many2Many('lims.analysis', None, None,
'Analysis domain')
class AddAnalysis(Wizard):
'Add Analysis'
__name__ = 'lims.planification.add_analysis'
start = StateView('lims.planification.add_analysis.start',
'lims.lims_add_analysis_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Add', 'add', 'tryton-ok', default=True),
])
add = StateTransition()
def default_start(self, fields):
Planification = Pool().get('lims.planification')
planification = Planification(Transaction().context['active_id'])
analysis_domain = self._get_analysis_domain(planification)
return {
'laboratory': planification.laboratory.id,
'analysis_domain': analysis_domain,
'date_from': planification.date_from,
'date_to': planification.date_to,
}
@staticmethod
def _get_analysis_domain(planification):
pool = Pool()
Planification = pool.get('lims.planification')
Analysis = pool.get('lims.analysis')
asg_list = Planification._get_analysis_domain(planification.laboratory)
new_context = {}
new_context['date_from'] = planification.date_from
new_context['date_to'] = planification.date_to
with Transaction().set_context(new_context):
pending_fractions = Analysis.analysis_pending_fractions(asg_list)
res = []
for analysis, pending in iter(pending_fractions.items()):
if pending > 0:
res.append(analysis)
return res
def transition_add(self):
Planification = Pool().get('lims.planification')
planification = Planification(Transaction().context['active_id'])
Planification.write([planification], {
'analysis': [('remove', self.start.analysis)],
})
Planification.write([planification], {
'analysis': [('add', self.start.analysis)],
})
return 'end'
class SearchFractionsNext(ModelView):
'Search Fractions'
__name__ = 'lims.planification.search_fractions.next'
details = fields.Many2Many(
'lims.planification.search_fractions.detail',
None, None, 'Fractions to plan', depends=['details_domain'],
domain=[('id', 'in', Eval('details_domain'))], required=True)
details_domain = fields.One2Many(
'lims.planification.search_fractions.detail',
None, 'Fractions domain')
class SearchFractionsDetail(ModelSQL, ModelView):
'Fraction to Plan'
__name__ = 'lims.planification.search_fractions.detail'
_table = 'lims_plan_search_fractions_detail'
fraction = fields.Many2One('lims.fraction', 'Fraction', readonly=True)
service_analysis = fields.Many2One('lims.analysis', 'Service',
readonly=True)
type = fields.Function(fields.Many2One('lims.fraction.type',
'Fraction type'), 'get_fraction_field',
searcher='search_fraction_field')
label = fields.Function(fields.Char('Label'), 'get_fraction_field',
searcher='search_fraction_field')
product_type = fields.Function(fields.Many2One('lims.product.type',
'Product type'), 'get_fraction_field',
searcher='search_fraction_field')
matrix = fields.Function(fields.Many2One('lims.matrix', 'Matrix'),
'get_fraction_field', searcher='search_fraction_field')
create_date2 = fields.Function(fields.DateTime('Create Date'),
'get_fraction_field', searcher='search_fraction_field')
urgent = fields.Function(fields.Boolean('Urgent'), 'get_service_field',
searcher='search_urgent')
priority = fields.Function(fields.Integer('Priority'), 'get_service_field')
repetition = fields.Boolean('Repetition', readonly=True)
laboratory_date = fields.Function(fields.Date('Laboratory deadline'),
'get_service_field')
report_date = fields.Function(fields.Date('Date agreed for result'),
'get_service_field')
results_estimated_date = fields.Function(fields.Date(
'Estimated date of result'), 'get_results_estimated_date')
completion_percentage = fields.Function(fields.Numeric('Complete',
digits=(1, 4)), 'get_completion_percentage')
department = fields.Function(fields.Many2One('company.department',
'Department'), 'get_department', searcher='search_department')
sample_state = fields.Function(fields.Selection(
'get_sample_states', 'State'), 'get_sample_state')
session_id = fields.Integer('Session ID')
@classmethod
def __register__(cls, module_name):
super().__register__(module_name)
cursor = Transaction().connection.cursor()
cursor.execute('DELETE FROM "' + cls._table + '"')
@classmethod
def __setup__(cls):
super().__setup__()
cls._order.insert(0, ('fraction', 'ASC'))
cls._order.insert(1, ('service_analysis', 'ASC'))
@classmethod
def get_sample_states(cls):
pool = Pool()
Sample = pool.get('lims.sample')
return Sample.fields_get(['state'])['state']['selection']
@classmethod
def get_sample_state(cls, details, name):
result = {}
for d in details:
result[d.id] = getattr(d.fraction.sample, 'state', None)
return result
@classmethod
def get_fraction_field(cls, details, names):
result = {}
for name in names:
result[name] = {}
if name in ('label', 'create_date2'):
for d in details:
result[name][d.id] = getattr(d.fraction, name, None)
else:
for d in details:
field = getattr(d.fraction, name, None)
result[name][d.id] = field.id if field else None
return result
@classmethod
def search_fraction_field(cls, name, clause):
return [('fraction.' + name,) + tuple(clause[1:])]
def _order_fraction_field(name):
def order_field(tables):
Fraction = Pool().get('lims.fraction')
field = Fraction._fields[name]
table, _ = tables[None]
fraction_tables = tables.get('fraction')
if fraction_tables is None:
fraction = Fraction.__table__()
fraction_tables = {
None: (fraction, fraction.id == table.fraction),
}
tables['fraction'] = fraction_tables
return field.convert_order(name, fraction_tables, Fraction)
return staticmethod(order_field)
# Redefine convert_order function with 'order_%s' % field
order_product_type = _order_fraction_field('product_type')
order_matrix = _order_fraction_field('matrix')
@classmethod
def get_service_field(cls, details, names):
result = {}
for name in names:
if name == 'urgent':
result[name] = dict((d.id, False) for d in details)
elif name == 'priority':
result[name] = dict((d.id, 0) for d in details)
else:
result[name] = dict((d.id, None) for d in details)
for d in details:
if d.fraction and d.service_analysis:
for service in d.fraction.services:
if service.analysis == d.service_analysis:
for name in names:
result[name][d.id] = getattr(service, name)
return result
@classmethod
def search_urgent(cls, name, clause):
cursor = Transaction().connection.cursor()
Service = Pool().get('lims.service')
cursor.execute('SELECT fd.id '
'FROM "' + cls._table + '" fd '
'INNER JOIN "' + Service._table + '" s '
'ON (fd.fraction = s.fraction '
'AND fd.service_analysis = s.analysis) '
'WHERE s.urgent = TRUE')
urgent_services = [x[0] for x in cursor.fetchall()]
field, op, operand = clause
if (op, operand) in (('=', True), ('!=', False)):
return [('id', 'in', urgent_services)]
return [('id', 'not in', urgent_services)]
@classmethod
def get_results_estimated_date(cls, details, name):
cursor = Transaction().connection.cursor()
pool = Pool()
NotebookLine = pool.get('lims.notebook.line')
EntryDetailAnalysis = pool.get('lims.entry.detail.analysis')
Service = pool.get('lims.service')
Notebook = pool.get('lims.notebook')
result = {}
for d in details:
cursor.execute('SELECT ad.confirmation_date, '
'nl.results_estimated_waiting '
'FROM "' + NotebookLine._table + '" nl '
'INNER JOIN "' + EntryDetailAnalysis._table + '" ad '
'ON ad.id = nl.analysis_detail '
'INNER JOIN "' + Service._table + '" srv '
'ON srv.id = nl.service '
'INNER JOIN "' + Notebook._table + '" n '
'ON n.id = nl.notebook '
'WHERE n.fraction = %s '
'AND srv.analysis = %s '
'AND ad.confirmation_date IS NOT NULL '
'AND nl.results_estimated_waiting IS NOT NULL',
(str(d.fraction.id), str(d.service_analysis.id)))
date = None
for x in cursor.fetchall():
line_date = NotebookLine._get_results_estimated_date(
x[0], x[1])
if not line_date:
continue
if not date or date > line_date:
date = line_date
result[d.id] = date
return result
@classmethod
def get_completion_percentage(cls, details, name):
result = {}
for d in details:
result[d.id] = getattr(d.fraction.sample, name, None)
return result
@classmethod
def get_department(cls, details, name):
result = {}
for d in details:
field = getattr(d.product_type, name, None)
result[d.id] = field.id if field else None
return result
@classmethod
def search_department(cls, name, clause):
return [('fraction.sample.product_type.' + name,) + tuple(clause[1:])]
class SearchFractions(Wizard):
'Search Fractions'
__name__ = 'lims.planification.search_fractions'
start_state = 'search'
search = StateTransition()
next = StateView('lims.planification.search_fractions.next',
'lims.lims_search_fractions_next_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Add', 'add', 'tryton-ok', default=True),
])
add = StateTransition()
def transition_search(self):
pool = Pool()
Planification = pool.get('lims.planification')
PlanificationServiceDetail = pool.get(
'lims.planification.service_detail')
PlanificationDetail = pool.get('lims.planification.detail')
SearchFractionsDetail = pool.get(
'lims.planification.search_fractions.detail')
planification = Planification(Transaction().context['active_id'])
service_detail = PlanificationServiceDetail.search([
('planification', '=', planification.id),
('is_control', '=', False),
('is_replanned', '=', False),
])
if service_detail:
PlanificationServiceDetail.delete(service_detail)
details = PlanificationDetail.search([
('planification', '=', planification.id),
('details', '=', None),
])
if details:
PlanificationDetail.delete(details)
fractions_added = []
if not planification.analysis:
self.next.details = fractions_added
return 'next'
data = self._get_service_details(planification)
to_create = []
for k, v in data.items():
to_create.append({
'session_id': self._session_id,
'fraction': k[0],
'service_analysis': k[1],
'repetition': v['repetition'],
})
fractions_added = SearchFractionsDetail.create(to_create)
self.next.details = fractions_added
return 'next'
def default_next(self, fields):
details = [d.id for d in self.next.details]
self.next.details = None
return {
'details': [],
'details_domain': details,
}
def transition_add(self):
pool = Pool()
Planification = pool.get('lims.planification')
PlanificationDetail = pool.get('lims.planification.detail')
Service = pool.get('lims.service')
planification = Planification(Transaction().context['active_id'])
records_added = ['(%s,%s)' % (d.fraction.id, d.service_analysis.id)
for d in self.next.details]
records_ids_added = ', '.join(str(x)
for x in ['(0,0)'] + records_added)
extra_where = (
'AND (nb.fraction, srv.analysis) IN (' + records_ids_added + ') ')
data = self._get_service_details(planification, extra_where)
to_create = []
for k, v in data.items():
details = PlanificationDetail.search([
('planification', '=', planification.id),
('fraction', '=', k[0]),
('service_analysis', '=', k[1]),
])
if details:
PlanificationDetail.write([details[0]], {
'details': [('create', v)],
})
else:
to_create.append({
'planification': planification.id,
'fraction': k[0],
'service_analysis': k[1],
'urgent': Service.is_service_urgent(k[0], k[1]),
'details': [('create', v)],
})
if to_create:
PlanificationDetail.create(to_create)
return 'end'
def _get_service_details(self, planification, extra_where=''):
cursor = Transaction().connection.cursor()
pool = Pool()
PlanificationServiceDetail = pool.get(
'lims.planification.service_detail')
NotebookLine = pool.get('lims.notebook.line')
Notebook = pool.get('lims.notebook')
Fraction = pool.get('lims.fraction')
EntryDetailAnalysis = pool.get('lims.entry.detail.analysis')
Service = pool.get('lims.service')
Analysis = pool.get('lims.analysis')
planification_details = PlanificationServiceDetail.search([
('planification.state', '=', 'preplanned'),
('notebook_line', '!=', None),
])
planned_lines = [pd.notebook_line.id for pd in planification_details]
planned_lines_ids = ', '.join(str(x) for x in [0] + planned_lines)
preplanned_where = 'AND nl.id NOT IN (%s) ' % planned_lines_ids
dates_where = ''
if planification.date_from:
dates_where += ('AND ad.confirmation_date::date >= \'%s\'::date ' %
planification.date_from)
if planification.date_to:
dates_where += ('AND ad.confirmation_date::date <= \'%s\'::date ' %
planification.date_to)
result = {}
nlines_added = []
for a in planification.analysis:
analysis_id = a.id
all_included_analysis = [analysis_id]
all_included_analysis.extend(
Analysis.get_included_analysis_analysis(analysis_id))
all_included_analysis_ids = ', '.join(str(x)
for x in all_included_analysis)
service_where = ('AND ad.analysis IN (%s) ' %
all_included_analysis_ids)
sql_select = (
'SELECT nl.id, nb.fraction, srv.analysis, nl.repetition != 0 ')
sql_from = (
'FROM "' + NotebookLine._table + '" nl '
'INNER JOIN "' + Analysis._table + '" nla '
'ON nla.id = nl.analysis '
'INNER JOIN "' + Notebook._table + '" nb '
'ON nb.id = nl.notebook '
'INNER JOIN "' + Fraction._table + '" frc '
'ON frc.id = nb.fraction '
'INNER JOIN "' + EntryDetailAnalysis._table + '" ad '
'ON ad.id = nl.analysis_detail '
'INNER JOIN "' + Service._table + '" srv '
'ON srv.id = nl.service ')
sql_where = (
'WHERE ad.plannable = TRUE '
'AND nl.start_date IS NULL '
'AND nl.annulled = FALSE '
'AND nl.laboratory = %s '
'AND nla.behavior != \'internal_relation\' ' +
preplanned_where + dates_where + service_where + extra_where)
sql_order = (
'ORDER BY nb.fraction ASC, srv.analysis ASC')
with Transaction().set_user(0):
cursor.execute(sql_select + sql_from + sql_where + sql_order,
(planification.laboratory.id,))
notebook_lines = cursor.fetchall()
if not notebook_lines:
continue
if extra_where:
for nl in notebook_lines:
f_ = nl[1]
s_ = nl[2]
if (f_, s_) not in result:
result[(f_, s_)] = []
if nl[0] not in nlines_added:
nlines_added.append(nl[0])
result[(f_, s_)].append({
'notebook_line': nl[0],
'planned_service': a.id,
})
else:
for nl in notebook_lines:
f_ = nl[1]
s_ = nl[2]
result[(f_, s_)] = {
'repetition': nl[3],
}
return result
class SearchPlannedFractionsStart(ModelView):
'Search Planned Fractions'
__name__ = 'lims.planification.search_planned_fractions.start'
laboratory = fields.Many2One('lims.laboratory', 'Laboratory')
date_from = fields.Date('Date from')
date_to = fields.Date('Date to')
analysis = fields.Many2Many('lims.planification-analysis',
'planification', 'analysis', 'Analysis/Sets/Groups',
domain=[('id', 'in', Eval('analysis_domain'))],
depends=['analysis_domain'], required=True)
analysis_domain = fields.One2Many('lims.analysis', None,
'Analysis domain')
class SearchPlannedFractionsNext(ModelView):
'Search Planned Fractions'
__name__ = 'lims.planification.search_planned_fractions.next'
details = fields.Many2Many(
'lims.planification.search_fractions.detail',
None, None, 'Fractions to replan', depends=['details_domain'],
domain=[('id', 'in', Eval('details_domain'))], required=True)
details_domain = fields.One2Many(
'lims.planification.search_fractions.detail',
None, 'Fractions domain')
class SearchPlannedFractions(Wizard):
'Search Planned Fractions'
__name__ = 'lims.planification.search_planned_fractions'
start = StateView('lims.planification.search_planned_fractions.start',
'lims.lims_search_planned_fractions_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Search', 'search', 'tryton-forward', default=True),
])
search = StateTransition()
next = StateView('lims.planification.search_planned_fractions.next',
'lims.lims_search_planned_fractions_next_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Add', 'add', 'tryton-ok', default=True),
])
add = StateTransition()
def default_start(self, fields):
Planification = Pool().get('lims.planification')
planification = Planification(Transaction().context['active_id'])
analysis = [a.id for a in planification.analysis]
return {
'laboratory': planification.laboratory.id,
'analysis': analysis,
'analysis_domain': analysis,
'date_from': planification.date_from,
'date_to': planification.date_to,
}
def transition_search(self):
pool = Pool()
SearchFractionsDetail = pool.get(
'lims.planification.search_fractions.detail')
data = self._get_service_details()
to_create = []
for k, v in data.items():
to_create.append({
'session_id': self._session_id,
'fraction': k[0],
'service_analysis': k[1],
'repetition': v['repetition'],
})
fractions_added = SearchFractionsDetail.create(to_create)
self.next.details = fractions_added
return 'next'
def default_next(self, fields):
details = [d.id for d in self.next.details]
self.next.details = None
return {
'details': [],
'details_domain': details,
}
def transition_add(self):
pool = Pool()
Planification = pool.get('lims.planification')
PlanificationDetail = pool.get('lims.planification.detail')
Service = pool.get('lims.service')
planification = Planification(Transaction().context['active_id'])
records_added = ['(%s,%s)' % (d.fraction.id, d.service_analysis.id)
for d in self.next.details]
records_ids_added = ', '.join(str(x)
for x in ['(0,0)'] + records_added)
extra_where = (
'AND (nb.fraction, srv.analysis) IN (' + records_ids_added + ') ')
data = self._get_service_details(extra_where)
to_create = []
for k, v in data.items():
details = PlanificationDetail.search([
('planification', '=', planification.id),
('fraction', '=', k[0]),
('service_analysis', '=', k[1]),
])
if details:
PlanificationDetail.write([details[0]], {
'details': [('create', v)],
})
else:
to_create.append({
'planification': planification.id,
'fraction': k[0],
'service_analysis': k[1],
'urgent': Service.is_service_urgent(k[0], k[1]),
'details': [('create', v)],
})
if to_create:
PlanificationDetail.create(to_create)
return 'end'
def _get_service_details(self, extra_where=''):
cursor = Transaction().connection.cursor()
pool = Pool()
NotebookLine = pool.get('lims.notebook.line')
Notebook = pool.get('lims.notebook')
Fraction = pool.get('lims.fraction')
EntryDetailAnalysis = pool.get('lims.entry.detail.analysis')
Service = pool.get('lims.service')
Analysis = pool.get('lims.analysis')
dates_where = ''
if self.start.date_from:
dates_where += ('AND ad.confirmation_date::date >= \'%s\'::date ' %
self.start.date_from)
if self.start.date_to:
dates_where += ('AND ad.confirmation_date::date <= \'%s\'::date ' %
self.start.date_to)
result = {}
nlines_added = []
for a in self.start.analysis:
analysis_id = a.id
all_included_analysis = [analysis_id]
all_included_analysis.extend(
Analysis.get_included_analysis_analysis(analysis_id))
all_included_analysis_ids = ', '.join(str(x)
for x in all_included_analysis)
service_where = ('AND ad.analysis IN (%s) ' %
all_included_analysis_ids)
excluded_fractions = self.get_control_fractions_excluded(
self.start.laboratory.id, service_where)
excluded_fractions_ids = ', '.join(str(x)
for x in [0] + excluded_fractions)
excluded_where = ('AND nb.fraction NOT IN (%s) ' %
excluded_fractions_ids)
sql_select = (
'SELECT nl.id, nb.fraction, srv.analysis, nl.repetition != 0 ')
sql_from = (
'FROM "' + NotebookLine._table + '" nl '
'INNER JOIN "' + Analysis._table + '" nla '
'ON nla.id = nl.analysis '
'INNER JOIN "' + Notebook._table + '" nb '
'ON nb.id = nl.notebook '
'INNER JOIN "' + Fraction._table + '" frc '
'ON frc.id = nb.fraction '
'INNER JOIN "' + EntryDetailAnalysis._table + '" ad '
'ON ad.id = nl.analysis_detail '
'INNER JOIN "' + Service._table + '" srv '
'ON srv.id = nl.service ')
sql_where = (
'WHERE ad.plannable = TRUE '
'AND nl.start_date IS NOT NULL '
'AND nl.end_date IS NULL '
'AND nl.laboratory = %s '
'AND nla.behavior != \'internal_relation\' ' +
excluded_where + dates_where + service_where + extra_where)
sql_order = (
'ORDER BY nb.fraction ASC, srv.analysis ASC')
with Transaction().set_user(0):
cursor.execute(sql_select + sql_from + sql_where + sql_order,
(self.start.laboratory.id,))
notebook_lines = cursor.fetchall()
if notebook_lines:
if extra_where:
for nl in notebook_lines:
f_ = nl[1]
s_ = nl[2]
if (f_, s_) not in result:
result[(f_, s_)] = []
if nl[0] not in nlines_added:
nlines_added.append(nl[0])
result[(f_, s_)].append({
'notebook_line': nl[0],
'planned_service': a.id,
'is_replanned': True,
})
else:
for nl in notebook_lines:
f_ = nl[1]
s_ = nl[2]
result[(f_, s_)] = {
'repetition': nl[3],
}
return result
def get_control_fractions_excluded(self, laboratory, search_clause):
cursor = Transaction().connection.cursor()
pool = Pool()
NotebookLine = pool.get('lims.notebook.line')
Notebook = pool.get('lims.notebook')
Fraction = pool.get('lims.fraction')
EntryDetailAnalysis = pool.get('lims.entry.detail.analysis')
Config = pool.get('lims.configuration')
config = Config(1)
special_types = []
if config.rm_fraction_type:
special_types.append(config.rm_fraction_type.id)
if config.bmz_fraction_type:
special_types.append(config.bmz_fraction_type.id)
if config.bre_fraction_type:
special_types.append(config.bre_fraction_type.id)
if config.mrt_fraction_type:
special_types.append(config.mrt_fraction_type.id)
special_types_ids = ', '.join(str(x) for x in [0] + special_types)
cursor.execute('SELECT nl.analysis, nl.notebook '
'FROM "' + NotebookLine._table + '" nl '
'INNER JOIN "' + Notebook._table + '" nb '
'ON nb.id = nl.notebook '
'INNER JOIN "' + Fraction._table + '" frc '
'ON frc.id = nb.fraction '
'INNER JOIN "' + EntryDetailAnalysis._table + '" ad '
'ON ad.id = nl.analysis_detail '
'WHERE frc.type IN (' + special_types_ids + ') '
'AND nl.start_date IS NOT NULL '
'AND nl.end_date IS NULL '
'AND nl.laboratory = %s ' +
search_clause, (laboratory,))
notebook_lines = cursor.fetchall()
if not notebook_lines:
return []
excluded_fractions = []
analysis = []
notebooks_id = []
for nbl in notebook_lines:
analysis.append(nbl[0])
notebooks_id.append(nbl[1])
analysis = set(analysis)
notebooks = Notebook.search([
('id', 'in', list(set(notebooks_id))),
])
for nb in notebooks:
cursor.execute('SELECT analysis '
'FROM "' + NotebookLine._table + '" '
'WHERE notebook = %s', (nb.id,))
nbl_analysis_ids = set(cursor.fetchall())
if not analysis.issubset(nbl_analysis_ids):
excluded_fractions.append(nb.fraction.id)
return list(set(excluded_fractions))
class CreateFractionControlStart(ModelView):
'Create Fraction Control'
__name__ = 'lims.planification.create_fraction_con.start'
laboratory = fields.Many2One('lims.laboratory', 'Laboratory',
required=True)
type = fields.Selection([
('coi', 'COI'),
('mrc', 'MRC'),
('sla', 'SLA'),
], 'Control type', sort=False, required=True)
product_type = fields.Many2One('lims.product.type', 'Product type',
required=True,
domain=[('id', 'in', Eval('product_type_domain'))],
depends=['product_type_domain'])
product_type_domain = fields.Function(fields.Many2Many(
'lims.product.type', None, None, 'Product type domain'),
'on_change_with_product_type_domain')
matrix = fields.Many2One('lims.matrix', 'Matrix', required=True,
domain=[('id', 'in', Eval('matrix_domain'))],
depends=['matrix_domain'])
matrix_domain = fields.Function(fields.Many2Many('lims.matrix',
None, None, 'Matrix domain'),
'on_change_with_matrix_domain')
analysis = fields.Many2Many('lims.planification-analysis',
'planification', 'analysis', 'Analysis/Sets/Groups',
domain=[('id', 'in', Eval('analysis_domain'))],
depends=['analysis_domain'])
analysis_domain = fields.Function(fields.One2Many('lims.analysis',
None, 'Analysis domain'), 'on_change_with_analysis_domain')
label = fields.Char('Label')
concentration_level = fields.Many2One('lims.concentration.level',
'Concentration level', states={
'invisible': Bool(Eval('concentration_level_invisible')),
}, depends=['concentration_level_invisible'])
concentration_level_invisible = fields.Boolean(
'Concentration level invisible')
sample_created = fields.Many2One('lims.sample', 'Sample created')
@fields.depends('type')
def on_change_with_concentration_level_invisible(self, name=None):
Config = Pool().get('lims.configuration')
config = Config(1)
if self.type == 'coi':
if (config.coi_fraction_type and
config.coi_fraction_type.control_charts):
return False
elif self.type == 'mrc':
if (config.mrc_fraction_type and
config.mrc_fraction_type.control_charts):
return False
elif self.type == 'sla':
if (config.sla_fraction_type and
config.sla_fraction_type.control_charts):
return False
return True
@fields.depends('laboratory', 'product_type', 'matrix')
def on_change_with_analysis_domain(self, name=None):
cursor = Transaction().connection.cursor()
pool = Pool()
AnalysisLaboratory = pool.get('lims.analysis-laboratory')
Analysis = pool.get('lims.analysis')
Typification = pool.get('lims.typification')
CalculatedTypification = pool.get('lims.typification.calculated')
if not self.laboratory or not self.product_type or not self.matrix:
return []
cursor.execute('SELECT DISTINCT(analysis) '
'FROM "' + AnalysisLaboratory._table + '" '
'WHERE laboratory = %s', (self.laboratory.id,))
analysis_sets_list = [a[0] for a in cursor.fetchall()]
if not analysis_sets_list:
return []
lab_analysis_ids = ', '.join(str(a) for a in
analysis_sets_list)
groups_list = []
groups = Analysis.search([
('type', '=', 'group'),
])
if groups:
for group in groups:
available = True
ia = Analysis.get_included_analysis_analysis(
group.id)
if not ia:
continue
included_ids = ', '.join(str(a) for a in ia)
cursor.execute('SELECT id '
'FROM "' + Analysis._table + '" '
'WHERE id IN (' + included_ids + ') '
'AND id NOT IN (' + lab_analysis_ids +
')')
if cursor.fetchone():
available = False
if available:
groups_list.append(group.id)
analysis_domain = analysis_sets_list + groups_list
analysis_domain_ids = ', '.join(str(a) for a in analysis_domain)
cursor.execute('SELECT DISTINCT(typ.analysis) '
'FROM ('
'SELECT t.analysis FROM "' + Typification._table + '" t '
'WHERE t.product_type = %s AND t.matrix = %s AND t.valid '
'UNION '
'SELECT ct.analysis FROM "' + CalculatedTypification._table +
'" ct '
'WHERE ct.product_type = %s AND ct.matrix = %s'
') AS typ '
'WHERE typ.analysis IN (' + analysis_domain_ids + ')',
(self.product_type.id, self.matrix.id,
self.product_type.id, self.matrix.id))
typified_analysis = [a[0] for a in cursor.fetchall()]
return typified_analysis
@staticmethod
def default_product_type_domain():
cursor = Transaction().connection.cursor()
Typification = Pool().get('lims.typification')
cursor.execute('SELECT DISTINCT(product_type) '
'FROM "' + Typification._table + '" '
'WHERE valid')
return [x[0] for x in cursor.fetchall()]
def on_change_with_product_type_domain(self, name=None):
return self.default_product_type_domain()
@fields.depends('product_type')
def on_change_product_type(self):
matrix = None
if self.product_type:
matrixs = self.on_change_with_matrix_domain()
if len(matrixs) == 1:
matrix = matrixs[0]
self.matrix = matrix
@fields.depends('product_type')
def on_change_with_matrix_domain(self, name=None):
cursor = Transaction().connection.cursor()
Typification = Pool().get('lims.typification')
if not self.product_type:
return []
cursor.execute('SELECT DISTINCT(matrix) '
'FROM "' + Typification._table + '" '
'WHERE product_type = %s '
'AND valid',
(self.product_type.id,))
return [x[0] for x in cursor.fetchall()]
@fields.depends('type', 'product_type', 'matrix',
'_parent_product_type.code', '_parent_matrix.code')
def on_change_with_label(self, name=None):
label = ''
if self.type == 'coi':
label += 'COI'
elif self.type == 'mrc':
label += 'MRC'
elif self.type == 'sla':
label += 'SLA'
if self.product_type:
label += ' ' + self.product_type.code
if self.matrix:
label += ' ' + self.matrix.code
return label
class CreateFractionControl(Wizard):
'Create Fraction Control'
__name__ = 'lims.planification.create_fraction_con'
start = StateView('lims.planification.create_fraction_con.start',
'lims.lims_create_fraction_con_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Create', 'create_', 'tryton-ok', default=True),
])
create_ = StateTransition()
open_ = StateAction('lims.act_lims_sample_list')
def default_start(self, fields):
defaults = {
'laboratory': Transaction().context.get('laboratory', None),
'concentration_level_invisible': True,
}
return defaults
def transition_create_(self):
control = self.create_control()
self.start.sample_created = control.sample.id
return 'open_'
def create_control(self):
pool = Pool()
Config = pool.get('lims.configuration')
LabWorkYear = pool.get('lims.lab.workyear')
Entry = pool.get('lims.entry')
Sample = pool.get('lims.sample')
Fraction = pool.get('lims.fraction')
Service = pool.get('lims.service')
NotebookLine = pool.get('lims.notebook.line')
config = Config(1)
if self.start.type == 'coi':
if not config.coi_fraction_type:
raise UserError(gettext('lims.msg_no_coi_fraction_type'))
fraction_type = config.coi_fraction_type
if (not fraction_type.default_package_type or
not fraction_type.default_fraction_state):
raise UserError(gettext(
'lims.msg_no_coi_default_configuration'))
elif self.start.type == 'mrc':
if not config.mrc_fraction_type:
raise UserError(gettext('lims.msg_no_mrc_fraction_type'))
fraction_type = config.mrc_fraction_type
if (not fraction_type.default_package_type or
not fraction_type.default_fraction_state):
raise UserError(gettext(
'lims.msg_no_mrc_default_configuration'))
elif self.start.type == 'sla':
if not config.sla_fraction_type:
raise UserError(gettext('lims.msg_no_sla_fraction_type'))
fraction_type = config.sla_fraction_type
if (not fraction_type.default_package_type or
not fraction_type.default_fraction_state):
raise UserError(gettext(
'lims.msg_no_sla_default_configuration'))
if (fraction_type.control_charts and not
self.start.concentration_level):
raise UserError(gettext('lims.msg_no_concentration_level'))
workyear_id = LabWorkYear.find()
workyear = LabWorkYear(workyear_id)
if not workyear.default_entry_control:
raise UserError(gettext('lims.msg_no_entry_control'))
entry = Entry(workyear.default_entry_control.id)
if not entry.party.entry_zone and config.zone_required:
raise UserError(gettext('lims.msg_no_party_zone',
party=entry.party.rec_name))
zone_id = entry.party.entry_zone and entry.party.entry_zone.id or None
laboratory = self.start.laboratory
obj_description = self._get_obj_description(self.start)
# new sample
new_sample, = Sample.create([{
'entry': entry.id,
'party': entry.party.id,
'date': datetime.now(),
'product_type': self.start.product_type.id,
'matrix': self.start.matrix.id,
'zone': zone_id,
'label': self.start.label,
'obj_description': obj_description,
'packages_quantity': 1,
'fractions': [],
}])
# new fraction
fraction_default = {
'sample': new_sample.id,
'type': fraction_type.id,
'storage_location': laboratory.related_location.id,
'packages_quantity': 1,
'package_type': fraction_type.default_package_type.id,
'fraction_state': fraction_type.default_fraction_state.id,
'services': [],
'con_type': self.start.type,
}
if fraction_type.max_storage_time:
fraction_default['storage_time'] = fraction_type.max_storage_time
elif laboratory.related_location.storage_time:
fraction_default['storage_time'] = (
laboratory.related_location.storage_time)
else:
fraction_default['storage_time'] = 3
new_fraction, = Fraction.create([fraction_default])
# new services
services_default = []
for p_analysis in self.start.analysis:
laboratory_id = (laboratory.id if p_analysis.type != 'group'
else None)
method_id = None
if new_sample.typification_domain:
for t in new_sample.typification_domain:
if (t.analysis.id == p_analysis.id and
t.by_default is True):
method_id = t.method.id
device_id = None
if p_analysis.devices:
for d in p_analysis.devices:
if (d.laboratory.id == laboratory.id and
d.by_default is True):
device_id = d.device.id
services_default.append({
'fraction': new_fraction.id,
'analysis': p_analysis.id,
'laboratory': laboratory_id,
'method': method_id,
'device': device_id,
})
for service in services_default:
new_service, = Service.create([service])
# new analysis details (on service create)
# confirm fraction: new notebook and stock move
Fraction.confirm([new_fraction])
# Edit notebook lines
if fraction_type.control_charts:
notebook_lines = NotebookLine.search([
('notebook.fraction', '=', new_fraction.id),
])
if notebook_lines:
defaults = {
'concentration_level': self.start.concentration_level.id,
}
NotebookLine.write(notebook_lines, defaults)
return new_fraction
def _get_obj_description(self, sample):
cursor = Transaction().connection.cursor()
ObjectiveDescription = Pool().get('lims.objective_description')
if not sample.product_type or not sample.matrix:
return None
cursor.execute('SELECT id '
'FROM "' + ObjectiveDescription._table + '" '
'WHERE product_type = %s '
'AND matrix = %s',
(sample.product_type.id, sample.matrix.id))
res = cursor.fetchone()
return res and res[0] or None
def do_open_(self, action):
action['pyson_domain'] = PYSONEncoder().encode([
('id', '=', self.start.sample_created.id),
])
return action, {}
class ReleaseFractionStart(ModelView):
'Release Fraction'
__name__ = 'lims.planification.release_fraction.start'
laboratory = fields.Many2One('lims.laboratory', 'Laboratory',
required=True)
date_from = fields.Date('Date from', required=True)
date_to = fields.Date('Date to', required=True)
class ReleaseFractionEmpty(ModelView):
'Release Fraction'
__name__ = 'lims.planification.release_fraction.empty'
class ReleaseFractionResult(ModelView):
'Release Fraction'
__name__ = 'lims.planification.release_fraction.result'
fractions = fields.Many2Many('lims.planification.detail', None, None,
'Fractions', required=True,
domain=[('id', 'in', Eval('fractions_domain'))],
depends=['fractions_domain'])
fractions_domain = fields.One2Many('lims.planification.detail', None,
'Fractions domain')
class ReleaseFraction(Wizard):
'Release Fraction'
__name__ = 'lims.planification.release_fraction'
start = StateView('lims.planification.release_fraction.start',
'lims.lims_planification_release_fraction_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Search', 'search', 'tryton-forward', default=True),
])
search = StateTransition()
empty = StateView('lims.planification.release_fraction.empty',
'lims.lims_planification_release_fraction_empty_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Search again', 'start', 'tryton-forward', default=True),
])
result = StateView('lims.planification.release_fraction.result',
'lims.lims_planification_release_fraction_result_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Release', 'release', 'tryton-ok', default=True),
])
release = StateTransition()
def default_start(self, fields):
res = {}
for field in ('date_from', 'date_to'):
if (hasattr(self.start, field) and getattr(self.start, field)):
res[field] = getattr(self.start, field)
if (hasattr(self.start, 'laboratory') and
getattr(self.start, 'laboratory')):
res['laboratory'] = getattr(self.start, 'laboratory').id
else:
res['laboratory'] = Transaction().context.get('laboratory', None)
return res
def transition_search(self):
PlanificationDetail = Pool().get('lims.planification.detail')
details = PlanificationDetail.search([
('planification.laboratory', '=', self.start.laboratory.id),
('planification.start_date', '>=', self.start.date_from),
('planification.start_date', '<=', self.start.date_to),
('planification.state', '!=', 'draft'),
])
if details:
fractions = []
for detail in details:
available = True
for service_detail in detail.details:
if service_detail.is_control:
available = False
break
nline = service_detail.notebook_line
if not nline:
available = False
break
if (nline.result or nline.converted_result or
nline.literal_result or nline.end_date or
nline.annulment_date):
available = False
break
if available:
fractions.append(detail)
if fractions:
self.result.fractions = fractions
return 'result'
return 'empty'
def default_result(self, fields):
fractions = [f.id for f in self.result.fractions]
self.result.fractions = None
return {
'fractions_domain': fractions,
}
def transition_release(self):
cursor = Transaction().connection.cursor()
pool = Pool()
NotebookLine = pool.get('lims.notebook.line')
NotebookLineProfessional = pool.get(
'lims.notebook.line-laboratory.professional')
NotebookLineControl = pool.get('lims.notebook.line-fraction')
EntryDetailAnalysis = pool.get('lims.entry.detail.analysis')
PlanificationServiceDetail = pool.get(
'lims.planification.service_detail')
PlanificationDetail = pool.get('lims.planification.detail')
notebook_lines_ids = []
analysis_detail_ids = []
details_ids = []
service_details_ids = []
for detail in self.result.fractions:
for service_detail in detail.details:
if service_detail.is_control:
continue
if service_detail.notebook_line:
notebook_lines_ids.append(service_detail.notebook_line.id)
if service_detail.notebook_line.analysis_detail:
analysis_detail_ids.append(
service_detail.notebook_line.analysis_detail.id)
service_details_ids.append(service_detail.id)
details_ids.append(service_detail.detail.id)
if notebook_lines_ids:
notebook_lines_ids = ', '.join(str(nl)
for nl in notebook_lines_ids)
cursor.execute('UPDATE "' + NotebookLine._table + '" '
'SET start_date = NULL, planification = NULL '
'WHERE id IN (' + notebook_lines_ids + ')')
cursor.execute('DELETE FROM "' +
NotebookLineProfessional._table + '" '
'WHERE notebook_line IN (' + notebook_lines_ids + ')')
cursor.execute('DELETE FROM "' +
NotebookLineControl._table + '" '
'WHERE notebook_line IN (' + notebook_lines_ids + ')')
if analysis_detail_ids:
analysis_detail_ids = ', '.join(str(ad)
for ad in analysis_detail_ids)
cursor.execute('UPDATE "' + EntryDetailAnalysis._table + '" '
'SET state = \'unplanned\' '
'WHERE id IN (' + analysis_detail_ids + ')')
if service_details_ids:
service_details_ids = ', '.join(str(sd)
for sd in service_details_ids)
cursor.execute('DELETE FROM "' +
PlanificationServiceDetail._table + '" '
'WHERE id IN (' + service_details_ids + ')')
details = PlanificationDetail.search([
('id', 'in', details_ids),
('details', '=', None),
])
if details:
PlanificationDetail.delete(details)
return 'end'
class QualificationSituations(ModelView):
'Technicians Qualification'
__name__ = 'lims.planification.qualification.situations'
situations = fields.Many2Many('lims.planification.qualification.situation',
None, None, 'Situations')
total = fields.Integer('Total')
index = fields.Integer('Index')
class QualificationSituation(ModelSQL, ModelView):
'Qualification Situation'
__name__ = 'lims.planification.qualification.situation'
method = fields.Many2One('lims.lab.method', 'Method', readonly=True)
professional = fields.Many2One('lims.laboratory.professional',
'Professional', readonly=True)
situation = fields.Integer('Situation', readonly=True)
session_id = fields.Integer('Session ID')
@classmethod
def __register__(cls, module_name):
super().__register__(module_name)
cursor = Transaction().connection.cursor()
cursor.execute('DELETE FROM "' + cls._table + '"')
class QualificationAction(ModelSQL):
'Qualification Action'
__name__ = 'lims.planification.qualification.action'
method = fields.Many2One('lims.lab.method', 'Method')
professional = fields.Many2One('lims.laboratory.professional',
'Professional')
action = fields.Integer('Action')
session_id = fields.Integer('Session ID')
@classmethod
def __register__(cls, module_name):
super().__register__(module_name)
cursor = Transaction().connection.cursor()
cursor.execute('DELETE FROM "' + cls._table + '"')
class QualificationSituation2(ModelView):
'Technicians Qualification'
__name__ = 'lims.planification.qualification.situation.2'
method = fields.Many2One('lims.lab.method', 'Method', readonly=True)
professional = fields.Many2One('lims.laboratory.professional',
'Professional', readonly=True)
class QualificationSituation3(ModelView):
'Technicians Qualification'
__name__ = 'lims.planification.qualification.situation.3'
method = fields.Many2One('lims.lab.method', 'Method', readonly=True)
professional = fields.Many2One('lims.laboratory.professional',
'Professional', readonly=True)
class QualificationSituation4(ModelView):
'Technicians Qualification'
__name__ = 'lims.planification.qualification.situation.4'
methods = fields.Text('Methods', readonly=True)
class TechniciansQualification(Wizard):
'Technicians Qualification'
__name__ = 'lims.planification.technicians_qualification'
situations = StateView('lims.planification.qualification.situations',
'lims.lims_qualification_situations_view_form', [])
start = StateTransition()
next_ = StateTransition()
confirm = StateTransition()
sit2 = StateView('lims.planification.qualification.situation.2',
'lims.lims_qualification_situation_2_view_form', [
Button('Qualify', 'sit2_op1', 'tryton-ok', default=True),
Button('New Training', 'sit2_op2', 'tryton-ok'),
Button('Cancel', 'end', 'tryton-cancel'),
])
sit2_op1 = StateTransition()
sit2_op2 = StateTransition()
sit3 = StateView('lims.planification.qualification.situation.3',
'lims.lims_qualification_situation_3_view_form', [
Button('Requalify', 'sit3_op1', 'tryton-ok', default=True),
Button('Cancel', 'end', 'tryton-cancel'),
])
sit3_op1 = StateTransition()
sit4 = StateView('lims.planification.qualification.situation.4',
'lims.lims_qualification_situation_4_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
])
def transition_start(self):
pool = Pool()
Planification = pool.get('lims.planification')
PlanificationServiceDetail = pool.get(
'lims.planification.service_detail')
LabProfessionalMethod = pool.get('lims.lab.professional.method')
QualificationSituation = pool.get(
'lims.planification.qualification.situation')
planification = Planification(Transaction().context['active_id'])
planification.wizard_executed = False
planification.save()
planification_details = PlanificationServiceDetail.search([
('planification', '=', planification.id),
])
situations = {}
for detail in planification_details:
method = (detail.notebook_line.method if detail.notebook_line
else None)
if method:
for technician in detail.staff_responsible:
key = (technician.id, method.id)
if key in situations:
continue
situations[key] = 0
qualifications = LabProfessionalMethod.search([
('professional', '=', technician.id),
('method', '=', method.id),
('type', '=', 'preparation'),
])
if not qualifications:
situations[key] = 1
else:
if qualifications[0].state == 'training':
situations[key] = 2
elif (qualifications[0].state in
('qualified', 'requalified')):
situations[key] = 3
if situations:
self.situations.situations = QualificationSituation.create([{
'session_id': self._session_id,
'professional': k[0],
'method': k[1],
'situation': v,
} for k, v in situations.items()])
self.situations.total = len(self.situations.situations)
self.situations.index = 0
return 'next_'
return self._confirm()
def transition_next_(self):
data = self.situations.situations[self.situations.index]
if data.situation == 1:
return self.situation_1(data)
elif data.situation == 2:
return self.situation_2(data)
elif data.situation == 3:
return self.situation_3(data)
return self._continue()
def situation_1(self, data):
# The technician has not the method. Write a new training directly.
QualificationAction = Pool().get(
'lims.planification.qualification.action')
QualificationAction.create([{
'session_id': self._session_id,
'professional': data.professional.id,
'method': data.method.id,
'action': 1,
}])
return self._continue()
def situation_2(self, data):
# The technician has received at least one training. Ask if qualify
# or write a new training.
self.sit2.professional = data.professional.id
self.sit2.method = data.method.id
return 'sit2'
def default_sit2(self, fields):
professional = self.sit2.professional.id
method = self.sit2.method.id
self.sit2.professional = None
self.sit2.method = None
return {
'professional': professional,
'method': method,
}
def transition_sit2_op1(self):
# Qualify the technician
QualificationAction = Pool().get(
'lims.planification.qualification.action')
QualificationAction.create([{
'session_id': self._session_id,
'professional': self.sit2.professional.id,
'method': self.sit2.method.id,
'action': 2,
}])
return self._continue()
def transition_sit2_op2(self):
# Write a new training.
QualificationAction = Pool().get(
'lims.planification.qualification.action')
QualificationAction.create([{
'session_id': self._session_id,
'professional': self.sit2.professional.id,
'method': self.sit2.method.id,
'action': 3,
}])
return self._continue()
def situation_3(self, data):
# The technician is qualified. Check the last execution date of the
# method; if it's expired, ask if requalify or cancel the process.
# If it is valid, write a new execution
pool = Pool()
LabProfessionalMethod = pool.get('lims.lab.professional.method')
Date = pool.get('ir.date')
QualificationAction = pool.get(
'lims.planification.qualification.action')
deadline = Date.today() - relativedelta(
months=data.method.requalification_months)
professional_method, = LabProfessionalMethod.search([
('professional', '=', data.professional),
('method', '=', data.method),
('type', '=', 'preparation'),
])
last_execution = date.min
for requalification in professional_method.requalification_history:
if (requalification.last_execution_date and
last_execution < requalification.last_execution_date):
last_execution = requalification.last_execution_date
if last_execution < deadline:
self.sit3.professional = data.professional.id
self.sit3.method = data.method.id
return 'sit3'
else:
QualificationAction.create([{
'session_id': self._session_id,
'professional': data.professional.id,
'method': data.method.id,
'action': 5,
}])
return self._continue()
def default_sit3(self, fields):
professional = self.sit3.professional.id
method = self.sit3.method.id
self.sit3.professional = None
self.sit3.method = None
return {
'professional': professional,
'method': method,
}
def transition_sit3_op1(self):
# Requalify the technician
QualificationAction = Pool().get(
'lims.planification.qualification.action')
QualificationAction.create([{
'session_id': self._session_id,
'professional': self.sit3.professional.id,
'method': self.sit3.method.id,
'action': 4,
}])
return self._continue()
def situation_4(self, data):
# The method has no qualified technician
self.sit4.methods = data['methods']
return 'sit4'
def default_sit4(self, fields):
methods = self.sit4.methods
self.sit4.methods = None
return {
'methods': methods,
}
def _continue(self):
self.situations.index += 1
if self.situations.index < self.situations.total:
return 'next_'
return self._confirm()
def _confirm(self):
pool = Pool()
Planification = pool.get('lims.planification')
PlanificationServiceDetail = pool.get(
'lims.planification.service_detail')
LabProfessionalMethod = pool.get('lims.lab.professional.method')
QualificationAction = pool.get(
'lims.planification.qualification.action')
planification = Planification(Transaction().context['active_id'])
planification_details = PlanificationServiceDetail.search([
('planification', '=', planification.id),
])
methods = []
for detail in planification_details:
method = (detail.notebook_line.method if detail.notebook_line
else None)
if method:
qualified = False
for technician in detail.staff_responsible:
qualifications = LabProfessionalMethod.search([
('professional', '=', technician.id),
('method', '=', method.id),
('type', '=', 'preparation'),
])
if (qualifications and qualifications[0].state in
('qualified', 'requalified')):
if not method.supervised_requalification:
qualified = True
break
actions = QualificationAction.search([
('session_id', '=', self._session_id),
('professional', '=', technician.id),
('method', '=', method.id),
('action', '=', 4),
])
if not actions:
qualified = True
break
if not qualified:
methods.append('[%s] %s' % (method.code, method.name))
if methods:
return self.situation_4({'methods': '\n'.join(list(set(methods)))})
actions = QualificationAction.search([
('session_id', '=', self._session_id),
])
if actions:
controls = self._get_controls()
start_date = planification.start_date
for data in actions:
if data.action == 1:
self.action_1(data, controls, start_date)
elif data.action == 2:
self.action_2(data, controls, start_date)
elif data.action == 3:
self.action_3(data, controls, start_date)
elif data.action == 4:
self.action_4(data, controls, start_date)
elif data.action == 5:
self.action_5(data, controls, start_date)
return 'confirm'
def action_1(self, data, controls, start_date):
# The technician has not the method. Write a new training directly.
pool = Pool()
LabProfessionalMethod = pool.get('lims.lab.professional.method')
LabProfessionalMethodRequalification = pool.get(
'lims.lab.professional.method.requalification')
Date = pool.get('ir.date')
supervisors = self._get_supervisors(data)
professional_method, = LabProfessionalMethod.create([{
'professional': data.professional.id,
'method': data.method.id,
'type': 'preparation',
'state': 'training',
}])
LabProfessionalMethodRequalification.create([{
'professional_method': professional_method.id,
'type': 'training',
'date': Date.today(),
'last_execution_date': start_date,
'supervisors': [('create', supervisors)],
'controls': [('create', controls)],
}])
def action_2(self, data, controls, start_date):
# Qualify the technician
pool = Pool()
LabProfessionalMethod = pool.get('lims.lab.professional.method')
LabProfessionalMethodRequalification = pool.get(
'lims.lab.professional.method.requalification')
Date = pool.get('ir.date')
supervisors = self._get_supervisors(data)
professional_method, = LabProfessionalMethod.search([
('professional', '=', data.professional),
('method', '=', data.method),
('type', '=', 'preparation'),
])
LabProfessionalMethod.write([professional_method], {
'state': 'qualified',
})
LabProfessionalMethodRequalification.create([{
'professional_method': professional_method.id,
'type': 'qualification',
'date': Date.today(),
'last_execution_date': start_date,
'supervisors': [('create', supervisors)],
'controls': [('create', controls)],
}])
def action_3(self, data, controls, start_date):
# Write a new training
pool = Pool()
LabProfessionalMethod = pool.get('lims.lab.professional.method')
LabProfessionalMethodRequalification = pool.get(
'lims.lab.professional.method.requalification')
Date = pool.get('ir.date')
supervisors = self._get_supervisors(data)
professional_method, = LabProfessionalMethod.search([
('professional', '=', data.professional),
('method', '=', data.method),
('type', '=', 'preparation'),
])
LabProfessionalMethodRequalification.create([{
'professional_method': professional_method.id,
'type': 'training',
'date': Date.today(),
'last_execution_date': start_date,
'supervisors': [('create', supervisors)],
'controls': [('create', controls)],
}])
def action_4(self, data, controls, start_date):
# Requalify the technician
pool = Pool()
LabProfessionalMethod = pool.get('lims.lab.professional.method')
LabProfessionalMethodRequalification = pool.get(
'lims.lab.professional.method.requalification')
Date = pool.get('ir.date')
supervisors = self._get_supervisors(data)
professional_method, = LabProfessionalMethod.search([
('professional', '=', data.professional),
('method', '=', data.method),
('type', '=', 'preparation'),
])
LabProfessionalMethod.write([professional_method], {
'state': 'requalified',
})
LabProfessionalMethodRequalification.create([{
'professional_method': professional_method.id,
'type': 'requalification',
'date': Date.today(),
'last_execution_date': start_date,
'supervisors': [('create', supervisors)],
'controls': [('create', controls)],
}])
def action_5(self, data, controls, start_date):
# Write a new execution
pool = Pool()
LabProfessionalMethod = pool.get('lims.lab.professional.method')
LabProfessionalMethodRequalification = pool.get(
'lims.lab.professional.method.requalification')
Date = pool.get('ir.date')
supervisors = self._get_supervisors(data)
professional_method, = LabProfessionalMethod.search([
('professional', '=', data.professional),
('method', '=', data.method),
('type', '=', 'preparation'),
])
LabProfessionalMethodRequalification.create([{
'professional_method': professional_method.id,
'type': ('qualification'
if professional_method.state == 'qualified'
else 'requalification'),
'date': Date.today(),
'last_execution_date': start_date,
'supervisors': [('create', supervisors)],
'controls': [('create', controls)],
}])
def transition_confirm(self):
Planification = Pool().get('lims.planification')
planification = Planification(Transaction().context['active_id'])
planification.state = 'confirmed'
planification.save()
planification.pre_update_laboratory_notebook()
planification.update_analysis_detail()
Planification.__queue__.do_confirm([planification])
return 'end'
def _get_supervisors(self, data):
pool = Pool()
PlanificationServiceDetail = pool.get(
'lims.planification.service_detail')
LabProfessionalMethod = pool.get('lims.lab.professional.method')
QualificationAction = pool.get(
'lims.planification.qualification.action')
planification_id = Transaction().context['active_id']
supervisors = []
planification_details = PlanificationServiceDetail.search([
('planification', '=', planification_id),
('notebook_line.method', '=', data.method.id),
])
for detail in planification_details:
for technician in detail.staff_responsible:
if technician.id == data.professional.id:
continue
qualifications = LabProfessionalMethod.search([
('professional', '=', technician.id),
('method', '=', data.method.id),
('type', '=', 'preparation'),
])
if (qualifications and qualifications[0].state in
('qualified', 'requalified')):
if not data.method.supervised_requalification:
supervisors.append(technician.id)
else:
actions = QualificationAction.search([
('session_id', '=', self._session_id),
('professional', '=', technician.id),
('method', '=', data.method.id),
('action', '=', 4),
])
if not actions:
supervisors.append(technician.id)
return [{'supervisor': t_id} for t_id in list(set(supervisors))]
def _get_controls(self):
PlanificationFraction = Pool().get('lims.planification-fraction')
planification_id = Transaction().context['active_id']
controls = []
planification_controls = PlanificationFraction.search([
('planification', '=', planification_id),
('fraction.type.requalify', '=', True),
])
for p_control in planification_controls:
controls.append(p_control.fraction.id)
return [{'control': f_id} for f_id in list(set(controls))]
class ReplaceTechnicianStart(ModelView):
'Replace Technician'
__name__ = 'lims.planification.replace_technician.start'
planification = fields.Many2One('lims.planification', 'Planification')
technician_replaced = fields.Many2One('lims.laboratory.professional',
'Technician replaced', required=True,
domain=[('id', 'in', Eval('replaced_domain'))],
depends=['replaced_domain'])
replaced_domain = fields.One2Many('lims.laboratory.professional',
None, 'Replaced domain')
technician_substitute = fields.Many2One('lims.laboratory.professional',
'Technician substitute', required=True,
domain=[('id', 'in', Eval('substitute_domain'))],
depends=['substitute_domain'])
substitute_domain = fields.Function(fields.One2Many(
'lims.laboratory.professional', None, 'Substitute domain'),
'on_change_with_substitute_domain')
@fields.depends('technician_replaced', 'planification',
'_parent_planification.laboratory')
def on_change_with_substitute_domain(self, name=None):
pool = Pool()
UserLaboratory = pool.get('lims.user-laboratory')
LaboratoryProfessional = pool.get('lims.laboratory.professional')
PlanificationServiceDetail = pool.get(
'lims.planification.service_detail')
LabProfessionalMethod = pool.get('lims.lab.professional.method')
if not self.technician_replaced:
return []
substitute_domain = []
users = UserLaboratory.search([
('laboratory', '=', self.planification.laboratory.id),
])
if users:
professionals = LaboratoryProfessional.search([
('party.lims_user', 'in', [u.user.id for u in users]),
])
if professionals:
substitute_domain = [p.id for p in professionals]
service_details = PlanificationServiceDetail.search([
('planification', '=', self.planification.id),
('notebook_line', '!=', None),
('staff_responsible', '=', self.technician_replaced.id),
])
methods_ids = []
for service_detail in service_details:
method = service_detail.notebook_line.method
if method:
methods_ids.append(method.id)
methods_ids = list(set(methods_ids))
for technician_id in substitute_domain:
for method_id in methods_ids:
qualifications = LabProfessionalMethod.search([
('professional', '=', technician_id),
('method', '=', method_id),
('type', '=', 'preparation'),
])
if not (qualifications and qualifications[0].state in
('qualified', 'requalified')):
substitute_domain.remove(technician_id)
break
return substitute_domain
class ReplaceTechnician(Wizard):
'Replace Technician'
__name__ = 'lims.planification.replace_technician'
start_state = 'check'
check = StateTransition()
start = StateView('lims.planification.replace_technician.start',
'lims.lims_replace_technician_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Replace', 'replace', 'tryton-ok', default=True),
])
replace = StateTransition()
def transition_check(self):
pool = Pool()
Planification = pool.get('lims.planification')
NotebookLine = pool.get('lims.notebook.line')
planification = Planification(Transaction().context['active_id'])
if planification.state != 'confirmed':
return 'end'
notebook_lines = NotebookLine.search([
('planification', '=', planification.id),
('end_date', '!=', None),
])
if notebook_lines:
return 'end'
return 'start'
def default_start(self, fields):
Planification = Pool().get('lims.planification')
planification = Planification(Transaction().context['active_id'])
return {
'planification': planification.id,
'replaced_domain': [t.laboratory_professional.id
for t in planification.technicians],
'substitute_domain': [],
}
def transition_replace(self):
planification_id = Transaction().context['active_id']
technician_replaced_id = self.start.technician_replaced.id
technician_substitute_id = self.start.technician_substitute.id
self.update_planification_detail(planification_id,
technician_replaced_id, technician_substitute_id)
self.update_planification_technician(planification_id,
technician_replaced_id, technician_substitute_id)
self.update_laboratory_notebook(planification_id,
technician_replaced_id, technician_substitute_id)
return 'end'
def update_planification_detail(self, planification_id,
technician_replaced_id, technician_substitute_id):
PlanificationServiceDetailProfessional = Pool().get(
'lims.planification.service_detail-laboratory.professional')
details_professional = PlanificationServiceDetailProfessional.search([
('detail.detail.planification', '=', planification_id),
('professional', '=', technician_replaced_id),
])
PlanificationServiceDetailProfessional.write(details_professional, {
'professional': technician_substitute_id,
})
def update_planification_technician(self, planification_id,
technician_replaced_id, technician_substitute_id):
PlanificationTechnician = Pool().get('lims.planification.technician')
planification_professional = PlanificationTechnician.search([
('planification', '=', planification_id),
('laboratory_professional', '=', technician_replaced_id),
])
PlanificationTechnician.delete(planification_professional)
planification_professional = PlanificationTechnician.search([
('planification', '=', planification_id),
('laboratory_professional', '=', technician_substitute_id),
])
if not planification_professional:
PlanificationTechnician.create([{
'planification': planification_id,
'laboratory_professional': technician_substitute_id,
}])
def update_laboratory_notebook(self, planification_id,
technician_replaced_id, technician_substitute_id):
NotebookLineProfessional = Pool().get(
'lims.notebook.line-laboratory.professional')
notebook_line_professional = NotebookLineProfessional.search([
('notebook_line.planification', '=', planification_id),
('professional', '=', technician_replaced_id),
])
NotebookLineProfessional.write(notebook_line_professional, {
'professional': technician_substitute_id,
})
class PlanificationSequenceReport(Report):
'Sequence'
__name__ = 'lims.planification.sequence.report'
@classmethod
def get_context(cls, records, header, data):
report_context = super().get_context(records, header, data)
report_context['company'] = report_context['user'].company
objects = {}
for planification in records:
if planification.state != 'confirmed':
continue
date = str(planification.start_date)
if date not in objects:
objects[date] = {
'date': planification.start_date,
'methods': {},
}
for detail in planification.details:
fraction = detail.fraction
for service_detail in detail.details:
if (service_detail.notebook_line.analysis.behavior ==
'internal_relation'):
continue
notebook_line = service_detail.notebook_line
method_id = notebook_line.method.id
if method_id not in objects[date]['methods']:
objects[date]['methods'][method_id] = {
'method': notebook_line.method.code,
'lines': {},
}
number = fraction.get_formated_number('sn-sy-fn')
number = (number + '-' + str(notebook_line.repetition))
number_parts = number.split('-')
order = (number_parts[1] + '-' + number_parts[0] + '-' +
number_parts[2] + '-' + number_parts[3])
product_type = fraction.product_type.code
matrix = fraction.matrix.code
fraction_type = fraction.type.code
comments = fraction.comments
analysis_origin = notebook_line.analysis_origin
priority = notebook_line.priority
urgent = notebook_line.urgent
report_date = (notebook_line.report_date or
notebook_line.results_estimated_date)
trace_report = fraction.sample.trace_report
sample_client_description = (
fraction.sample.sample_client_description)
key = (number, product_type, matrix, fraction_type,
analysis_origin, priority, trace_report)
if key not in objects[date]['methods'][method_id]['lines']:
objects[date]['methods'][method_id]['lines'][key] = {
'order': order,
'number': number,
'product_type': product_type,
'matrix': matrix,
'fraction_type': fraction_type,
'analysis_origin': analysis_origin,
'urgent': urgent,
'priority': priority,
'report_date': report_date,
'trace_report': trace_report,
'comments': comments,
'sample_client_description': (
sample_client_description),
}
for k1 in objects.keys():
for k2, lines in objects[k1]['methods'].items():
sorted_lines = sorted(list(lines['lines'].values()),
key=lambda x: x['order'])
objects[k1]['methods'][k2]['lines'] = sorted_lines
report_context['records'] = objects
return report_context
class PlanificationWorksheetAnalysisReport(Report):
'Worksheet by Analysis'
__name__ = 'lims.planification.worksheet_analysis.report'
@classmethod
def get_context(cls, records, header, data):
report_context = super().get_context(records, header, data)
report_context['company'] = report_context['user'].company
objects = {}
for planification in records:
if planification.state != 'confirmed':
continue
date = str(planification.start_date)
if date not in objects:
objects[date] = {
'date': planification.start_date,
'professionals': {},
}
for detail in planification.details:
fraction = detail.fraction
for service_detail in detail.details:
if not service_detail.notebook_line:
continue
if (service_detail.notebook_line.analysis.behavior ==
'internal_relation'):
continue
p_key = ()
p_names = ''
for professional in service_detail.staff_responsible:
p_key += (professional.id,)
if p_names:
p_names += ', '
p_names += professional.rec_name
if not p_key:
continue
p_key = tuple(sorted(p_key))
if (p_key not in objects[date]['professionals']):
objects[date]['professionals'][p_key] = {
'professional': p_names,
'analysis': {},
'total': 0,
}
notebook_line = service_detail.notebook_line
key = (notebook_line.analysis.id, notebook_line.method.id)
if (key not in
objects[date]['professionals'][p_key]['analysis']):
objects[date]['professionals'][p_key]['analysis'][
key] = {
'order': notebook_line.analysis.order or 9999,
'analysis': notebook_line.analysis.rec_name,
'method': notebook_line.method.rec_name,
'lines': {},
}
number = fraction.get_formated_number('pt-m-sy-sn-fn')
number = (number + '-' + str(notebook_line.repetition))
concentration_level = (
notebook_line.concentration_level.description if
notebook_line.concentration_level else '')
if (number in objects[date]['professionals'][p_key][
'analysis'][key]['lines']):
continue
number_parts = number.split('-')
order = (number_parts[3] + '-' + number_parts[2] + '-' +
number_parts[4] + '-' + number_parts[5])
comments = '%s - %s - %s' % (planification.comments or '',
notebook_line.service.comments or '',
notebook_line.service.fraction.comments or '')
record = {
'order': order,
'number': number,
'label': fraction.label,
'sample_client_description': (
fraction.sample.sample_client_description),
'party': fraction.party.code,
'storage_location': fraction.storage_location.code,
'fraction_type': fraction.type.code,
'concentration_level': concentration_level,
'device': (notebook_line.device.code if
notebook_line.device else None),
'urgent': 'SI' if notebook_line.urgent else '',
'comments': comments,
'planification_code': planification.code,
'sample_obj_description': (
fraction.sample.obj_description.description
if fraction.sample.obj_description else
fraction.sample.obj_description_manual
if fraction.sample.obj_description_manual else
''),
}
objects[date]['professionals'][p_key]['analysis'][
key]['lines'][number] = record
objects[date]['professionals'][p_key]['total'] += 1
for k1 in objects.keys():
for k2 in objects[k1]['professionals'].keys():
sorted_analysis = sorted(list(objects[k1]['professionals'][k2][
'analysis'].items()), key=lambda x: x[1]['order'])
objects[k1]['professionals'][k2]['analysis'] = []
for item in sorted_analysis:
sorted_lines = sorted(list(item[1]['lines'].items()),
key=lambda x: x[1]['order'])
item[1]['lines'] = [l[1] for l in sorted_lines]
objects[k1]['professionals'][k2]['analysis'].append(
item[1])
report_context['records'] = objects
return report_context
class PlanificationWorksheetMethodReport(Report):
'Worksheet by Method'
__name__ = 'lims.planification.worksheet_method.report'
@classmethod
def get_context(cls, records, header, data):
report_context = super().get_context(records, header, data)
report_context['company'] = report_context['user'].company
objects = {}
for planification in records:
if planification.state != 'confirmed':
continue
date = str(planification.start_date)
if date not in objects:
objects[date] = {
'date': planification.start_date,
'professionals': {},
}
for detail in planification.details:
fraction = detail.fraction
for service_detail in detail.details:
if not service_detail.notebook_line:
continue
if (service_detail.notebook_line.analysis.behavior ==
'internal_relation'):
continue
p_key = ()
p_names = ''
for professional in service_detail.staff_responsible:
p_key += (professional.id,)
if p_names:
p_names += ', '
p_names += professional.rec_name
if not p_key:
continue
p_key = tuple(sorted(p_key))
if (p_key not in objects[date]['professionals']):
objects[date]['professionals'][p_key] = {
'professional': p_names,
'total': 0,
'lines': {},
}
notebook_line = service_detail.notebook_line
number = fraction.get_formated_number('pt-m-sy-sn-fn')
number = (number + '-' + str(notebook_line.repetition))
concentration_level = (
notebook_line.concentration_level.description if
notebook_line.concentration_level else '')
if (number not in objects[date]['professionals'][p_key][
'lines']):
number_parts = number.split('-')
order = (number_parts[3] + '-' + number_parts[2] +
'-' + number_parts[4] + '-' + number_parts[5])
comments_planif = '. '.join(cls.get_planning_legend(
fraction, planification))
if comments_planif:
comments = '%s - %s - %s - %s' % (
planification.comments or '',
notebook_line.service.comments or '',
notebook_line.service.fraction.comments or '',
comments_planif or '')
else:
comments = '%s - %s - %s' % (
planification.comments or
'', notebook_line.service.comments or '',
notebook_line.service.fraction.comments or '')
if fraction.packages_quantity != 0.0:
pack_quant = str(fraction.packages_quantity)
else:
pack_quant = ''
record = {
'order': order,
'number': number,
'label': fraction.label,
'sample_client_description': (
fraction.sample.sample_client_description),
'party': fraction.party.code,
'storage_location': fraction.storage_location.code,
'fraction_type': fraction.type.code,
'concentration_level': concentration_level,
'device': (notebook_line.device.code if
notebook_line.device else None),
'urgent': 'SI' if notebook_line.urgent else '',
'comments': comments,
'planification_code': planification.code,
'package_type':
pack_quant + ' ' +
fraction.package_type.description,
'methods': {},
'sample_obj_description': (
fraction.sample.obj_description.description
if fraction.sample.obj_description else
fraction.sample.obj_description_manual
if fraction.sample.obj_description_manual else
''),
}
objects[date]['professionals'][p_key]['lines'][
number] = record
objects[date]['professionals'][p_key]['total'] += 1
if (notebook_line.method.id not in
objects[date]['professionals'][p_key]['lines'][
number]['methods']):
objects[date]['professionals'][p_key]['lines'][
number]['methods'][notebook_line.method.id] = (
notebook_line.method.rec_name)
for k1 in objects.keys():
for k2 in objects[k1]['professionals'].keys():
objects[k1]['professionals'][k2]['methods'] = {}
fractions = list(
objects[k1]['professionals'][k2]['lines'].values())
for fraction in fractions:
m_key = ()
m_names = []
for m_id, m_name in fraction['methods'].items():
m_key += (m_id,)
m_names.append(m_name)
m_key = tuple(sorted(m_key))
if (m_key not in objects[k1]['professionals'][k2][
'methods']):
objects[k1]['professionals'][k2]['methods'][m_key] = {
'methods': m_names,
'lines': [],
}
objects[k1]['professionals'][k2]['methods'][m_key][
'lines'].append(fraction)
del objects[k1]['professionals'][k2]['lines']
for m_key in objects[k1]['professionals'][k2][
'methods'].keys():
sorted_lines = sorted(objects[k1]['professionals'][k2][
'methods'][m_key]['lines'], key=lambda x: x['order'])
objects[k1]['professionals'][k2]['methods'][m_key][
'lines'] = sorted_lines
report_context['records'] = objects
return report_context
@classmethod
def get_planning_legend(cls, fraction, planification):
cursor = Transaction().connection.cursor()
pool = Pool()
Analysis = pool.get('lims.analysis')
PlanificationServiceDetail = pool.get(
'lims.planification.service_detail')
PlanificationDetail = pool.get('lims.planification.detail')
NotebookLine = pool.get('lims.notebook.line')
cursor.execute('SELECT DISTINCT(a.planning_legend) '
'FROM "' + PlanificationDetail._table + '" pd '
'INNER JOIN "' + PlanificationServiceDetail._table +
'" psd '
'ON pd.id = psd.detail '
'INNER JOIN "' + NotebookLine._table + '" nl '
'ON nl.id = psd.notebook_line '
'INNER JOIN "' + Analysis._table + '" a '
'ON a.id = nl.analysis '
'WHERE pd.planification = %s '
'AND pd.fraction = %s '
'AND a.planning_legend IS NOT NULL ',
(planification.id, fraction.id))
planned_ids = [s[0] for s in cursor.fetchall()]
return planned_ids
class PlanificationWorksheetReport(Report):
'Worksheet'
__name__ = 'lims.planification.worksheet.report'
@classmethod
def get_context(cls, records, header, data):
report_context = super().get_context(records, header, data)
report_context['company'] = report_context['user'].company
objects = {}
for planification in records:
if planification.state != 'confirmed':
continue
date = str(planification.start_date)
if date not in objects:
objects[date] = {
'date': planification.start_date,
'professionals': {},
}
for detail in planification.details:
fraction = detail.fraction
for service_detail in detail.details:
if not service_detail.notebook_line:
continue
if (service_detail.notebook_line.analysis.behavior ==
'internal_relation'):
continue
p_key = ()
p_names = ''
for professional in service_detail.staff_responsible:
p_key += (professional.id,)
if p_names:
p_names += ', '
p_names += professional.rec_name
if not p_key:
continue
p_key = tuple(sorted(p_key))
if (p_key not in objects[date]['professionals']):
objects[date]['professionals'][p_key] = {
'professional': p_names,
'analysis': {},
'total': 0,
}
notebook_line = service_detail.notebook_line
key = service_detail.planned_service.id
if (key not in
objects[date]['professionals'][p_key]['analysis']):
objects[date]['professionals'][p_key]['analysis'][
key] = {
'analysis': (
service_detail.planned_service.rec_name),
'lines': {},
}
number = fraction.get_formated_number('pt-m-sy-sn-fn')
number = (number + '-' + str(notebook_line.repetition))
concentration_level = (
notebook_line.concentration_level.description if
notebook_line.concentration_level else '')
if (number not in objects[date]['professionals'][p_key][
'analysis'][key]['lines']):
number_parts = number.split('-')
order = (number_parts[3] + '-' + number_parts[2] +
'-' + number_parts[4] + '-' + number_parts[5])
comments_planif = '. '.join(cls.get_planning_legend(
fraction, planification))
if comments_planif:
comments = '%s - %s - %s - %s' % (
planification.comments or '',
notebook_line.service.comments or '',
notebook_line.service.fraction.comments or '',
comments_planif or '')
else:
comments = '%s - %s - %s' % (
planification.comments or
'', notebook_line.service.comments or '',
notebook_line.service.fraction.comments or '')
if fraction.packages_quantity != 0.0:
pack_quant = str(fraction.packages_quantity)
else:
pack_quant = ''
record = {
'order': order,
'number': number,
'label': fraction.label,
'sample_client_description': (
fraction.sample.sample_client_description if
fraction.sample.sample_client_description else
''),
'party': fraction.party.code,
'storage_location': fraction.storage_location.code,
'fraction_type': fraction.type.code,
'concentration_level': concentration_level,
'device': (notebook_line.device.code if
notebook_line.device else None),
'urgent': 'SI' if notebook_line.urgent else '',
'comments': comments,
'planification_code': planification.code,
'package_type':
pack_quant + ' ' +
fraction.package_type.description,
'methods': {},
'sample_obj_description': (
fraction.sample.obj_description.description
if fraction.sample.obj_description else
fraction.sample.obj_description_manual
if fraction.sample.obj_description_manual else
''),
}
objects[date]['professionals'][p_key]['analysis'][key][
'lines'][number] = record
objects[date]['professionals'][p_key]['total'] += 1
if (notebook_line.method.id not in
objects[date]['professionals'][p_key]['analysis'][
key]['lines'][number]['methods']):
objects[date]['professionals'][p_key]['analysis'][
key]['lines'][number]['methods'][
notebook_line.method.id] = (
notebook_line.method.rec_name)
for k1 in objects.keys():
for k2 in objects[k1]['professionals'].keys():
for k3 in objects[k1]['professionals'][k2][
'analysis'].keys():
objects[k1]['professionals'][k2]['analysis'][k3][
'methods'] = {}
fractions = list(
objects[k1]['professionals'][k2]['analysis'][
k3]['lines'].values())
for fraction in fractions:
m_key = ()
m_names = []
for m_id, m_name in fraction['methods'].items():
m_key += (m_id,)
m_names.append(m_name)
m_key = tuple(sorted(m_key))
if (m_key not in objects[k1]['professionals'][k2][
'analysis'][k3]['methods']):
objects[k1]['professionals'][k2]['analysis'][k3][
'methods'][m_key] = {
'methods': m_names,
'lines': [],
}
objects[k1]['professionals'][k2]['analysis'][k3][
'methods'][m_key]['lines'].append(fraction)
del objects[k1]['professionals'][k2]['analysis'][k3][
'lines']
for m_key in objects[k1]['professionals'][k2][
'analysis'][k3]['methods'].keys():
sorted_lines = sorted(objects[k1]['professionals'][k2][
'analysis'][k3]['methods'][m_key]['lines'],
key=lambda x: x['order'])
objects[k1]['professionals'][k2]['analysis'][k3][
'methods'][m_key]['lines'] = sorted_lines
report_context['records'] = objects
return report_context
@classmethod
def get_planning_legend(cls, fraction, planification):
cursor = Transaction().connection.cursor()
pool = Pool()
Analysis = pool.get('lims.analysis')
PlanificationServiceDetail = pool.get(
'lims.planification.service_detail')
PlanificationDetail = pool.get('lims.planification.detail')
NotebookLine = pool.get('lims.notebook.line')
cursor.execute('SELECT DISTINCT(a.planning_legend) '
'FROM "' + PlanificationDetail._table + '" pd '
'INNER JOIN "' + PlanificationServiceDetail._table +
'" psd '
'ON pd.id = psd.detail '
'INNER JOIN "' + NotebookLine._table + '" nl '
'ON nl.id = psd.notebook_line '
'INNER JOIN "' + Analysis._table + '" a '
'ON a.id = nl.analysis '
'WHERE pd.planification = %s '
'AND pd.fraction = %s '
'AND a.planning_legend IS NOT NULL ',
(planification.id, fraction.id))
planned_ids = [s[0] for s in cursor.fetchall()]
return planned_ids
class PrintPendingServicesUnplannedReportStart(ModelView):
'Print Pending Services Unplanned Report Start'
__name__ = 'lims.pending_services_unplanned.start'
start_date = fields.Date('Start Date', required=True)
end_date = fields.Date('End Date', required=True)
party = fields.Many2One('party.party', 'Party')
include_method = fields.Boolean('Include method')
class PrintPendingServicesUnplannedReport(Wizard):
'Print Pending Services Unplanned Report'
__name__ = 'lims.pending_services_unplanned'
start = StateView('lims.pending_services_unplanned.start',
'lims.print_pending_services_unplanned_report_start'
'_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Print', 'print_', 'tryton-print', default=True),
Button('Save', 'save', 'tryton-save'),
])
print_ = StateAction(
'lims.report_pending_services_unplanned')
save = StateAction(
'lims.report_pending_services_unplanned_spreadsheet')
@classmethod
def check_access(cls):
pass
def do_print_(self, action):
data = {
'start_date': self.start.start_date,
'end_date': self.start.end_date or None,
'party': self.start.party and self.start.party.id or None,
'include_method': self.start.include_method,
}
return action, data
def do_save(self, action):
data = {
'start_date': self.start.start_date,
'end_date': self.start.end_date or None,
'party': self.start.party and self.start.party.id or None,
'include_method': self.start.include_method,
}
return action, data
class PendingServicesUnplannedReport(Report):
'Pending Services Unplanned'
__name__ = 'lims.pending_services_unplanned.report'
@classmethod
def get_context(cls, records, header, data):
pool = Pool()
Laboratory = pool.get('lims.laboratory')
EntryDetailAnalysis = pool.get('lims.entry.detail.analysis')
NotebookLine = pool.get('lims.notebook.line')
report_context = super().get_context(records, header, data)
report_context['company'] = report_context['user'].company
report_context['start_date'] = (data['start_date']
if data['start_date'] else '')
report_context['end_date'] = (data['end_date']
if data['end_date'] else '')
report_context['include_method'] = data['include_method']
if report_context['user'].laboratory:
labs = [report_context['user'].laboratory.id]
else:
labs = [l.id for l in Laboratory.search([])]
clause = cls._get_details_clause(data)
objects = {}
with Transaction().set_user(0):
details = EntryDetailAnalysis.search(clause)
for detail in details:
# Check for available lines
if NotebookLine.search_count([
('analysis_detail', '=', detail),
('start_date', '=', None),
('annulment_date', '=', None),
]) == 0:
continue
# Laboratory
laboratory_id = detail.laboratory.id
if laboratory_id not in labs:
continue
if laboratory_id not in objects:
objects[laboratory_id] = {
'laboratory': detail.laboratory.rec_name,
'services': {},
'total': 0,
}
# Service
analysis_id = detail.service.analysis.id
if analysis_id not in objects[laboratory_id]['services']:
objects[laboratory_id]['services'][analysis_id] = {
'service': detail.service.analysis.rec_name,
'parties': {},
'total': 0,
}
# Party
party_id = detail.party.id
if (party_id not in objects[laboratory_id]['services'][
analysis_id]['parties']):
objects[laboratory_id]['services'][analysis_id]['parties'][
party_id] = {
'party': detail.party.code,
'lines': {},
'total': 0,
}
# Key
if data['include_method']:
key = (detail.fraction.id, detail.method.id)
else:
key = detail.fraction.id
if (key in objects[laboratory_id]['services'][analysis_id][
'parties'][party_id]['lines']):
continue
number = detail.service.fraction.get_formated_number(
'pt-m-sn-sy-fn')
number = (number + '-' + str(detail.service.sample.label))
number_parts = number.split('-')
order = (number_parts[3] + '-' + number_parts[2] + '-' +
number_parts[4])
results_estimated_date = None
results_estimated_waiting = cls._get_estimated_waiting(detail.id)
if results_estimated_waiting:
results_estimated_date = (detail.service.confirmation_date +
relativedelta(days=results_estimated_waiting))
record = {
'order': order,
'number': number,
'date': detail.service.sample.date2,
'sample_client_description': (
detail.service.sample.sample_client_description),
'current_location': (
detail.service.fraction.current_location.code
if detail.service.fraction.current_location else ''),
'fraction_type': detail.service.fraction.type.code,
'priority': detail.service.priority,
'urgent': 'Yes' if detail.service.urgent else 'No',
'method': detail.method.code if data['include_method'] else '',
'comments': ('%s - %s' % (
detail.service.fraction.comments or '',
detail.service.sample.comments or '')),
'report_date': detail.service.report_date,
'confirmation_date': (detail.service.confirmation_date
if detail.service.confirmation_date else ''),
'results_estimated_date': results_estimated_date or '',
}
objects[laboratory_id]['services'][analysis_id]['parties'][
party_id]['lines'][key] = record
objects[laboratory_id]['services'][analysis_id]['parties'][
party_id]['total'] += 1
objects[laboratory_id]['services'][analysis_id][
'total'] += 1
objects[laboratory_id]['total'] += 1
for k1 in objects.keys():
for k2 in objects[k1]['services'].keys():
for k3, lines in objects[k1]['services'][k2][
'parties'].items():
sorted_lines = sorted(lines['lines'].values(),
key=lambda x: x['order'])
objects[k1]['services'][k2]['parties'][k3]['lines'] = (
sorted_lines)
report_context['records'] = objects
return report_context
@classmethod
def _get_details_clause(cls, data):
clause = [
('plannable', '=', True),
('state', '=', 'unplanned'),
('analysis.behavior', '!=', 'internal_relation'),
('service.fraction.confirmed', '=', True),
]
if data['start_date']:
clause.append(
('service.confirmation_date', '>=', data['start_date']))
if data['end_date']:
clause.append(
('service.confirmation_date', '<=', data['end_date']))
if data['party']:
clause.append(('party', '=', data['party']))
return clause
@classmethod
def _get_estimated_waiting(cls, detail_id):
cursor = Transaction().connection.cursor()
NotebookLine = Pool().get('lims.notebook.line')
cursor.execute('SELECT MIN(results_estimated_waiting) '
'FROM "' + NotebookLine._table + '" '
'WHERE analysis_detail = %s '
'AND results_estimated_waiting IS NOT NULL',
(detail_id,))
res = cursor.fetchone()
return res and res[0] or None
class PendingServicesUnplannedSpreadsheet(Report):
'Pending Services Unplanned'
__name__ = 'lims.pending_services_unplanned.spreadsheet'
@classmethod
def get_context(cls, records, header, data):
pool = Pool()
Laboratory = pool.get('lims.laboratory')
EntryDetailAnalysis = pool.get('lims.entry.detail.analysis')
report_context = super().get_context(records, header, data)
report_context['company'] = report_context['user'].company
report_context['start_date'] = (data['start_date']
if data['start_date'] else '')
report_context['end_date'] = (data['end_date']
if data['end_date'] else '')
report_context['include_method'] = data['include_method']
if report_context['user'].laboratory:
labs = [report_context['user'].laboratory.id]
else:
labs = [l.id for l in Laboratory.search([])]
clause = cls._get_details_clause(data)
objects = {}
with Transaction().set_user(0):
details = EntryDetailAnalysis.search(clause)
for detail in details:
# Laboratory
laboratory_id = detail.laboratory.id
if laboratory_id not in labs:
continue
# Service
analysis_id = detail.service.analysis.id
# Party
party_id = detail.party.id
# Key
if data['include_method']:
key = (laboratory_id, analysis_id, party_id,
detail.fraction.id, detail.method.id)
else:
key = (laboratory_id, analysis_id, party_id,
detail.fraction.id)
if key in objects:
continue
number = detail.service.fraction.get_formated_number(
'pt-m-sn-sy-fn')
number = (number + '-' + str(detail.service.sample.label))
number_parts = number.split('-')
order = (number_parts[3] + '-' + number_parts[2] + '-' +
number_parts[4])
results_estimated_date = None
results_estimated_waiting = cls._get_estimated_waiting(detail.id)
if results_estimated_waiting:
results_estimated_date = (detail.service.confirmation_date +
relativedelta(days=results_estimated_waiting))
notice = None
report_date = detail.service.report_date
today = get_print_date().date()
if report_date:
if report_date < today:
notice = 'Timed out'
else:
d = (report_date - today).days
if d >= 0 and d < 3:
notice = 'To expire'
elif results_estimated_date:
if results_estimated_date < today:
notice = 'Timed out'
else:
d = (results_estimated_date - today).days
if d >= 0 and d < 3:
notice = 'To expire'
record = {
'laboratory': detail.laboratory.rec_name,
'service': detail.service.analysis.rec_name,
'party': detail.party.code,
'order': order,
'number': number,
'date': detail.service.sample.date2,
'sample_client_description': (
detail.service.sample.sample_client_description),
'current_location': (
detail.service.fraction.current_location.code
if detail.service.fraction.current_location else ''),
'fraction_type': detail.service.fraction.type.code,
'priority': detail.service.priority,
'urgent': 'Yes' if detail.service.urgent else 'No',
'method': detail.method.code if data['include_method'] else '',
'comments': ('%s - %s' % (
detail.service.fraction.comments or '',
detail.service.sample.comments or '')),
'report_date': detail.service.report_date,
'confirmation_date': (detail.service.confirmation_date
if detail.service.confirmation_date else ''),
'results_estimated_date': results_estimated_date or '',
'results_estimated_waiting': results_estimated_waiting or '',
'notice': notice or '',
}
objects[key] = record
objects = sorted(objects.values(), key=lambda x: (
x['laboratory'], x['service'], x['party'], x['order']))
report_context['records'] = objects
return report_context
@classmethod
def _get_details_clause(cls, data):
clause = [
('plannable', '=', True),
('state', '=', 'unplanned'),
('analysis.behavior', '!=', 'internal_relation'),
('service.fraction.confirmed', '=', True),
]
if data['start_date']:
clause.append(
('service.confirmation_date', '>=', data['start_date']))
if data['end_date']:
clause.append(
('service.confirmation_date', '<=', data['end_date']))
if data['party']:
clause.append(('party', '=', data['party']))
return clause
@classmethod
def _get_estimated_waiting(cls, detail_id):
cursor = Transaction().connection.cursor()
NotebookLine = Pool().get('lims.notebook.line')
cursor.execute('SELECT MIN(results_estimated_waiting) '
'FROM "' + NotebookLine._table + '" '
'WHERE analysis_detail = %s '
'AND results_estimated_waiting IS NOT NULL',
(detail_id,))
res = cursor.fetchone()
return res and res[0] or None
class PrintBlindSampleReportStart(ModelView):
'Blind Samples Report'
__name__ = 'lims.print_blind_sample_report.start'
date_from = fields.Date('Date from', required=True)
date_to = fields.Date('Date to', required=True)
class PrintBlindSampleReport(Wizard):
'Blind Samples Report'
__name__ = 'lims.print_blind_sample_report'
start = StateView('lims.print_blind_sample_report.start',
'lims.lims_print_blind_sample_report_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Print', 'print_', 'tryton-print', default=True),
])
print_ = StateAction('lims.report_blind_sample')
def do_print_(self, action):
BlindSample = Pool().get('lims.blind_sample')
blind_samples = BlindSample.search_count([
('date', '>=', self.start.date_from),
('date', '<=', self.start.date_to),
('line.result', '!=', None),
])
if blind_samples > 0:
data = {
'date_from': self.start.date_from,
'date_to': self.start.date_to,
}
return action, data
def transition_print_(self):
return 'end'
class BlindSampleReport(Report):
'Blind Samples Report'
__name__ = 'lims.blind_sample_report'
@classmethod
def get_context(cls, records, header, data):
BlindSample = Pool().get('lims.blind_sample')
report_context = super().get_context(records, header, data)
report_context['company'] = report_context['user'].company
objects = []
blind_samples = BlindSample.search([
('date', '>=', data['date_from']),
('date', '<=', data['date_to']),
('line.result', '!=', None),
], order=[('date', 'ASC')])
for bs in blind_samples:
if bs.line.converted_result:
result = float(bs.line.converted_result)
unit = bs.line.final_unit
concentration = bs.line.final_concentration
result = round(result, bs.line.decimals)
elif bs.line.result:
result = float(bs.line.result)
unit = bs.line.initial_unit
concentration = bs.line.initial_concentration
result = round(result, bs.line.decimals)
else:
result = None
unit = None
concentration = ''
record = {
'date': bs.date,
'fraction': bs.fraction.rec_name,
'report': (bs.line.results_report.number if
bs.line.results_report else ''),
'repetition': bs.line.repetition,
'analysis_origin': bs.line.analysis_origin,
'analysis': bs.analysis.rec_name,
'result': result,
'unit': unit.symbol if unit else '',
'concentration': concentration,
'original_fraction': '',
'original_report': '',
'original_result': '',
'original_unit': '',
'original_concentration': '',
'error': '',
'max_sd': '',
'two_max_sd': '',
'accepted': '',
}
if bs.original_fraction:
record['original_fraction'] = bs.original_fraction.rec_name
if bs.original_line:
if bs.original_line.results_report:
record['original_report'] = (
bs.original_line.results_report.number)
if bs.original_line.converted_result:
original_result = float(
bs.original_line.converted_result)
original_unit = bs.original_line.final_unit
original_concentration = (
bs.original_line.final_concentration)
elif bs.original_line.result:
original_result = float(bs.original_line.result)
original_unit = bs.original_line.initial_unit
original_concentration = (
bs.original_line.initial_concentration)
else:
original_result = None
if original_result:
original_result = round(original_result,
bs.original_line.decimals)
record['original_result'] = original_result
record['original_unit'] = (original_unit.symbol
if original_unit else '')
record['original_concentration'] = (
original_concentration)
if unit == original_unit:
average = (result + original_result) / 2
difference = result - original_result
record['error'] = round(difference * 100 / average,
2)
try:
maximum_concentration = float(
unit.maximum_concentration)
rsd_horwitz = float(unit.rsd_horwitz)
except (TypeError, ValueError):
maximum_concentration = 0
rsd_horwitz = 0
if result <= maximum_concentration:
record['max_sd'] = round(average * rsd_horwitz,
2)
record['two_max_sd'] = round(record['max_sd'] *
2, 2)
if abs(difference) <= record['two_max_sd']:
record['accepted'] = 'a'
else:
record['accepted'] = 'r'
else:
if bs.min_value and bs.max_value:
min_value = float(bs.min_value)
max_value = float(bs.max_value)
if min_value <= result and result <= max_value:
record['error'] = 'OK'
elif result < min_value:
record['error'] = round(result - min_value, 2)
elif result > max_value:
record['error'] = round(result - max_value, 2)
objects.append(record)
report_context['records'] = objects
report_context['date_from'] = data['date_from']
report_context['date_to'] = data['date_to']
return report_context
def _get_variables(self, formula, notebook_line):
VolumeConversion = Pool().get('lims.volume.conversion')
variables = {}
for var in ('DI',):
while True:
idx = formula.find(var)
if idx >= 0:
variables[var] = 0
formula = formula.replace(var, '_')
else:
break
for var in variables.keys():
if var == 'DI':
ic = float(notebook_line.final_concentration)
result = VolumeConversion.brixToDensity(ic)
if result:
variables[var] = result
return variables
class PlanificationSequenceAnalysisReport(Report):
'Sequence Analysis'
__name__ = 'lims.planification.sequence.analysis.report'
@classmethod
def get_context(cls, records, header, data):
report_context = super().get_context(records, header, data)
report_context['company'] = report_context['user'].company
objects = {}
for planification in records:
if planification.state != 'confirmed':
continue
date = str(planification.start_date)
if date not in objects:
objects[date] = {
'date': planification.start_date,
'methods': {},
}
for detail in planification.details:
fraction = detail.fraction
for service_detail in detail.details:
if (service_detail.notebook_line.analysis.behavior ==
'internal_relation'):
continue
notebook_line = service_detail.notebook_line
method_id = notebook_line.method.id
if method_id not in objects[date]['methods']:
objects[date]['methods'][method_id] = {
'method': notebook_line.method.code,
'lines': {},
}
number = fraction.get_formated_number('sn-sy-fn')
number = (number + '-' + str(notebook_line.repetition))
number_parts = number.split('-')
order = (number_parts[1] + '-' + number_parts[0] + '-' +
number_parts[2] + '-' + number_parts[3])
product_type = fraction.product_type.code
matrix = fraction.matrix.code
fraction_type = fraction.type.code
analysis = notebook_line.analysis.rec_name
initial_unit = (notebook_line.initial_unit.symbol
if notebook_line.initial_unit else '')
priority = notebook_line.priority
urgent = notebook_line.urgent
report_date = (notebook_line.report_date or
notebook_line.results_estimated_date)
trace_report = fraction.sample.trace_report
sample_client_description = (
fraction.sample.sample_client_description)
key = (number, product_type, matrix, fraction_type,
analysis, priority, trace_report)
if key not in objects[date]['methods'][method_id]['lines']:
objects[date]['methods'][method_id]['lines'][key] = {
'order': order,
'number': number,
'product_type': product_type,
'matrix': matrix,
'fraction_type': fraction_type,
'analysis': analysis,
'priority': priority,
'urgent': urgent,
'report_date': report_date,
'trace_report': trace_report,
'sample_client_description': (
sample_client_description),
'initial_unit': initial_unit,
}
for k1 in objects.keys():
for k2, lines in objects[k1]['methods'].items():
sorted_lines = sorted(list(lines['lines'].values()),
key=lambda x: x['order'])
objects[k1]['methods'][k2]['lines'] = sorted_lines
report_context['records'] = objects
return report_context