8005 lines
303 KiB
Python
8005 lines
303 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 sys
|
|
from datetime import datetime, timedelta
|
|
from dateutil.relativedelta import relativedelta
|
|
import operator
|
|
from email.mime.base import MIMEBase
|
|
from email.mime.multipart import MIMEMultipart
|
|
from email.mime.application import MIMEApplication
|
|
import logging
|
|
from sql import Literal, Join
|
|
from sql.functions import CurrentTimestamp
|
|
|
|
from trytond.model import Workflow, ModelView, ModelSQL, fields, Unique
|
|
from trytond.pool import Pool
|
|
from trytond.pyson import Eval, Equal, Bool, Not, Or, And, If, Len
|
|
from trytond.transaction import Transaction
|
|
from trytond.tools import get_smtp_server
|
|
from trytond.config import config
|
|
|
|
__all__ = ['LimsLaboratoryProfessional', 'LimsLaboratory', 'LimsLabMethod',
|
|
'LimsLabDeviceType', 'LimsLabDevice', 'LimsLabDeviceLaboratory',
|
|
'LimsProductType', 'LimsMatrix', 'LimsFormula', 'LimsFractionType',
|
|
'LimsLaboratoryCVCorrection', 'LimsFormulaVariable', 'LimsAnalysis',
|
|
'LimsTypification', 'LimsTypificationAditional',
|
|
'LimsTypificationReadOnly', 'LimsCalculatedTypification',
|
|
'LimsCalculatedTypificationReadOnly', 'LimsPackagingType',
|
|
'LimsAnalysisIncluded', 'LimsAnalysisDevice', 'LimsCertificationType',
|
|
'LimsTechnicalScope', 'LimsTechnicalScopeVersion',
|
|
'LimsTechnicalScopeVersionLine', 'LimsPackagingIntegrity',
|
|
'LimsEntrySuspensionReason', 'LimsEntry', 'LimsZone', 'LimsVariety',
|
|
'LimsSampleProducer', 'LimsSample', 'LimsFraction', 'LimsService',
|
|
'LimsConcentrationLevel', 'LimsEntryDetailAnalysis', 'LimsNotebook',
|
|
'LimsResultsReport', 'LimsPlanification', 'LimsNotebookLine',
|
|
'LimsNotebookLineAllFields', 'LimsNotebookLineProfessional',
|
|
'LimsRangeType', 'LimsResultsReportVersion',
|
|
'LimsResultsReportVersionDetail', 'LimsResultsReportVersionDetailLine',
|
|
'LimsAnalysisFamily', 'LimsAnalysisFamilyCertificant', 'LimsMatrixVariety',
|
|
'LimsLabDeviceTypeLabMethod', 'LimsAnalysisLaboratory',
|
|
'LimsAnalysisLabMethod', 'LimsNotebookLineLaboratoryProfessional',
|
|
'LimsEntryInvoiceContact', 'LimsEntryReportContact',
|
|
'LimsEntryAcknowledgmentContact', 'LimsVolumeConversion',
|
|
'LimsUomConversion', 'LimsRange', 'LimsControlTendency',
|
|
'LimsControlTendencyDetail', 'LimsControlTendencyDetailRule']
|
|
|
|
|
|
class LimsLaboratory(ModelSQL, ModelView):
|
|
'Laboratory'
|
|
__name__ = 'lims.laboratory'
|
|
_rec_name = 'description'
|
|
|
|
code = fields.Char('Code', required=True)
|
|
description = fields.Char('Description', required=True)
|
|
default_laboratory_professional = fields.Many2One(
|
|
'lims.laboratory.professional', 'Default professional')
|
|
default_signer = fields.Many2One('lims.laboratory.professional',
|
|
'Default signer', required=True)
|
|
related_location = fields.Many2One('stock.location', 'Related location',
|
|
required=True, domain=[('type', '=', 'storage')])
|
|
cv_corrections = fields.One2Many('lims.laboratory.cv_correction',
|
|
'laboratory', 'CV Corrections',
|
|
help="Corrections for Coefficients of Variation (Control Charts)")
|
|
section = fields.Selection([
|
|
('amb', 'Ambient'),
|
|
('for', 'Formulated'),
|
|
('mi', 'Microbiology'),
|
|
('rp', 'Agrochemical Residues'),
|
|
('sq', 'Chemistry'),
|
|
], 'Section', sort=False)
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(LimsLaboratory, cls).__setup__()
|
|
t = cls.__table__()
|
|
cls._sql_constraints += [
|
|
('code_uniq', Unique(t, t.code),
|
|
'Laboratory code must be unique'),
|
|
]
|
|
|
|
def get_rec_name(self, name):
|
|
if self.code:
|
|
return self.code + ' - ' + self.description
|
|
else:
|
|
return self.description
|
|
|
|
@classmethod
|
|
def search_rec_name(cls, name, clause):
|
|
field = None
|
|
for field in ('code', 'description'):
|
|
records = cls.search([(field,) + tuple(clause[1:])], limit=1)
|
|
if records:
|
|
break
|
|
if records:
|
|
return [(field,) + tuple(clause[1:])]
|
|
return [(cls._rec_name,) + tuple(clause[1:])]
|
|
|
|
|
|
class LimsLaboratoryCVCorrection(ModelSQL, ModelView):
|
|
'CV Correction'
|
|
__name__ = 'lims.laboratory.cv_correction'
|
|
|
|
laboratory = fields.Many2One('lims.laboratory', 'Laboratory',
|
|
required=True, ondelete='CASCADE', select=True)
|
|
fraction_type = fields.Many2One('lims.fraction.type', 'Fraction type',
|
|
required=True)
|
|
min_cv = fields.Float('Minimum CV (%)')
|
|
max_cv = fields.Float('Maximum CV (%)')
|
|
min_cv_corr_fact = fields.Float('Correction factor for Minimum CV',
|
|
help="Correction factor for CV between Min and Max")
|
|
max_cv_corr_fact = fields.Float('Correction factor for Maximum CV',
|
|
help="Correction factor for CV greater than Max")
|
|
|
|
|
|
class LimsLaboratoryProfessional(ModelSQL, ModelView):
|
|
'Laboratory Professional'
|
|
__name__ = 'lims.laboratory.professional'
|
|
_rec_name = 'party'
|
|
|
|
party = fields.Many2One('party.party', 'Party', required=True,
|
|
domain=[('is_lab_professional', '=', True)])
|
|
code = fields.Char('Code')
|
|
role = fields.Char('Signature role', translate=True)
|
|
signature = fields.Binary('Signature')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(LimsLaboratoryProfessional, cls).__setup__()
|
|
t = cls.__table__()
|
|
cls._sql_constraints += [
|
|
('code_uniq', Unique(t, t.code),
|
|
'Professional code must be unique'),
|
|
('party_uniq', Unique(t, t.party),
|
|
'The party is already associated to a professional'),
|
|
]
|
|
|
|
def get_rec_name(self, name):
|
|
if self.party:
|
|
return self.party.name
|
|
|
|
@classmethod
|
|
def search_rec_name(cls, name, clause):
|
|
return [('party',) + tuple(clause[1:])]
|
|
|
|
@classmethod
|
|
def get_lab_professional(cls):
|
|
cursor = Transaction().connection.cursor()
|
|
login_user_id = Transaction().user
|
|
cursor.execute('SELECT id '
|
|
'FROM party_party '
|
|
'WHERE is_lab_professional = true '
|
|
'AND lims_user = %s '
|
|
'LIMIT 1', (login_user_id,))
|
|
party_id = cursor.fetchone()
|
|
if not party_id:
|
|
return None
|
|
cursor.execute('SELECT id '
|
|
'FROM "' + cls._table + '" '
|
|
'WHERE party = %s '
|
|
'LIMIT 1', (party_id[0],))
|
|
lab_professional_id = cursor.fetchone()
|
|
if (lab_professional_id):
|
|
return lab_professional_id[0]
|
|
return None
|
|
|
|
|
|
class LimsLabMethod(ModelSQL, ModelView):
|
|
'Laboratory Method'
|
|
__name__ = 'lims.lab.method'
|
|
|
|
code = fields.Char('Code', required=True)
|
|
name = fields.Char('Name', required=True, translate=True)
|
|
reference = fields.Char('Reference')
|
|
determination = fields.Char('Determination', required=True)
|
|
requalification_months = fields.Integer('Requalification months',
|
|
required=True)
|
|
supervised_requalification = fields.Boolean('Supervised requalification')
|
|
deprecated_since = fields.Date('Deprecated since')
|
|
pnt = fields.Char('PNT')
|
|
results_estimated_waiting = fields.Integer(
|
|
'Estimated number of days for results')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(LimsLabMethod, cls).__setup__()
|
|
t = cls.__table__()
|
|
cls._sql_constraints += [
|
|
('code_uniq', Unique(t, t.code),
|
|
'Method code must be unique'),
|
|
]
|
|
|
|
def get_rec_name(self, name):
|
|
if self.code:
|
|
return self.code + ' - ' + self.name
|
|
else:
|
|
return self.name
|
|
|
|
@classmethod
|
|
def search_rec_name(cls, name, clause):
|
|
field = None
|
|
for field in ('code', 'name'):
|
|
records = cls.search([(field,) + tuple(clause[1:])], limit=1)
|
|
if records:
|
|
break
|
|
if records:
|
|
return [(field,) + tuple(clause[1:])]
|
|
return [(cls._rec_name,) + tuple(clause[1:])]
|
|
|
|
@classmethod
|
|
def write(cls, *args):
|
|
super(LimsLabMethod, cls).write(*args)
|
|
actions = iter(args)
|
|
for methods, vals in zip(actions, actions):
|
|
if 'results_estimated_waiting' in vals:
|
|
cls.update_laboratory_notebook(methods)
|
|
|
|
@classmethod
|
|
def update_laboratory_notebook(cls, methods):
|
|
LimsNotebookLine = Pool().get('lims.notebook.line')
|
|
|
|
for method in methods:
|
|
notebook_lines = LimsNotebookLine.search([
|
|
('method', '=', method.id),
|
|
('accepted', '=', False),
|
|
])
|
|
if notebook_lines:
|
|
LimsNotebookLine.write(notebook_lines, {
|
|
'results_estimated_waiting': (
|
|
method.results_estimated_waiting),
|
|
})
|
|
|
|
|
|
class LimsLabDevice(ModelSQL, ModelView):
|
|
'Laboratory Device'
|
|
__name__ = 'lims.lab.device'
|
|
_rec_name = 'description'
|
|
|
|
code = fields.Char('Code', required=True)
|
|
description = fields.Char('Description', required=True)
|
|
device_type = fields.Many2One('lims.lab.device.type', 'Device type',
|
|
required=True)
|
|
laboratories = fields.One2Many('lims.lab.device.laboratory', 'device',
|
|
'Laboratories', required=True)
|
|
serial_number = fields.Char('Serial number')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(LimsLabDevice, cls).__setup__()
|
|
t = cls.__table__()
|
|
cls._sql_constraints += [
|
|
('code_uniq', Unique(t, t.code),
|
|
'Device code must be unique'),
|
|
]
|
|
|
|
def get_rec_name(self, name):
|
|
if self.code:
|
|
return self.code + ' - ' + self.description
|
|
else:
|
|
return self.description
|
|
|
|
@classmethod
|
|
def search_rec_name(cls, name, clause):
|
|
field = None
|
|
for field in ('code', 'description'):
|
|
records = cls.search([(field,) + tuple(clause[1:])], limit=1)
|
|
if records:
|
|
break
|
|
if records:
|
|
return [(field,) + tuple(clause[1:])]
|
|
return [(cls._rec_name,) + tuple(clause[1:])]
|
|
|
|
|
|
class LimsLabDeviceType(ModelSQL, ModelView):
|
|
'Laboratory Device Type'
|
|
__name__ = 'lims.lab.device.type'
|
|
_rec_name = 'description'
|
|
|
|
code = fields.Char('Code', required=True)
|
|
description = fields.Char('Description', required=True)
|
|
methods = fields.Many2Many('lims.lab.device.type-lab.method',
|
|
'device_type', 'method', 'Methods')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(LimsLabDeviceType, cls).__setup__()
|
|
t = cls.__table__()
|
|
cls._sql_constraints += [
|
|
('code_uniq', Unique(t, t.code),
|
|
'Device type code must be unique'),
|
|
]
|
|
|
|
def get_rec_name(self, name):
|
|
if self.code:
|
|
return self.code + ' - ' + self.description
|
|
else:
|
|
return self.description
|
|
|
|
@classmethod
|
|
def search_rec_name(cls, name, clause):
|
|
field = None
|
|
for field in ('code', 'description'):
|
|
records = cls.search([(field,) + tuple(clause[1:])], limit=1)
|
|
if records:
|
|
break
|
|
if records:
|
|
return [(field,) + tuple(clause[1:])]
|
|
return [(cls._rec_name,) + tuple(clause[1:])]
|
|
|
|
|
|
class LimsLabDeviceTypeLabMethod(ModelSQL):
|
|
'Laboratory Device Type - Laboratory Method'
|
|
__name__ = 'lims.lab.device.type-lab.method'
|
|
|
|
device_type = fields.Many2One('lims.lab.device.type', 'Device type',
|
|
ondelete='CASCADE', select=True, required=True)
|
|
method = fields.Many2One('lims.lab.method', 'Method',
|
|
ondelete='CASCADE', select=True, required=True)
|
|
|
|
|
|
class LimsLabDeviceLaboratory(ModelSQL, ModelView):
|
|
'Laboratory Device Laboratory'
|
|
__name__ = 'lims.lab.device.laboratory'
|
|
|
|
device = fields.Many2One('lims.lab.device', 'Device', required=True,
|
|
ondelete='CASCADE', select=True)
|
|
laboratory = fields.Many2One('lims.laboratory', 'Laboratory',
|
|
required=True)
|
|
physically_here = fields.Boolean('Physically here')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(LimsLabDeviceLaboratory, cls).__setup__()
|
|
cls._error_messages.update({
|
|
'physically_elsewhere': ('This Device is physically in another'
|
|
' Laboratory'),
|
|
})
|
|
|
|
@staticmethod
|
|
def default_physically_here():
|
|
return True
|
|
|
|
@classmethod
|
|
def validate(cls, laboratories):
|
|
super(LimsLabDeviceLaboratory, cls).validate(laboratories)
|
|
for l in laboratories:
|
|
l.check_location()
|
|
|
|
def check_location(self):
|
|
if self.physically_here:
|
|
laboratories = self.search([
|
|
('device', '=', self.device.id),
|
|
('physically_here', '=', True),
|
|
('id', '!=', self.id),
|
|
])
|
|
if laboratories:
|
|
self.raise_user_error('physically_elsewhere')
|
|
|
|
|
|
class LimsTypification(ModelSQL, ModelView):
|
|
'Typification'
|
|
__name__ = 'lims.typification'
|
|
_rec_name = 'analysis'
|
|
|
|
product_type = fields.Many2One('lims.product.type', 'Product type',
|
|
required=True, select=True,
|
|
states={'readonly': Bool(Eval('id', 0) > 0)})
|
|
matrix = fields.Many2One('lims.matrix', 'Matrix', required=True,
|
|
select=True, states={'readonly': Bool(Eval('id', 0) > 0)})
|
|
analysis = fields.Many2One('lims.analysis', 'Analysis', required=True,
|
|
domain=[
|
|
('state', '=', 'active'),
|
|
('type', '=', 'analysis'),
|
|
('behavior', '!=', 'additional'),
|
|
], select=True, states={'readonly': Bool(Eval('id', 0) > 0)})
|
|
method = fields.Many2One('lims.lab.method', 'Method', required=True,
|
|
domain=[('id', 'in', Eval('method_domain'))],
|
|
depends=['method_domain'], select=True)
|
|
method_view = fields.Function(fields.Many2One('lims.lab.method', 'Method'),
|
|
'get_views_field', searcher='search_views_field')
|
|
method_domain = fields.Function(fields.Many2Many('lims.lab.method',
|
|
None, None, 'Method domain'),
|
|
'on_change_with_method_domain')
|
|
detection_limit = fields.Float('Detection limit',
|
|
digits=(16, Eval('limit_digits', 2)), depends=['limit_digits'])
|
|
quantification_limit = fields.Float('Quantification limit',
|
|
digits=(16, Eval('limit_digits', 2)), depends=['limit_digits'])
|
|
limit_digits = fields.Integer('Limit digits')
|
|
check_result_limits = fields.Boolean(
|
|
'Validate limits directly on the result')
|
|
initial_concentration = fields.Char('Initial concentration')
|
|
start_uom = fields.Many2One('product.uom', 'Start UoM',
|
|
domain=[('category.lims_only_available', '=', True)])
|
|
final_concentration = fields.Char('Final concentration')
|
|
end_uom = fields.Many2One('product.uom', 'End UoM',
|
|
domain=[('category.lims_only_available', '=', True)])
|
|
default_repetitions = fields.Integer('Default repetitions',
|
|
required=True)
|
|
technical_scope_versions = fields.Function(fields.One2Many(
|
|
'lims.technical.scope.version', None,
|
|
'Technical scope versions'), 'get_technical_scope_versions')
|
|
comments = fields.Text('Comments')
|
|
additional = fields.Many2One('lims.analysis', 'Additional analysis',
|
|
domain=[('state', '=', 'active'), ('behavior', '=', 'additional')])
|
|
additionals = fields.Many2Many('lims.typification-analysis',
|
|
'typification', 'analysis', 'Additional analysis',
|
|
domain=[('id', 'in', Eval('additionals_domain'))],
|
|
depends=['additionals_domain'])
|
|
additionals_domain = fields.Function(fields.Many2Many('lims.analysis',
|
|
None, None, 'Additional analysis domain'),
|
|
'on_change_with_additionals_domain')
|
|
by_default = fields.Boolean('By default')
|
|
calc_decimals = fields.Integer('Calculation decimals', required=True)
|
|
report = fields.Boolean('Report')
|
|
report_type = fields.Selection([
|
|
('normal', 'Normal'),
|
|
('polisample', 'Polisample'),
|
|
], 'Report type', sort=False)
|
|
report_result_type = fields.Selection([
|
|
('result', 'Result'),
|
|
('both', 'Both'),
|
|
], 'Result type', sort=False)
|
|
valid = fields.Boolean('Active', depends=['valid_readonly'],
|
|
states={'readonly': Bool(Eval('valid_readonly'))})
|
|
valid_view = fields.Function(fields.Boolean('Active'),
|
|
'get_views_field', searcher='search_views_field')
|
|
valid_readonly = fields.Function(fields.Boolean(
|
|
'Field active readonly'),
|
|
'on_change_with_valid_readonly')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(LimsTypification, cls).__setup__()
|
|
cls._order.insert(0, ('product_type', 'ASC'))
|
|
cls._order.insert(1, ('matrix', 'ASC'))
|
|
cls._order.insert(2, ('analysis', 'ASC'))
|
|
cls._order.insert(3, ('method', 'ASC'))
|
|
t = cls.__table__()
|
|
cls._sql_constraints += [
|
|
('product_matrix_analysis_method_uniq',
|
|
Unique(t, t.product_type, t.matrix, t.analysis, t.method),
|
|
'This typification already exists'),
|
|
]
|
|
cls._error_messages.update({
|
|
'limits': ('Quantification limit must be greater than'
|
|
' Detection limit'),
|
|
'default_typification': ('There is already a default typification'
|
|
' for this combination of product type, matrix and analysis'),
|
|
'not_default_typification': ('This typification should be the'
|
|
' default'),
|
|
})
|
|
|
|
@staticmethod
|
|
def default_limit_digits():
|
|
return 2
|
|
|
|
@staticmethod
|
|
def default_default_repetitions():
|
|
return 0
|
|
|
|
@staticmethod
|
|
def default_by_default():
|
|
return True
|
|
|
|
@staticmethod
|
|
def default_calc_decimals():
|
|
return 2
|
|
|
|
@staticmethod
|
|
def default_report():
|
|
return True
|
|
|
|
@staticmethod
|
|
def default_report_type():
|
|
return 'normal'
|
|
|
|
@staticmethod
|
|
def default_report_result_type():
|
|
return 'result'
|
|
|
|
@staticmethod
|
|
def default_valid():
|
|
return True
|
|
|
|
@staticmethod
|
|
def default_check_result_limits():
|
|
return False
|
|
|
|
@classmethod
|
|
def get_views_field(cls, typifications, names):
|
|
result = {}
|
|
for name in names:
|
|
field_name = name[:-5]
|
|
result[name] = {}
|
|
if field_name == 'valid':
|
|
for t in typifications:
|
|
result[name][t.id] = getattr(t, field_name, None)
|
|
else:
|
|
for t in typifications:
|
|
field = getattr(t, field_name, None)
|
|
result[name][t.id] = field.id if field else None
|
|
return result
|
|
|
|
@classmethod
|
|
def search_views_field(cls, name, clause):
|
|
return [(name[:-5],) + tuple(clause[1:])]
|
|
|
|
@fields.depends('analysis')
|
|
def on_change_with_valid_readonly(self, name=None):
|
|
if self.analysis and self.analysis.state == 'disabled':
|
|
return True
|
|
return False
|
|
|
|
@fields.depends('analysis')
|
|
def on_change_analysis(self):
|
|
method = None
|
|
if self.analysis:
|
|
methods = self.on_change_with_method_domain()
|
|
if len(methods) == 1:
|
|
method = methods[0]
|
|
self.method = method
|
|
|
|
@fields.depends('analysis')
|
|
def on_change_with_method_domain(self, name=None):
|
|
methods = []
|
|
if self.analysis and self.analysis.methods:
|
|
methods = [m.id for m in self.analysis.methods]
|
|
return methods
|
|
|
|
def get_technical_scope_versions(self, name=None):
|
|
pool = Pool()
|
|
LimsTechnicalScopeVersionLine = pool.get(
|
|
'lims.technical.scope.version.line')
|
|
|
|
version_lines = LimsTechnicalScopeVersionLine.search([
|
|
('typification', '=', self.id),
|
|
('version.valid', '=', True),
|
|
])
|
|
if version_lines:
|
|
return [line.version.id for line in version_lines]
|
|
return []
|
|
|
|
@fields.depends('analysis', 'product_type', 'matrix')
|
|
def on_change_with_additionals_domain(self, name=None):
|
|
cursor = Transaction().connection.cursor()
|
|
pool = Pool()
|
|
Analysis = pool.get('lims.analysis')
|
|
Typification = pool.get('lims.typification')
|
|
|
|
if not self.analysis:
|
|
return []
|
|
if not self.product_type or not self.matrix:
|
|
return []
|
|
|
|
cursor.execute('SELECT a.id '
|
|
'FROM "' + Analysis._table + '" a '
|
|
'INNER JOIN "' + Typification._table + '" t '
|
|
'ON a.id = t.analysis '
|
|
'WHERE a.id != %s '
|
|
'AND a.type = \'analysis\' '
|
|
'AND a.behavior != \'additional\' '
|
|
'AND a.state = \'active\' '
|
|
'AND t.product_type = %s '
|
|
'AND t.matrix = %s '
|
|
'AND t.valid IS TRUE',
|
|
(self.analysis.id, self.product_type.id, self.matrix.id))
|
|
res = cursor.fetchall()
|
|
if not res:
|
|
return []
|
|
return [x[0] for x in res]
|
|
|
|
def get_rec_name(self, name):
|
|
return self.product_type.rec_name + '-' + self.matrix.rec_name
|
|
|
|
@classmethod
|
|
def search_rec_name(cls, name, clause):
|
|
typifications = cls.search(['OR',
|
|
('product_type',) + tuple(clause[1:]),
|
|
('matrix',) + tuple(clause[1:]),
|
|
('analysis',) + tuple(clause[1:]),
|
|
('method',) + tuple(clause[1:]),
|
|
], order=[])
|
|
if typifications:
|
|
return [('id', 'in', [t.id for t in typifications])]
|
|
return [(cls._rec_name,) + tuple(clause[1:])]
|
|
|
|
@classmethod
|
|
def validate(cls, typifications):
|
|
super(LimsTypification, cls).validate(typifications)
|
|
for t in typifications:
|
|
t.check_limits()
|
|
t.check_default()
|
|
|
|
def check_limits(self):
|
|
if (self.detection_limit and
|
|
self.quantification_limit <= self.detection_limit):
|
|
self.raise_user_error('limits')
|
|
|
|
def check_default(self):
|
|
if self.by_default:
|
|
typifications = self.search([
|
|
('product_type', '=', self.product_type.id),
|
|
('matrix', '=', self.matrix.id),
|
|
('analysis', '=', self.analysis.id),
|
|
('valid', '=', True),
|
|
('by_default', '=', True),
|
|
('id', '!=', self.id),
|
|
])
|
|
if typifications:
|
|
self.raise_user_error('default_typification')
|
|
else:
|
|
if self.valid:
|
|
typifications = self.search([
|
|
('product_type', '=', self.product_type.id),
|
|
('matrix', '=', self.matrix.id),
|
|
('analysis', '=', self.analysis.id),
|
|
('valid', '=', True),
|
|
('id', '!=', self.id),
|
|
])
|
|
if not typifications:
|
|
self.raise_user_error('not_default_typification')
|
|
|
|
@classmethod
|
|
def create(cls, vlist):
|
|
typifications = super(LimsTypification, cls).create(vlist)
|
|
active_typifications = [t for t in typifications if t.valid]
|
|
cls.create_typification_calculated(active_typifications)
|
|
return typifications
|
|
|
|
@classmethod
|
|
def create_typification_calculated(cls, typifications):
|
|
cursor = Transaction().connection.cursor()
|
|
pool = Pool()
|
|
LimsAnalysis = pool.get('lims.analysis')
|
|
LimsCalculatedTypification = pool.get('lims.typification.calculated')
|
|
|
|
for typification in typifications:
|
|
|
|
cursor.execute('SELECT DISTINCT(analysis) '
|
|
'FROM "' + cls._table + '" '
|
|
'WHERE product_type = %s '
|
|
'AND matrix = %s '
|
|
'AND valid',
|
|
(typification.product_type.id, typification.matrix.id))
|
|
typified_analysis = [a[0] for a in cursor.fetchall()]
|
|
typified_analysis_ids = ', '.join(str(a) for a in
|
|
typified_analysis)
|
|
|
|
sets_groups_ids = LimsAnalysis.get_parents_analysis(
|
|
typification.analysis.id)
|
|
for set_group_id in sets_groups_ids:
|
|
t_set_group = LimsCalculatedTypification.search([
|
|
('product_type', '=', typification.product_type.id),
|
|
('matrix', '=', typification.matrix.id),
|
|
('analysis', '=', set_group_id),
|
|
])
|
|
if not t_set_group:
|
|
|
|
ia = LimsAnalysis.get_included_analysis_analysis(
|
|
set_group_id)
|
|
if not ia:
|
|
continue
|
|
included_ids = ', '.join(str(a) for a in ia)
|
|
|
|
cursor.execute('SELECT id '
|
|
'FROM "' + LimsAnalysis._table + '" '
|
|
'WHERE id IN (' + included_ids + ') '
|
|
'AND id NOT IN (' + typified_analysis_ids
|
|
+ ')')
|
|
if cursor.fetchone():
|
|
typified = False
|
|
else:
|
|
typified = True
|
|
|
|
if typified:
|
|
typification_create = [{
|
|
'product_type': typification.product_type.id,
|
|
'matrix': typification.matrix.id,
|
|
'analysis': set_group_id,
|
|
}]
|
|
LimsCalculatedTypification.create(
|
|
typification_create)
|
|
|
|
return typifications
|
|
|
|
@classmethod
|
|
def delete(cls, typifications):
|
|
cls.delete_typification_calculated(typifications)
|
|
super(LimsTypification, cls).delete(typifications)
|
|
|
|
@classmethod
|
|
def delete_typification_calculated(cls, typifications):
|
|
pool = Pool()
|
|
LimsAnalysis = pool.get('lims.analysis')
|
|
LimsCalculatedTypification = pool.get('lims.typification.calculated')
|
|
|
|
for typification in typifications:
|
|
|
|
others = cls.search([
|
|
('product_type', '=', typification.product_type.id),
|
|
('matrix', '=', typification.matrix.id),
|
|
('analysis', '=', typification.analysis.id),
|
|
('valid', '=', True),
|
|
('id', '!=', typification.id),
|
|
])
|
|
if others:
|
|
continue
|
|
|
|
sets_groups_ids = LimsAnalysis.get_parents_analysis(
|
|
typification.analysis.id)
|
|
for set_group_id in sets_groups_ids:
|
|
typified_set_group = LimsCalculatedTypification.search([
|
|
('product_type', '=', typification.product_type.id),
|
|
('matrix', '=', typification.matrix.id),
|
|
('analysis', '=', set_group_id),
|
|
])
|
|
if typified_set_group:
|
|
LimsCalculatedTypification.delete(typified_set_group)
|
|
|
|
@classmethod
|
|
def write(cls, *args):
|
|
super(LimsTypification, cls).write(*args)
|
|
actions = iter(args)
|
|
for typifications, vals in zip(actions, actions):
|
|
if 'valid' in vals:
|
|
if vals['valid']:
|
|
cls.create_typification_calculated(typifications)
|
|
else:
|
|
cls.delete_typification_calculated(typifications)
|
|
|
|
fields_check = ('detection_limit', 'quantification_limit',
|
|
'initial_concentration', 'final_concentration', 'start_uom',
|
|
'end_uom', 'calc_decimals', 'report')
|
|
for field in fields_check:
|
|
if field in vals:
|
|
cls.update_laboratory_notebook(typifications)
|
|
break
|
|
|
|
@classmethod
|
|
def update_laboratory_notebook(cls, typifications):
|
|
LimsNotebookLine = Pool().get('lims.notebook.line')
|
|
|
|
for typification in typifications:
|
|
if not typification.valid:
|
|
continue
|
|
|
|
# Update not RM
|
|
notebook_lines = LimsNotebookLine.search([
|
|
('notebook.fraction.special_type', '!=', 'rm'),
|
|
('notebook.product_type', '=', typification.product_type.id),
|
|
('notebook.matrix', '=', typification.matrix.id),
|
|
('analysis', '=', typification.analysis.id),
|
|
('method', '=', typification.method.id),
|
|
('end_date', '=', None),
|
|
])
|
|
if notebook_lines:
|
|
LimsNotebookLine.write(notebook_lines, {
|
|
'detection_limit': str(
|
|
typification.detection_limit),
|
|
'quantification_limit': str(
|
|
typification.quantification_limit),
|
|
'initial_concentration': unicode(
|
|
typification.initial_concentration or ''),
|
|
'final_concentration': unicode(
|
|
typification.final_concentration or ''),
|
|
'initial_unit': typification.start_uom,
|
|
'final_unit': typification.end_uom,
|
|
'decimals': typification.calc_decimals,
|
|
'report': typification.report,
|
|
})
|
|
|
|
# Update RM
|
|
notebook_lines = LimsNotebookLine.search([
|
|
('notebook.fraction.special_type', '=', 'rm'),
|
|
('notebook.product_type', '=', typification.product_type.id),
|
|
('notebook.matrix', '=', typification.matrix.id),
|
|
('analysis', '=', typification.analysis.id),
|
|
('method', '=', typification.method.id),
|
|
('end_date', '=', None),
|
|
])
|
|
if notebook_lines:
|
|
LimsNotebookLine.write(notebook_lines, {
|
|
'initial_concentration': unicode(
|
|
typification.initial_concentration or ''),
|
|
})
|
|
|
|
|
|
class LimsTypificationAditional(ModelSQL):
|
|
'Typification - Additional analysis'
|
|
__name__ = 'lims.typification-analysis'
|
|
|
|
typification = fields.Many2One('lims.typification', 'Typification',
|
|
ondelete='CASCADE', select=True, required=True)
|
|
analysis = fields.Many2One('lims.analysis', 'Analysis',
|
|
ondelete='CASCADE', select=True, required=True)
|
|
|
|
|
|
class LimsTypificationReadOnly(ModelSQL, ModelView):
|
|
'Typification'
|
|
__name__ = 'lims.typification.readonly'
|
|
|
|
product_type = fields.Many2One('lims.product.type', 'Product type',
|
|
readonly=True)
|
|
matrix = fields.Many2One('lims.matrix', 'Matrix', readonly=True)
|
|
analysis = fields.Many2One('lims.analysis', 'Analysis', readonly=True)
|
|
method = fields.Many2One('lims.lab.method', 'Method', readonly=True)
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(LimsTypificationReadOnly, cls).__setup__()
|
|
cls._order.insert(0, ('product_type', 'ASC'))
|
|
cls._order.insert(1, ('matrix', 'ASC'))
|
|
cls._order.insert(2, ('analysis', 'ASC'))
|
|
cls._order.insert(3, ('method', 'ASC'))
|
|
|
|
@staticmethod
|
|
def table_query():
|
|
pool = Pool()
|
|
typification = pool.get('lims.typification').__table__()
|
|
|
|
columns = [
|
|
typification.id,
|
|
typification.create_uid,
|
|
typification.create_date,
|
|
typification.write_uid,
|
|
typification.write_date,
|
|
typification.product_type,
|
|
typification.matrix,
|
|
typification.analysis,
|
|
typification.method,
|
|
]
|
|
where = Literal(True)
|
|
return typification.select(*columns, where=where)
|
|
|
|
|
|
class LimsCalculatedTypification(ModelSQL):
|
|
'Calculated Typification'
|
|
__name__ = 'lims.typification.calculated'
|
|
_rec_name = 'analysis'
|
|
|
|
product_type = fields.Many2One('lims.product.type', 'Product type',
|
|
required=True, select=True)
|
|
matrix = fields.Many2One('lims.matrix', 'Matrix', required=True,
|
|
select=True)
|
|
analysis = fields.Many2One('lims.analysis', 'Analysis', required=True,
|
|
ondelete='CASCADE', select=True)
|
|
|
|
@classmethod
|
|
def __register__(cls, module_name):
|
|
super(LimsCalculatedTypification, cls).__register__(module_name)
|
|
if cls.search_count([]) == 0:
|
|
cls.populate_typification_calculated()
|
|
|
|
@classmethod
|
|
def populate_typification_calculated(cls):
|
|
cursor = Transaction().connection.cursor()
|
|
pool = Pool()
|
|
LimsAnalysis = pool.get('lims.analysis')
|
|
LimsTypification = pool.get('lims.typification')
|
|
|
|
cursor.execute('SELECT DISTINCT(product_type, matrix) '
|
|
'FROM "' + LimsTypification._table + '" '
|
|
'WHERE valid')
|
|
typifications = cursor.fetchall()
|
|
if typifications:
|
|
typifications_count = 0
|
|
typifications_total = len(typifications)
|
|
for typification in typifications:
|
|
|
|
typifications_count += 1
|
|
logging.getLogger('lims').info(
|
|
'Calculating typification %s of %s' %
|
|
(typifications_count, typifications_total))
|
|
|
|
product_type = int(typification[0].split(',')[0][1:])
|
|
matrix = int(typification[0].split(',')[1][:-1])
|
|
cursor.execute('SELECT DISTINCT(analysis) '
|
|
'FROM "' + LimsTypification._table + '" '
|
|
'WHERE product_type = %s '
|
|
'AND matrix = %s '
|
|
'AND valid',
|
|
(product_type, matrix))
|
|
typified_analysis = [a[0] for a in cursor.fetchall()]
|
|
typified_analysis_ids = ', '.join(str(a) for a in
|
|
typified_analysis)
|
|
|
|
cursor.execute('SELECT id '
|
|
'FROM "' + LimsAnalysis._table + '" '
|
|
'WHERE type IN (\'set\', \'group\') '
|
|
'AND state = \'active\'')
|
|
sets_groups_ids = [x[0] for x in cursor.fetchall()]
|
|
if sets_groups_ids:
|
|
for set_group_id in sets_groups_ids:
|
|
typified = True
|
|
|
|
ia = LimsAnalysis.get_included_analysis_analysis(
|
|
set_group_id)
|
|
if not ia:
|
|
continue
|
|
included_ids = ', '.join(str(a) for a in ia)
|
|
|
|
cursor.execute('SELECT id '
|
|
'FROM "' + LimsAnalysis._table + '" '
|
|
'WHERE id IN (' + included_ids + ') '
|
|
'AND id NOT IN (' + typified_analysis_ids
|
|
+ ')')
|
|
if cursor.fetchone():
|
|
typified = False
|
|
|
|
if typified:
|
|
typification_create = [{
|
|
'product_type': product_type,
|
|
'matrix': matrix,
|
|
'analysis': set_group_id,
|
|
}]
|
|
cls.create(typification_create)
|
|
|
|
|
|
class LimsCalculatedTypificationReadOnly(ModelSQL, ModelView):
|
|
'Calculated Typification'
|
|
__name__ = 'lims.typification.calculated.readonly'
|
|
|
|
product_type = fields.Many2One('lims.product.type', 'Product type',
|
|
readonly=True)
|
|
matrix = fields.Many2One('lims.matrix', 'Matrix', readonly=True)
|
|
analysis = fields.Many2One('lims.analysis', 'Analysis', readonly=True)
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(LimsCalculatedTypificationReadOnly, cls).__setup__()
|
|
cls._order.insert(0, ('product_type', 'ASC'))
|
|
cls._order.insert(1, ('matrix', 'ASC'))
|
|
cls._order.insert(2, ('analysis', 'ASC'))
|
|
|
|
@staticmethod
|
|
def table_query():
|
|
pool = Pool()
|
|
typification = pool.get('lims.typification.calculated').__table__()
|
|
|
|
columns = [
|
|
typification.id,
|
|
typification.create_uid,
|
|
typification.create_date,
|
|
typification.write_uid,
|
|
typification.write_date,
|
|
typification.product_type,
|
|
typification.matrix,
|
|
typification.analysis,
|
|
]
|
|
where = Literal(True)
|
|
return typification.select(*columns, where=where)
|
|
|
|
|
|
class LimsProductType(ModelSQL, ModelView):
|
|
'Product Type'
|
|
__name__ = 'lims.product.type'
|
|
_rec_name = 'description'
|
|
|
|
code = fields.Char('Code', required=True)
|
|
description = fields.Char('Description', required=True)
|
|
restricted_entry = fields.Boolean('Restricted entry')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(LimsProductType, cls).__setup__()
|
|
t = cls.__table__()
|
|
cls._sql_constraints += [
|
|
('code_uniq', Unique(t, t.code),
|
|
'Product type code must be unique'),
|
|
]
|
|
|
|
@staticmethod
|
|
def default_restricted_entry():
|
|
return False
|
|
|
|
def get_rec_name(self, name):
|
|
if self.code:
|
|
return self.code + ' - ' + self.description
|
|
else:
|
|
return self.description
|
|
|
|
@classmethod
|
|
def search_rec_name(cls, name, clause):
|
|
field = None
|
|
for field in ('code', 'description'):
|
|
records = cls.search([(field,) + tuple(clause[1:])], limit=1)
|
|
if records:
|
|
break
|
|
if records:
|
|
return [(field,) + tuple(clause[1:])]
|
|
return [(cls._rec_name,) + tuple(clause[1:])]
|
|
|
|
|
|
class LimsMatrix(ModelSQL, ModelView):
|
|
'Matrix'
|
|
__name__ = 'lims.matrix'
|
|
_rec_name = 'description'
|
|
|
|
code = fields.Char('Code', required=True)
|
|
description = fields.Char('Description', required=True)
|
|
restricted_entry = fields.Boolean('Restricted entry')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(LimsMatrix, cls).__setup__()
|
|
t = cls.__table__()
|
|
cls._sql_constraints += [
|
|
('code_uniq', Unique(t, t.code),
|
|
'Matrix code must be unique'),
|
|
]
|
|
|
|
@staticmethod
|
|
def default_restricted_entry():
|
|
return False
|
|
|
|
def get_rec_name(self, name):
|
|
if self.code:
|
|
return self.code + ' - ' + self.description
|
|
else:
|
|
return self.description
|
|
|
|
@classmethod
|
|
def search_rec_name(cls, name, clause):
|
|
field = None
|
|
for field in ('code', 'description'):
|
|
records = cls.search([(field,) + tuple(clause[1:])], limit=1)
|
|
if records:
|
|
break
|
|
if records:
|
|
return [(field,) + tuple(clause[1:])]
|
|
return [(cls._rec_name,) + tuple(clause[1:])]
|
|
|
|
|
|
class LimsPackagingType(ModelSQL, ModelView):
|
|
'Packaging Type'
|
|
__name__ = 'lims.packaging.type'
|
|
_rec_name = 'description'
|
|
|
|
code = fields.Char('Code', required=True)
|
|
description = fields.Char('Description', required=True, translate=True)
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(LimsPackagingType, cls).__setup__()
|
|
t = cls.__table__()
|
|
cls._sql_constraints += [
|
|
('code_uniq', Unique(t, t.code),
|
|
'Packaging type code must be unique'),
|
|
]
|
|
|
|
def get_rec_name(self, name):
|
|
if self.code:
|
|
return self.code + ' - ' + self.description
|
|
else:
|
|
return self.description
|
|
|
|
@classmethod
|
|
def search_rec_name(cls, name, clause):
|
|
field = None
|
|
for field in ('code', 'description'):
|
|
records = cls.search([(field,) + tuple(clause[1:])], limit=1)
|
|
if records:
|
|
break
|
|
if records:
|
|
return [(field,) + tuple(clause[1:])]
|
|
return [(cls._rec_name,) + tuple(clause[1:])]
|
|
|
|
|
|
class LimsFormula(ModelSQL, ModelView):
|
|
'Formula'
|
|
__name__ = 'lims.formula'
|
|
|
|
name = fields.Char('Name', required=True)
|
|
formula = fields.Char('Formula', required=True)
|
|
variables = fields.One2Many('lims.formula.variable', 'formula',
|
|
'Variables', required=True)
|
|
|
|
|
|
class LimsFormulaVariable(ModelSQL, ModelView):
|
|
'Formula Variable'
|
|
__name__ = 'lims.formula.variable'
|
|
|
|
formula = fields.Many2One('lims.formula', 'Formula', required=True,
|
|
ondelete='CASCADE', select=True)
|
|
number = fields.Char('Number', required=True)
|
|
description = fields.Char('Description', required=True)
|
|
fraction_type = fields.Many2One('lims.fraction.type', 'Fraction type')
|
|
constant = fields.Char('Constant')
|
|
|
|
|
|
class LimsAnalysis(Workflow, ModelSQL, ModelView):
|
|
'Analysis/Set/Group'
|
|
__name__ = 'lims.analysis'
|
|
_rec_name = 'description'
|
|
|
|
code = fields.Char('Code', required=True,
|
|
states={'readonly': Eval('state') != 'draft'}, depends=['state'])
|
|
description = fields.Char('Description', required=True, translate=True,
|
|
states={'readonly': Bool(Equal(Eval('state'), 'disabled'))},
|
|
depends=['state'])
|
|
type = fields.Selection([
|
|
('analysis', 'Analysis'),
|
|
('set', 'Set'),
|
|
('group', 'Group'),
|
|
], 'Type', sort=False, required=True,
|
|
states={'readonly': Eval('state') != 'draft'}, depends=['state'])
|
|
laboratories = fields.One2Many('lims.analysis-laboratory', 'analysis',
|
|
'Laboratories', context={'type': Eval('type')},
|
|
states={
|
|
'invisible': Or(
|
|
Eval('type').in_(['group']),
|
|
Bool(Equal(Eval('behavior'), 'additional'))),
|
|
'required': Not(Or(
|
|
Eval('type').in_(['set', 'group']),
|
|
Bool(Equal(Eval('behavior'), 'additional')))),
|
|
'readonly': Bool(Equal(Eval('state'), 'disabled')),
|
|
}, depends=['type', 'behavior', 'state'])
|
|
laboratory_domain = fields.Function(fields.Many2Many('lims.laboratory',
|
|
None, None, 'Laboratories'), 'on_change_with_laboratory_domain')
|
|
methods = fields.Many2Many('lims.analysis-lab.method', 'analysis',
|
|
'method', 'Methods', states={
|
|
'invisible': Or(
|
|
Eval('type').in_(['set', 'group']),
|
|
Bool(Equal(Eval('behavior'), 'additional'))),
|
|
'required': Not(Or(
|
|
Eval('type').in_(['set', 'group']),
|
|
Bool(Equal(Eval('behavior'), 'additional')))),
|
|
'readonly': Bool(Equal(Eval('state'), 'disabled')),
|
|
}, depends=['type', 'behavior', 'state'])
|
|
devices = fields.One2Many('lims.analysis.device', 'analysis', 'Devices',
|
|
context={'laboratory_domain': Eval('laboratory_domain')},
|
|
states={
|
|
'invisible': Or(
|
|
Eval('type').in_(['set', 'group']),
|
|
Bool(Equal(Eval('behavior'), 'additional'))),
|
|
'readonly': Bool(Equal(Eval('state'), 'disabled')),
|
|
}, depends=['type', 'behavior', 'laboratory_domain', 'state'])
|
|
start_date = fields.Date('Entry date', readonly=True)
|
|
end_date = fields.Date('Leaving date', readonly=True)
|
|
included_analysis = fields.One2Many('lims.analysis.included', 'analysis',
|
|
'Included analysis', context={
|
|
'analysis': Eval('id'), 'type': Eval('type'),
|
|
'laboratory_domain': Eval('laboratory_domain')},
|
|
states={
|
|
'invisible': Bool(Equal(Eval('type'), 'analysis')),
|
|
'readonly': Bool(Equal(Eval('state'), 'disabled')),
|
|
}, depends=['type', 'laboratory_domain', 'state'])
|
|
all_included_analysis = fields.Function(fields.One2Many('lims.analysis',
|
|
None, 'All included analysis'),
|
|
'on_change_with_all_included_analysis')
|
|
behavior = fields.Selection([
|
|
('normal', 'Normal'),
|
|
('internal_relation', 'Internal Relation'),
|
|
('additional', 'Additional'),
|
|
], 'Behavior', required=True, sort=False,
|
|
states={
|
|
'readonly': Or(
|
|
Eval('type').in_(['set', 'group']),
|
|
Eval('state') != 'draft',
|
|
),
|
|
}, depends=['type', 'state'])
|
|
result_formula = fields.Char('Result formula',
|
|
states={
|
|
'invisible': Not(
|
|
Bool(Equal(Eval('behavior'), 'internal_relation'))),
|
|
'required': Bool(Equal(Eval('behavior'), 'internal_relation')),
|
|
'readonly': Bool(Equal(Eval('state'), 'disabled')),
|
|
}, depends=['behavior', 'state'])
|
|
converted_result_formula = fields.Char('Converted result formula',
|
|
states={
|
|
'invisible': Not(
|
|
Bool(Equal(Eval('behavior'), 'internal_relation'))),
|
|
'required': Bool(Equal(Eval('behavior'), 'internal_relation')),
|
|
'readonly': Bool(Equal(Eval('state'), 'disabled')),
|
|
}, depends=['behavior', 'state'])
|
|
gender_species = fields.Text('Gender Species', translate=True,
|
|
states={
|
|
'invisible': Not(And(
|
|
Bool(Equal(Eval('type'), 'analysis')),
|
|
Bool(Equal(Eval('behavior'), 'normal')))),
|
|
'readonly': Bool(Equal(Eval('state'), 'disabled')),
|
|
}, depends=['type', 'behavior', 'state'])
|
|
microbiology = fields.Function(fields.Boolean('Microbiology'),
|
|
'on_change_with_microbiology')
|
|
formula = fields.Many2One('lims.formula', 'Formula',
|
|
states={
|
|
'invisible': Not(And(
|
|
Bool(Equal(Eval('type'), 'analysis')),
|
|
Bool(Equal(Eval('behavior'), 'normal')))),
|
|
'readonly': Bool(Equal(Eval('state'), 'disabled')),
|
|
}, depends=['type', 'behavior', 'state'])
|
|
product = fields.Many2One('product.product', 'Product')
|
|
automatic_acquisition = fields.Boolean('Automatic acquisition',
|
|
states={'readonly': Bool(Equal(Eval('state'), 'disabled'))},
|
|
depends=['state'])
|
|
order = fields.Integer('Order', states={
|
|
'invisible': Not(And(
|
|
Bool(Equal(Eval('type'), 'analysis')),
|
|
Eval('behavior').in_(['normal', 'internal_relation']))),
|
|
'readonly': Bool(Equal(Eval('state'), 'disabled')),
|
|
}, depends=['type', 'behavior', 'state'])
|
|
disable_as_individual = fields.Boolean(
|
|
'Not allowed as individual service', states={
|
|
'invisible': Not(And(
|
|
Bool(Equal(Eval('type'), 'analysis')),
|
|
Eval('behavior').in_(['normal', 'internal_relation']))),
|
|
'readonly': Bool(Equal(Eval('state'), 'disabled')),
|
|
},
|
|
depends=['type', 'behavior', 'state'])
|
|
state = fields.Selection([
|
|
('draft', 'Draft'),
|
|
('active', 'Active'),
|
|
('disabled', 'Disabled'),
|
|
], 'State', required=True, readonly=True)
|
|
planning_legend = fields.Char('Planning legend',
|
|
states={
|
|
'invisible': Not(And(
|
|
Bool(Equal(Eval('type'), 'analysis')),
|
|
Bool(Equal(Eval('behavior'), 'normal')))),
|
|
'readonly': Bool(Equal(Eval('state'), 'disabled')),
|
|
}, depends=['type', 'behavior', 'state'])
|
|
comments = fields.Text('Warnings/Comments')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(LimsAnalysis, cls).__setup__()
|
|
t = cls.__table__()
|
|
cls._sql_constraints += [
|
|
('code_uniq', Unique(t, t.code),
|
|
'Analysis code must be unique'),
|
|
]
|
|
cls._error_messages.update({
|
|
'description_uniq': 'Analysis description must be unique',
|
|
'not_laboratory': 'Must define a Laboratory',
|
|
'set_laboratories': ('A Set can be assigned to a single'
|
|
' laboratory'),
|
|
'analysis_laboratory': ('The "%(analysis)s" analysis is not'
|
|
' defined in laboratory "%(laboratory)s"'),
|
|
'not_laboratory_change': ('You can not change the laboratory'
|
|
' because the analysis is included in a set/group with this'
|
|
' laboratory'),
|
|
'end_date': 'The leaving date cannot be lower than entry date',
|
|
'end_date_wrong': ('End date should not be greater than the '
|
|
'current date'),
|
|
})
|
|
cls._transitions |= set((
|
|
('draft', 'active'),
|
|
('active', 'disabled'),
|
|
))
|
|
cls._buttons.update({
|
|
'relate_analysis': {
|
|
'invisible': (Eval('type') != 'set'),
|
|
'readonly': Bool(Equal(Eval('state'), 'disabled')),
|
|
},
|
|
'activate': {
|
|
'invisible': (Eval('state') != 'draft'),
|
|
},
|
|
'disable': {
|
|
'invisible': (Eval('state') != 'active'),
|
|
},
|
|
})
|
|
|
|
@staticmethod
|
|
def default_behavior():
|
|
return 'normal'
|
|
|
|
@staticmethod
|
|
def default_automatic_acquisition():
|
|
return False
|
|
|
|
@staticmethod
|
|
def default_disable_as_individual():
|
|
return False
|
|
|
|
@staticmethod
|
|
def default_state():
|
|
return 'draft'
|
|
|
|
@fields.depends('type', 'behavior')
|
|
def on_change_with_behavior(self, name=None):
|
|
if self.type in ('set', 'group'):
|
|
return 'normal'
|
|
return self.behavior
|
|
|
|
@fields.depends('laboratories')
|
|
def on_change_with_laboratory_domain(self, name=None):
|
|
if self.laboratories:
|
|
return [l.laboratory.id for l in self.laboratories if l.laboratory]
|
|
return []
|
|
|
|
@fields.depends('included_analysis')
|
|
def on_change_with_all_included_analysis(self, name=None):
|
|
LimsAnalysis = Pool().get('lims.analysis')
|
|
return LimsAnalysis.get_included_analysis(self.id)
|
|
|
|
@classmethod
|
|
def view_attributes(cls):
|
|
return [
|
|
('//page[@id="microbiology"]', 'states', {
|
|
'invisible': Not(Bool(Eval('microbiology'))),
|
|
}),
|
|
('//group[@id="button_holder"]', 'states', {
|
|
'invisible': Eval('type') != 'set',
|
|
}),
|
|
('//page[@id="included_analysis"]', 'states', {
|
|
'invisible': Bool(Equal(Eval('type'), 'analysis')),
|
|
}),
|
|
('//page[@id="devices"]|//page[@id="methods"]',
|
|
'states', {
|
|
'invisible': Or(
|
|
Eval('type').in_(['set', 'group']),
|
|
Bool(Equal(Eval('behavior'), 'additional'))),
|
|
}),
|
|
('//page[@id="laboratories"]',
|
|
'states', {
|
|
'invisible': Or(
|
|
Eval('type').in_(['group']),
|
|
Bool(Equal(Eval('behavior'), 'additional'))),
|
|
}),
|
|
]
|
|
|
|
@classmethod
|
|
def get_included_analysis(cls, analysis_id):
|
|
cursor = Transaction().connection.cursor()
|
|
LimsAnalysisIncluded = Pool().get('lims.analysis.included')
|
|
|
|
childs = []
|
|
cursor.execute('SELECT included_analysis '
|
|
'FROM "' + LimsAnalysisIncluded._table + '" '
|
|
'WHERE analysis = %s', (analysis_id,))
|
|
included_analysis_ids = [x[0] for x in cursor.fetchall()]
|
|
if included_analysis_ids:
|
|
for analysis_id in included_analysis_ids:
|
|
if analysis_id not in childs:
|
|
childs.append(analysis_id)
|
|
childs.extend(cls.get_included_analysis(analysis_id))
|
|
return childs
|
|
|
|
@classmethod
|
|
def get_included_analysis_analysis(cls, analysis_id):
|
|
cursor = Transaction().connection.cursor()
|
|
pool = Pool()
|
|
LimsAnalysisIncluded = pool.get('lims.analysis.included')
|
|
LimsAnalysis = pool.get('lims.analysis')
|
|
|
|
childs = []
|
|
cursor.execute('SELECT ia.included_analysis, a.type '
|
|
'FROM "' + LimsAnalysisIncluded._table + '" ia '
|
|
'INNER JOIN "' + LimsAnalysis._table + '" a '
|
|
'ON a.id = ia.included_analysis '
|
|
'WHERE analysis = %s', (analysis_id,))
|
|
included_analysis = cursor.fetchall()
|
|
if included_analysis:
|
|
for analysis in included_analysis:
|
|
if analysis[1] == 'analysis' and analysis[0] not in childs:
|
|
childs.append(analysis[0])
|
|
childs.extend(cls.get_included_analysis_analysis(analysis[0]))
|
|
return childs
|
|
|
|
@classmethod
|
|
def get_parents_analysis(cls, analysis_id):
|
|
cursor = Transaction().connection.cursor()
|
|
pool = Pool()
|
|
LimsAnalysisIncluded = pool.get('lims.analysis.included')
|
|
LimsAnalysis = pool.get('lims.analysis')
|
|
|
|
parents = []
|
|
cursor.execute('SELECT ia.analysis '
|
|
'FROM "' + LimsAnalysisIncluded._table + '" ia '
|
|
'INNER JOIN "' + LimsAnalysis._table + '" a '
|
|
'ON a.id = ia.analysis '
|
|
'WHERE ia.included_analysis = %s '
|
|
'AND a.state = \'active\'', (analysis_id,))
|
|
parents_analysis_ids = [x[0] for x in cursor.fetchall()]
|
|
if parents_analysis_ids:
|
|
for analysis_id in parents_analysis_ids:
|
|
if analysis_id not in parents:
|
|
parents.append(analysis_id)
|
|
parents.extend(cls.get_parents_analysis(analysis_id))
|
|
return parents
|
|
|
|
def get_rec_name(self, name):
|
|
if self.code:
|
|
return self.code + ' - ' + self.description
|
|
else:
|
|
return self.description
|
|
|
|
@classmethod
|
|
def search_rec_name(cls, name, clause):
|
|
field = None
|
|
for field in ('code', 'description'):
|
|
records = cls.search([(field,) + tuple(clause[1:])], limit=1)
|
|
if records:
|
|
break
|
|
if records:
|
|
return [(field,) + tuple(clause[1:])]
|
|
return [(cls._rec_name,) + tuple(clause[1:])]
|
|
|
|
@classmethod
|
|
def validate(cls, analysis):
|
|
super(LimsAnalysis, cls).validate(analysis)
|
|
for a in analysis:
|
|
a.check_description()
|
|
a.check_set()
|
|
a.check_end_date()
|
|
|
|
def check_description(self):
|
|
if not self.end_date:
|
|
analysis = self.search([
|
|
('description', '=', self.description),
|
|
('type', '=', self.type),
|
|
('end_date', '=', None),
|
|
('id', '!=', self.id),
|
|
])
|
|
if analysis:
|
|
self.raise_user_error('description_uniq')
|
|
|
|
def check_set(self):
|
|
if self.type == 'set':
|
|
if self.laboratories and len(self.laboratories) > 1:
|
|
self.raise_user_error('set_laboratories')
|
|
if self.included_analysis and not self.laboratories:
|
|
self.raise_user_error('not_laboratory')
|
|
if self.included_analysis:
|
|
set_laboratory = self.laboratories[0].laboratory
|
|
for ia in self.included_analysis:
|
|
included_analysis_laboratories = [lab.laboratory
|
|
for lab in ia.included_analysis.laboratories]
|
|
if (set_laboratory not in included_analysis_laboratories):
|
|
self.raise_user_error('analysis_laboratory', {
|
|
'analysis': ia.included_analysis.rec_name,
|
|
'laboratory': set_laboratory.rec_name,
|
|
})
|
|
|
|
def check_end_date(self):
|
|
if self.end_date:
|
|
if not self.start_date or self.end_date < self.start_date:
|
|
self.raise_user_error('end_date')
|
|
if not self.start_date or self.end_date > datetime.now().date():
|
|
self.raise_user_error('end_date_wrong')
|
|
|
|
@classmethod
|
|
def write(cls, *args):
|
|
actions = iter(args)
|
|
for analysis, vals in zip(actions, actions):
|
|
if vals.get('laboratories'):
|
|
cls.check_laboratory_change(analysis, vals['laboratories'])
|
|
super(LimsAnalysis, cls).write(*args)
|
|
|
|
@classmethod
|
|
def check_laboratory_change(cls, analysis, laboratories):
|
|
LimsAnalysisIncluded = Pool().get('lims.analysis.included')
|
|
|
|
for a in analysis:
|
|
if a.type == 'analysis':
|
|
for operation in laboratories:
|
|
if operation[0] == 'unlink':
|
|
for laboratory in operation[1]:
|
|
parent = LimsAnalysisIncluded.search([
|
|
('included_analysis', '=', a.id),
|
|
('laboratory', '=', laboratory),
|
|
])
|
|
if parent:
|
|
cls.raise_user_error('not_laboratory_change')
|
|
|
|
@classmethod
|
|
@ModelView.button_action('lims.wiz_lims_relate_analysis')
|
|
def relate_analysis(cls, analysis):
|
|
pass
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
@Workflow.transition('active')
|
|
def activate(cls, analysis):
|
|
Date = Pool().get('ir.date')
|
|
cls.write(analysis, {'start_date': Date.today()})
|
|
cls.create_typification_calculated(analysis)
|
|
cls.create_product(analysis)
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
@Workflow.transition('disabled')
|
|
def disable(cls, analysis):
|
|
Date = Pool().get('ir.date')
|
|
cls.write(analysis, {'end_date': Date.today()})
|
|
cls.disable_typifications(analysis)
|
|
cls.delete_included_analysis(analysis)
|
|
|
|
@classmethod
|
|
def create_typification_calculated(cls, analysis):
|
|
cursor = Transaction().connection.cursor()
|
|
pool = Pool()
|
|
LimsAnalysis = pool.get('lims.analysis')
|
|
LimsTypification = pool.get('lims.typification')
|
|
LimsCalculatedTypification = pool.get('lims.typification.calculated')
|
|
|
|
for included in analysis:
|
|
if included.type == 'analysis':
|
|
continue
|
|
sets_groups_ids = [included.id]
|
|
sets_groups_ids.extend(LimsAnalysis.get_parents_analysis(
|
|
included.id))
|
|
for set_group_id in sets_groups_ids:
|
|
|
|
ia = LimsAnalysis.get_included_analysis_analysis(
|
|
set_group_id)
|
|
if not ia:
|
|
continue
|
|
included_ids = ', '.join(str(a) for a in ia)
|
|
|
|
cursor.execute('SELECT DISTINCT(product_type, matrix) '
|
|
'FROM "' + LimsTypification._table + '" '
|
|
'WHERE valid '
|
|
'AND analysis IN (' + included_ids + ')')
|
|
typifications = cursor.fetchall()
|
|
if not typifications:
|
|
continue
|
|
|
|
for typification in typifications:
|
|
|
|
product_type = int(typification[0].split(',')[0][1:])
|
|
matrix = int(typification[0].split(',')[1][:-1])
|
|
cursor.execute('SELECT DISTINCT(analysis) '
|
|
'FROM "' + LimsTypification._table + '" '
|
|
'WHERE product_type = %s '
|
|
'AND matrix = %s '
|
|
'AND valid',
|
|
(product_type, matrix))
|
|
typified_analysis = [a[0] for a in cursor.fetchall()]
|
|
typified_analysis_ids = ', '.join(str(a) for a in
|
|
typified_analysis)
|
|
|
|
cursor.execute('SELECT id '
|
|
'FROM "' + LimsAnalysis._table + '" '
|
|
'WHERE id IN (' + included_ids + ') '
|
|
'AND id NOT IN (' + typified_analysis_ids
|
|
+ ')')
|
|
if cursor.fetchone():
|
|
typified = False
|
|
else:
|
|
typified = True
|
|
|
|
if typified:
|
|
t_set_group = LimsCalculatedTypification.search([
|
|
('product_type', '=', product_type),
|
|
('matrix', '=', matrix),
|
|
('analysis', '=', set_group_id),
|
|
])
|
|
if not t_set_group:
|
|
typification_create = [{
|
|
'product_type': product_type,
|
|
'matrix': matrix,
|
|
'analysis': set_group_id,
|
|
}]
|
|
LimsCalculatedTypification.create(
|
|
typification_create)
|
|
else:
|
|
t_set_group = LimsCalculatedTypification.search([
|
|
('product_type', '=', product_type),
|
|
('matrix', '=', matrix),
|
|
('analysis', '=', set_group_id),
|
|
])
|
|
if t_set_group:
|
|
LimsCalculatedTypification.delete(t_set_group)
|
|
|
|
return analysis
|
|
|
|
@classmethod
|
|
def create_product(cls, analysis):
|
|
CreateProduct = Pool().get('lims.create_analysis_product',
|
|
type='wizard')
|
|
s_analysis, = analysis
|
|
session_id, _, _ = CreateProduct.create()
|
|
create_product = CreateProduct(session_id)
|
|
with Transaction().set_context(active_id=s_analysis.id):
|
|
create_product.transition_start()
|
|
|
|
@classmethod
|
|
def disable_typifications(cls, analysis):
|
|
pool = Pool()
|
|
LimsTypification = pool.get('lims.typification')
|
|
LimsCalculatedTypification = pool.get('lims.typification.calculated')
|
|
|
|
analysis_ids = []
|
|
sets_groups_ids = []
|
|
for a in analysis:
|
|
if a.type == 'analysis':
|
|
analysis_ids.append(a.id)
|
|
else:
|
|
sets_groups_ids.append(a.id)
|
|
if analysis_ids:
|
|
typifications = LimsTypification.search([
|
|
('analysis', 'in', analysis_ids),
|
|
])
|
|
if typifications:
|
|
LimsTypification.write(typifications, {'valid': False})
|
|
if sets_groups_ids:
|
|
typifications = LimsCalculatedTypification.search([
|
|
('analysis', 'in', sets_groups_ids),
|
|
])
|
|
if typifications:
|
|
LimsCalculatedTypification.delete(typifications)
|
|
|
|
@classmethod
|
|
def delete_included_analysis(cls, analysis):
|
|
LimsAnalysisIncluded = Pool().get('lims.analysis.included')
|
|
analysis_ids = [a.id for a in analysis]
|
|
if analysis_ids:
|
|
included_delete = LimsAnalysisIncluded.search([
|
|
('included_analysis', 'in', analysis_ids),
|
|
])
|
|
if included_delete:
|
|
LimsAnalysisIncluded.delete(included_delete)
|
|
|
|
@fields.depends('laboratories')
|
|
def on_change_with_microbiology(self, name=None):
|
|
Config = Pool().get('lims.configuration')
|
|
|
|
config_ = Config(1)
|
|
if not config_.microbiology_laboratories:
|
|
return False
|
|
|
|
if self.laboratories:
|
|
for lab in self.laboratories:
|
|
if lab.laboratory in config_.microbiology_laboratories:
|
|
return True
|
|
return False
|
|
|
|
@staticmethod
|
|
def is_typified(analysis, product_type, matrix):
|
|
pool = Pool()
|
|
LimsTypification = pool.get('lims.typification')
|
|
LimsCalculatedTypification = pool.get('lims.typification.calculated')
|
|
|
|
if analysis.type == 'analysis':
|
|
typified_service = LimsTypification.search([
|
|
('analysis', '=', analysis.id),
|
|
('product_type', '=', product_type.id),
|
|
('matrix', '=', matrix.id),
|
|
('valid', '=', True),
|
|
])
|
|
if typified_service:
|
|
return True
|
|
else:
|
|
typified_service = LimsCalculatedTypification.search([
|
|
('analysis', '=', analysis.id),
|
|
('product_type', '=', product_type.id),
|
|
('matrix', '=', matrix.id),
|
|
])
|
|
if typified_service:
|
|
return True
|
|
return False
|
|
|
|
@classmethod
|
|
def copy(cls, analysis, default=None):
|
|
if default is None:
|
|
default = {}
|
|
current_default = default.copy()
|
|
current_default['state'] = 'draft'
|
|
current_default['start_date'] = None
|
|
current_default['end_date'] = None
|
|
return super(LimsAnalysis, cls).copy(analysis, default=current_default)
|
|
|
|
|
|
class LimsAnalysisIncluded(ModelSQL, ModelView):
|
|
'Included Analysis'
|
|
__name__ = 'lims.analysis.included'
|
|
|
|
analysis = fields.Many2One('lims.analysis', 'Analysis', required=True,
|
|
ondelete='CASCADE', select=True)
|
|
included_analysis = fields.Many2One('lims.analysis', 'Included analysis',
|
|
required=True, depends=['analysis_domain'], domain=[
|
|
('id', 'in', Eval('analysis_domain')),
|
|
])
|
|
analysis_domain = fields.Function(fields.Many2Many('lims.analysis',
|
|
None, None, 'Analysis domain'),
|
|
'on_change_with_analysis_domain')
|
|
analysis_type = fields.Function(fields.Selection([
|
|
('analysis', 'Analysis'),
|
|
('set', 'Set'),
|
|
('group', 'Group'),
|
|
], 'Type', sort=False),
|
|
'on_change_with_analysis_type')
|
|
laboratory = fields.Many2One('lims.laboratory', 'Laboratory',
|
|
domain=[('id', 'in', Eval('laboratory_domain'))],
|
|
states={
|
|
'required': Or(
|
|
Bool(Equal(Eval('_parent_analysis', {}).get('type'), 'set')),
|
|
And(Bool(Equal(Eval('_parent_analysis', {}).get('type'),
|
|
'group')),
|
|
Bool(Equal(Eval('analysis_type'), 'analysis'))),
|
|
Bool(Eval('laboratory_domain'))),
|
|
'readonly': Bool(
|
|
Equal(Eval('_parent_analysis', {}).get('type'), 'set')),
|
|
'invisible': Eval('analysis_type').in_(['set', 'group']),
|
|
},
|
|
depends=['laboratory_domain', 'analysis_type'])
|
|
laboratory_domain = fields.Function(fields.Many2Many('lims.laboratory',
|
|
None, None, 'Laboratory domain'), 'on_change_with_laboratory_domain')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(LimsAnalysisIncluded, cls).__setup__()
|
|
cls._error_messages.update({
|
|
'duplicated_analysis': 'The analysis "%s" is already included',
|
|
'not_set_laboratory': 'No Laboratory loaded for the Set',
|
|
})
|
|
|
|
@classmethod
|
|
def validate(cls, included_analysis):
|
|
super(LimsAnalysisIncluded, cls).validate(included_analysis)
|
|
for analysis in included_analysis:
|
|
analysis.check_duplicated_analysis()
|
|
|
|
def check_duplicated_analysis(self):
|
|
LimsAnalysis = Pool().get('lims.analysis')
|
|
|
|
analysis_id = self.analysis.id
|
|
included = self.search([
|
|
('analysis', '=', analysis_id),
|
|
('id', '!=', self.id)
|
|
])
|
|
if included:
|
|
analysis_ids = []
|
|
for ai in included:
|
|
if ai.included_analysis:
|
|
analysis_ids.append(ai.included_analysis.id)
|
|
analysis_ids.extend(LimsAnalysis.get_included_analysis(
|
|
ai.included_analysis.id))
|
|
if self.included_analysis.id in analysis_ids:
|
|
self.raise_user_error('duplicated_analysis',
|
|
(self.included_analysis.rec_name,))
|
|
|
|
@fields.depends('included_analysis', 'analysis', 'laboratory',
|
|
'_parent_analysis.type', '_parent_analysis.laboratories')
|
|
def on_change_included_analysis(self):
|
|
laboratory = None
|
|
if self.included_analysis:
|
|
laboratories = self.on_change_with_laboratory_domain()
|
|
if len(laboratories) == 1:
|
|
laboratory = laboratories[0]
|
|
self.laboratory = laboratory
|
|
|
|
@fields.depends('included_analysis')
|
|
def on_change_with_analysis_type(self, name=None):
|
|
res = ''
|
|
if self.included_analysis:
|
|
res = self.included_analysis.type
|
|
return res
|
|
|
|
@staticmethod
|
|
def default_analysis_domain():
|
|
LimsAnalysisIncluded = Pool().get('lims.analysis.included')
|
|
context = Transaction().context
|
|
analysis_id = context.get('analysis', None)
|
|
analysis_type = context.get('type', None)
|
|
laboratories = context.get('laboratory_domain', [])
|
|
return LimsAnalysisIncluded.get_analysis_domain(analysis_id,
|
|
analysis_type, laboratories)
|
|
|
|
@fields.depends('analysis', '_parent_analysis.type',
|
|
'_parent_analysis.laboratories')
|
|
def on_change_with_analysis_domain(self, name=None):
|
|
analysis_id = self.analysis.id if self.analysis else None
|
|
analysis_type = self.analysis.type if self.analysis else None
|
|
laboratories = []
|
|
if self.analysis and self.analysis.laboratories:
|
|
laboratories = [l.laboratory.id
|
|
for l in self.analysis.laboratories]
|
|
return self.get_analysis_domain(analysis_id,
|
|
analysis_type, laboratories)
|
|
|
|
@staticmethod
|
|
def get_analysis_domain(analysis_id=None, analysis_type=None,
|
|
laboratories=[]):
|
|
cursor = Transaction().connection.cursor()
|
|
pool = Pool()
|
|
LimsAnalysisIncluded = pool.get('lims.analysis.included')
|
|
LimsAnalysis = pool.get('lims.analysis')
|
|
LimsAnalysisLaboratory = pool.get('lims.analysis-laboratory')
|
|
|
|
if not analysis_type:
|
|
return []
|
|
|
|
if analysis_type == 'set':
|
|
if len(laboratories) != 1:
|
|
LimsAnalysisIncluded.raise_user_error('not_set_laboratory')
|
|
#return []
|
|
set_laboratory_id = laboratories[0]
|
|
not_parent_clause = ''
|
|
if analysis_id:
|
|
not_parent_clause = 'AND al.analysis != ' + str(analysis_id)
|
|
|
|
cursor.execute('SELECT DISTINCT(al.analysis) '
|
|
'FROM "' + LimsAnalysisLaboratory._table + '" al '
|
|
'INNER JOIN "' + LimsAnalysis._table + '" a '
|
|
'ON a.id = al.analysis '
|
|
'WHERE al.laboratory = %s '
|
|
'AND a.state = \'active\' '
|
|
'AND a.type = \'analysis\' '
|
|
'AND a.end_date IS NULL '
|
|
+ not_parent_clause,
|
|
(set_laboratory_id,))
|
|
res = cursor.fetchall()
|
|
if not res:
|
|
return []
|
|
return [x[0] for x in res]
|
|
else:
|
|
not_parent_clause = ''
|
|
if analysis_id:
|
|
not_parent_clause = 'AND id != ' + str(analysis_id)
|
|
cursor.execute('SELECT id '
|
|
'FROM "' + LimsAnalysis._table + '" '
|
|
'WHERE state = \'active\' '
|
|
'AND type != \'group\' '
|
|
'AND end_date IS NULL '
|
|
+ not_parent_clause)
|
|
res = cursor.fetchall()
|
|
if not res:
|
|
return []
|
|
return [x[0] for x in res]
|
|
|
|
@staticmethod
|
|
def default_laboratory_domain():
|
|
return Transaction().context.get('laboratory_domain', [])
|
|
|
|
@fields.depends('included_analysis', 'analysis', '_parent_analysis.type',
|
|
'_parent_analysis.laboratories', 'laboratory')
|
|
def on_change_with_laboratory_domain(self, name=None):
|
|
laboratories = []
|
|
analysis_laboratories = []
|
|
if self.included_analysis and self.included_analysis.laboratories:
|
|
analysis_laboratories = [l.laboratory.id
|
|
for l in self.included_analysis.laboratories]
|
|
if self.analysis and self.analysis.type == 'set':
|
|
if self.analysis.laboratories:
|
|
set_laboratory = self.analysis.laboratories[0].laboratory.id
|
|
if set_laboratory in analysis_laboratories:
|
|
laboratories = [set_laboratory]
|
|
else:
|
|
laboratories = analysis_laboratories
|
|
if not laboratories and self.laboratory:
|
|
laboratories = [self.laboratory.id]
|
|
return laboratories
|
|
|
|
@classmethod
|
|
def create(cls, vlist):
|
|
included_analysis = super(LimsAnalysisIncluded, cls).create(vlist)
|
|
cls.create_typification_calculated(included_analysis)
|
|
return included_analysis
|
|
|
|
@classmethod
|
|
def create_typification_calculated(cls, included_analysis):
|
|
cursor = Transaction().connection.cursor()
|
|
pool = Pool()
|
|
LimsAnalysis = pool.get('lims.analysis')
|
|
LimsTypification = pool.get('lims.typification')
|
|
LimsCalculatedTypification = pool.get('lims.typification.calculated')
|
|
|
|
for included in included_analysis:
|
|
if included.analysis.state != 'active':
|
|
continue
|
|
sets_groups_ids = [included.analysis.id]
|
|
sets_groups_ids.extend(LimsAnalysis.get_parents_analysis(
|
|
included.analysis.id))
|
|
for set_group_id in sets_groups_ids:
|
|
|
|
ia = LimsAnalysis.get_included_analysis_analysis(
|
|
set_group_id)
|
|
if not ia:
|
|
continue
|
|
included_ids = ', '.join(str(a) for a in ia)
|
|
|
|
cursor.execute('SELECT DISTINCT(product_type, matrix) '
|
|
'FROM "' + LimsTypification._table + '" '
|
|
'WHERE valid '
|
|
'AND analysis IN (' + included_ids + ')')
|
|
typifications = cursor.fetchall()
|
|
if not typifications:
|
|
continue
|
|
|
|
for typification in typifications:
|
|
|
|
product_type = int(typification[0].split(',')[0][1:])
|
|
matrix = int(typification[0].split(',')[1][:-1])
|
|
cursor.execute('SELECT DISTINCT(analysis) '
|
|
'FROM "' + LimsTypification._table + '" '
|
|
'WHERE product_type = %s '
|
|
'AND matrix = %s '
|
|
'AND valid',
|
|
(product_type, matrix))
|
|
typified_analysis = [a[0] for a in cursor.fetchall()]
|
|
typified_analysis_ids = ', '.join(str(a) for a in
|
|
typified_analysis)
|
|
|
|
cursor.execute('SELECT id '
|
|
'FROM "' + LimsAnalysis._table + '" '
|
|
'WHERE id IN (' + included_ids + ') '
|
|
'AND id NOT IN (' + typified_analysis_ids
|
|
+ ')')
|
|
if cursor.fetchone():
|
|
typified = False
|
|
else:
|
|
typified = True
|
|
|
|
if typified:
|
|
t_set_group = LimsCalculatedTypification.search([
|
|
('product_type', '=', product_type),
|
|
('matrix', '=', matrix),
|
|
('analysis', '=', set_group_id),
|
|
])
|
|
if not t_set_group:
|
|
typification_create = [{
|
|
'product_type': product_type,
|
|
'matrix': matrix,
|
|
'analysis': set_group_id,
|
|
}]
|
|
LimsCalculatedTypification.create(
|
|
typification_create)
|
|
else:
|
|
t_set_group = LimsCalculatedTypification.search([
|
|
('product_type', '=', product_type),
|
|
('matrix', '=', matrix),
|
|
('analysis', '=', set_group_id),
|
|
])
|
|
if t_set_group:
|
|
LimsCalculatedTypification.delete(t_set_group)
|
|
|
|
return included_analysis
|
|
|
|
@classmethod
|
|
def delete(cls, included_analysis):
|
|
cls.delete_typification_calculated(included_analysis)
|
|
super(LimsAnalysisIncluded, cls).delete(included_analysis)
|
|
|
|
@classmethod
|
|
def delete_typification_calculated(cls, included_analysis):
|
|
cursor = Transaction().connection.cursor()
|
|
pool = Pool()
|
|
LimsAnalysis = pool.get('lims.analysis')
|
|
LimsTypification = pool.get('lims.typification')
|
|
LimsCalculatedTypification = pool.get('lims.typification.calculated')
|
|
|
|
for included in included_analysis:
|
|
if included.analysis.state != 'active':
|
|
continue
|
|
if included.included_analysis.type == 'analysis':
|
|
deleted_analysis = [included.included_analysis.id]
|
|
else:
|
|
deleted_analysis = (
|
|
LimsAnalysis.get_included_analysis_analysis(
|
|
included.included_analysis.id))
|
|
|
|
sets_groups_ids = [included.analysis.id]
|
|
sets_groups_ids.extend(LimsAnalysis.get_parents_analysis(
|
|
included.analysis.id))
|
|
for set_group_id in sets_groups_ids:
|
|
typified = True
|
|
|
|
ia = LimsAnalysis.get_included_analysis_analysis(
|
|
set_group_id)
|
|
if deleted_analysis:
|
|
for da in deleted_analysis:
|
|
if da in ia:
|
|
ia.remove(da)
|
|
if not ia:
|
|
continue
|
|
included_ids = ', '.join(str(a) for a in ia)
|
|
|
|
cursor.execute('SELECT DISTINCT(product_type, matrix) '
|
|
'FROM "' + LimsTypification._table + '" '
|
|
'WHERE valid '
|
|
'AND analysis IN (' + included_ids + ')')
|
|
typifications = cursor.fetchall()
|
|
if not typifications:
|
|
continue
|
|
|
|
for typification in typifications:
|
|
|
|
product_type = int(typification[0].split(',')[0][1:])
|
|
matrix = int(typification[0].split(',')[1][:-1])
|
|
cursor.execute('SELECT DISTINCT(analysis) '
|
|
'FROM "' + LimsTypification._table + '" '
|
|
'WHERE product_type = %s '
|
|
'AND matrix = %s '
|
|
'AND valid',
|
|
(product_type, matrix))
|
|
typified_analysis = [a[0] for a in cursor.fetchall()]
|
|
typified_analysis_ids = ', '.join(str(a) for a in
|
|
typified_analysis)
|
|
|
|
cursor.execute('SELECT id '
|
|
'FROM "' + LimsAnalysis._table + '" '
|
|
'WHERE id IN (' + included_ids + ') '
|
|
'AND id NOT IN (' + typified_analysis_ids
|
|
+ ')')
|
|
if cursor.fetchone():
|
|
typified = False
|
|
else:
|
|
typified = True
|
|
|
|
if typified:
|
|
t_set_group = LimsCalculatedTypification.search([
|
|
('product_type', '=', product_type),
|
|
('matrix', '=', matrix),
|
|
('analysis', '=', set_group_id),
|
|
])
|
|
if not t_set_group:
|
|
typification_create = [{
|
|
'product_type': product_type,
|
|
'matrix': matrix,
|
|
'analysis': set_group_id,
|
|
}]
|
|
LimsCalculatedTypification.create(
|
|
typification_create)
|
|
else:
|
|
t_set_group = LimsCalculatedTypification.search([
|
|
('product_type', '=', product_type),
|
|
('matrix', '=', matrix),
|
|
('analysis', '=', set_group_id),
|
|
])
|
|
if t_set_group:
|
|
LimsCalculatedTypification.delete(t_set_group)
|
|
|
|
@classmethod
|
|
def search_rec_name(cls, name, clause):
|
|
return ['OR',
|
|
('included_analysis.code',) + tuple(clause[1:]),
|
|
('included_analysis.description',) + tuple(clause[1:]),
|
|
]
|
|
|
|
|
|
class LimsAnalysisLaboratory(ModelSQL, ModelView):
|
|
'Analysis - Laboratory'
|
|
__name__ = 'lims.analysis-laboratory'
|
|
|
|
analysis = fields.Many2One('lims.analysis', 'Analysis',
|
|
ondelete='CASCADE', select=True, required=True)
|
|
laboratory = fields.Many2One('lims.laboratory', 'Laboratory',
|
|
ondelete='CASCADE', select=True, required=True)
|
|
department = fields.Many2One('company.department', 'Department',
|
|
states={'readonly': ~Equal(Eval('context', {}).get('type', ''),
|
|
'analysis')})
|
|
|
|
|
|
class LimsAnalysisLabMethod(ModelSQL):
|
|
'Analysis - Laboratory Method'
|
|
__name__ = 'lims.analysis-lab.method'
|
|
|
|
analysis = fields.Many2One('lims.analysis', 'Analysis',
|
|
ondelete='CASCADE', select=True, required=True)
|
|
method = fields.Many2One('lims.lab.method', 'Method',
|
|
ondelete='CASCADE', select=True, required=True)
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(LimsAnalysisLabMethod, cls).__setup__()
|
|
cls._error_messages.update({
|
|
'typificated_method': ('You can not delete method "%s" because '
|
|
'is typificated'),
|
|
})
|
|
|
|
@classmethod
|
|
def delete(cls, methods):
|
|
cls.check_delete(methods)
|
|
super(LimsAnalysisLabMethod, cls).delete(methods)
|
|
|
|
@classmethod
|
|
def check_delete(cls, methods):
|
|
LimsTypification = Pool().get('lims.typification')
|
|
for method in methods:
|
|
typifications = LimsTypification.search_count([
|
|
('analysis', '=', method.analysis.id),
|
|
('method', '=', method.method.id),
|
|
('valid', '=', True),
|
|
])
|
|
if typifications != 0:
|
|
cls.raise_user_error('typificated_method',
|
|
(method.method.code,))
|
|
|
|
|
|
class LimsAnalysisDevice(ModelSQL, ModelView):
|
|
'Analysis Device'
|
|
__name__ = 'lims.analysis.device'
|
|
|
|
analysis = fields.Many2One('lims.analysis', 'Analysis', required=True,
|
|
ondelete='CASCADE', select=True)
|
|
laboratory = fields.Many2One('lims.laboratory', 'Laboratory',
|
|
required=True, domain=[('id', 'in', Eval('laboratory_domain'))],
|
|
depends=['laboratory_domain'])
|
|
laboratory_domain = fields.Function(fields.Many2Many('lims.laboratory',
|
|
None, None, 'Laboratory domain'),
|
|
'on_change_with_laboratory_domain')
|
|
device = fields.Many2One('lims.lab.device', 'Device', required=True,
|
|
domain=[('laboratories.laboratory', '=', Eval('laboratory'))],
|
|
depends=['laboratory'])
|
|
by_default = fields.Boolean('By default')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(LimsAnalysisDevice, cls).__setup__()
|
|
cls._error_messages.update({
|
|
'default_device': ('There is already a default device for this'
|
|
' analysis on this laboratory'),
|
|
})
|
|
|
|
@staticmethod
|
|
def default_by_default():
|
|
return True
|
|
|
|
@classmethod
|
|
def validate(cls, devices):
|
|
super(LimsAnalysisDevice, cls).validate(devices)
|
|
for d in devices:
|
|
d.check_default()
|
|
|
|
def check_default(self):
|
|
if self.by_default:
|
|
devices = self.search([
|
|
('analysis', '=', self.analysis.id),
|
|
('laboratory', '=', self.laboratory.id),
|
|
('by_default', '=', True),
|
|
('id', '!=', self.id),
|
|
])
|
|
if devices:
|
|
self.raise_user_error('default_device')
|
|
|
|
@staticmethod
|
|
def default_laboratory_domain():
|
|
return Transaction().context.get('laboratory_domain', [])
|
|
|
|
@fields.depends('analysis', '_parent_analysis.laboratories', 'laboratory')
|
|
def on_change_with_laboratory_domain(self, name=None):
|
|
laboratories = []
|
|
if self.analysis and self.analysis.laboratories:
|
|
laboratories = [l.laboratory.id for l in
|
|
self.analysis.laboratories]
|
|
if not laboratories and self.laboratory:
|
|
laboratories = [self.laboratory.id]
|
|
return laboratories
|
|
|
|
@classmethod
|
|
def search_rec_name(cls, name, clause):
|
|
return ['OR',
|
|
('laboratory.code',) + tuple(clause[1:]),
|
|
('laboratory.description',) + tuple(clause[1:]),
|
|
]
|
|
|
|
|
|
class LimsFractionType(ModelSQL, ModelView):
|
|
'Fraction Type'
|
|
__name__ = 'lims.fraction.type'
|
|
_rec_name = 'description'
|
|
|
|
code = fields.Char('Code', required=True)
|
|
description = fields.Char('Description', required=True)
|
|
max_storage_time = fields.Integer('Maximum storage time (in months)')
|
|
requalify = fields.Boolean('Requalify')
|
|
control_charts = fields.Boolean('Available for Control Charts')
|
|
report = fields.Boolean('Available for Results Report')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(LimsFractionType, cls).__setup__()
|
|
t = cls.__table__()
|
|
cls._order.insert(0, ('code', 'ASC'))
|
|
cls._sql_constraints += [
|
|
('code_uniq', Unique(t, t.code),
|
|
'Fraction type code must be unique'),
|
|
]
|
|
|
|
@staticmethod
|
|
def default_requalify():
|
|
return False
|
|
|
|
@staticmethod
|
|
def default_control_charts():
|
|
return False
|
|
|
|
@staticmethod
|
|
def default_report():
|
|
return True
|
|
|
|
def get_rec_name(self, name):
|
|
if self.code:
|
|
return self.code + ' - ' + self.description
|
|
else:
|
|
return self.description
|
|
|
|
@classmethod
|
|
def search_rec_name(cls, name, clause):
|
|
field = None
|
|
for field in ('code', 'description'):
|
|
records = cls.search([(field,) + tuple(clause[1:])], limit=1)
|
|
if records:
|
|
break
|
|
if records:
|
|
return [(field,) + tuple(clause[1:])]
|
|
return [(cls._rec_name,) + tuple(clause[1:])]
|
|
|
|
|
|
class LimsCertificationType(ModelSQL, ModelView):
|
|
'Certification Type'
|
|
__name__ = 'lims.certification.type'
|
|
_rec_name = 'description'
|
|
|
|
code = fields.Char('Code', required=True)
|
|
description = fields.Char('Description', required=True)
|
|
report = fields.Boolean('Report')
|
|
|
|
def get_rec_name(self, name):
|
|
if self.code:
|
|
return self.code + ' - ' + self.description
|
|
else:
|
|
return self.description
|
|
|
|
@classmethod
|
|
def search_rec_name(cls, name, clause):
|
|
field = None
|
|
for field in ('code', 'description'):
|
|
records = cls.search([(field,) + tuple(clause[1:])], limit=1)
|
|
if records:
|
|
break
|
|
if records:
|
|
return [(field,) + tuple(clause[1:])]
|
|
return [(cls._rec_name,) + tuple(clause[1:])]
|
|
|
|
|
|
class LimsTechnicalScope(ModelSQL, ModelView):
|
|
'Technical Scope'
|
|
__name__ = 'lims.technical.scope'
|
|
_rec_name = 'party'
|
|
|
|
party = fields.Many2One('party.party', 'Party', required=True)
|
|
certification_type = fields.Many2One('lims.certification.type',
|
|
'Certification type')
|
|
versions = fields.One2Many('lims.technical.scope.version',
|
|
'technical_scope', 'Versions')
|
|
|
|
def get_rec_name(self, name):
|
|
if self.party:
|
|
return self.party.rec_name
|
|
|
|
@classmethod
|
|
def search_rec_name(cls, name, clause):
|
|
return [('party',) + tuple(clause[1:])]
|
|
|
|
|
|
class LimsTechnicalScopeVersion(ModelSQL, ModelView):
|
|
'Technical Scope Version'
|
|
__name__ = 'lims.technical.scope.version'
|
|
_rec_name = 'number'
|
|
|
|
technical_scope = fields.Many2One('lims.technical.scope',
|
|
'Technical scope', required=True)
|
|
number = fields.Char('Number', required=True)
|
|
date = fields.Date('Date', required=True)
|
|
expiration_date = fields.Date('Expiration date')
|
|
version_lines = fields.Many2Many('lims.technical.scope.version.line',
|
|
'version', 'typification', 'Typifications')
|
|
valid = fields.Boolean('Active')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(LimsTechnicalScopeVersion, cls).__setup__()
|
|
cls._error_messages.update({
|
|
'active_version': ('Only one version can be active for each '
|
|
'technical scope'),
|
|
})
|
|
cls._buttons.update({
|
|
'open_typifications': {},
|
|
'add_typifications': {
|
|
'invisible': ~Eval('valid'),
|
|
},
|
|
'remove_typifications': {
|
|
'invisible': ~Eval('valid'),
|
|
},
|
|
})
|
|
|
|
@staticmethod
|
|
def default_valid():
|
|
return True
|
|
|
|
@classmethod
|
|
def validate(cls, versions):
|
|
super(LimsTechnicalScopeVersion, cls).validate(versions)
|
|
for version in versions:
|
|
version.check_active()
|
|
|
|
def check_active(self):
|
|
if self.valid:
|
|
versions = self.search([
|
|
('technical_scope', '=', self.technical_scope.id),
|
|
('valid', '=', True),
|
|
('id', '!=', self.id),
|
|
])
|
|
if versions:
|
|
self.raise_user_error('active_version')
|
|
|
|
@classmethod
|
|
@ModelView.button_action('lims.act_open_typifications')
|
|
def open_typifications(cls, versions):
|
|
pass
|
|
|
|
@classmethod
|
|
@ModelView.button_action('lims.act_add_typifications')
|
|
def add_typifications(cls, versions):
|
|
pass
|
|
|
|
@classmethod
|
|
@ModelView.button_action('lims.act_remove_typifications')
|
|
def remove_typifications(cls, versions):
|
|
pass
|
|
|
|
|
|
class LimsTechnicalScopeVersionLine(ModelSQL):
|
|
'Technical Scope Version Line'
|
|
__name__ = 'lims.technical.scope.version.line'
|
|
|
|
version = fields.Many2One('lims.technical.scope.version',
|
|
'Technical scope version', ondelete='CASCADE', select=True,
|
|
required=True)
|
|
typification = fields.Many2One('lims.typification', 'Typification',
|
|
ondelete='CASCADE', select=True, required=True)
|
|
|
|
|
|
class LimsService(ModelSQL, ModelView):
|
|
'Service'
|
|
__name__ = 'lims.service'
|
|
_rec_name = 'number'
|
|
|
|
number = fields.Char('Number', select=True, readonly=True)
|
|
create_date2 = fields.Function(fields.DateTime('Create Date'),
|
|
'get_create_date2', searcher='search_create_date2')
|
|
fraction = fields.Many2One('lims.fraction', 'Fraction', required=True,
|
|
ondelete='CASCADE', select=True, depends=['number'],
|
|
states={'readonly': Or(Bool(Eval('number')),
|
|
Bool(Eval('context', {}).get('readonly', True))),
|
|
})
|
|
fraction_view = fields.Function(fields.Many2One('lims.fraction',
|
|
'Fraction', states={'invisible': Not(Bool(Eval('_parent_fraction')))}),
|
|
'on_change_with_fraction_view')
|
|
sample = fields.Function(fields.Many2One('lims.sample', 'Sample'),
|
|
'get_fraction_field',
|
|
searcher='search_fraction_field')
|
|
entry = fields.Function(fields.Many2One('lims.entry', 'Entry'),
|
|
'get_fraction_field',
|
|
searcher='search_fraction_field')
|
|
party = fields.Function(fields.Many2One('party.party', 'Party'),
|
|
'get_fraction_field',
|
|
searcher='search_fraction_field')
|
|
analysis = fields.Many2One('lims.analysis', 'Analysis/Set/Group',
|
|
required=True, depends=['analysis_domain'],
|
|
domain=[('id', 'in', Eval('analysis_domain'))],
|
|
states={'readonly': Bool(Eval('context', {}).get('readonly', True))})
|
|
analysis_view = fields.Function(fields.Many2One('lims.analysis',
|
|
'Analysis/Set/Group'), 'get_views_field',
|
|
searcher='search_views_field')
|
|
analysis_domain = fields.Function(fields.Many2Many('lims.analysis',
|
|
None, None, 'Analysis domain'),
|
|
'on_change_with_analysis_domain')
|
|
typification_domain = fields.Function(fields.Many2Many(
|
|
'lims.typification', None, None, 'Typification domain'),
|
|
'on_change_with_typification_domain')
|
|
analysis_type = fields.Function(fields.Selection([
|
|
('analysis', 'Analysis'),
|
|
('set', 'Set'),
|
|
('group', 'Group'),
|
|
], 'Type', sort=False),
|
|
'on_change_with_analysis_type', searcher='search_analysis_field')
|
|
urgent = fields.Boolean('Urgent',
|
|
states={'readonly': Bool(Eval('context', {}).get('readonly', True))})
|
|
priority = fields.Integer('Priority',
|
|
states={'readonly': Bool(Eval('context', {}).get('readonly', True))})
|
|
report_date = fields.Date('Date agreed for result')
|
|
laboratory = fields.Many2One('lims.laboratory', 'Laboratory',
|
|
domain=[('id', 'in', Eval('laboratory_domain'))],
|
|
states={
|
|
'required': Bool(Eval('laboratory_domain')),
|
|
'readonly': Bool(Eval('context', {}).get('readonly', True)),
|
|
},
|
|
depends=['laboratory_domain'])
|
|
laboratory_view = fields.Function(fields.Many2One('lims.laboratory',
|
|
'Laboratory'), 'get_views_field')
|
|
laboratory_domain = fields.Function(fields.Many2Many('lims.laboratory',
|
|
None, None, 'Laboratory domain'),
|
|
'on_change_with_laboratory_domain')
|
|
method = fields.Many2One('lims.lab.method', 'Method',
|
|
domain=[('id', 'in', Eval('method_domain'))],
|
|
states={
|
|
'required': Bool(Eval('method_domain')),
|
|
'readonly': Bool(Eval('context', {}).get('readonly', True)),
|
|
},
|
|
depends=['method_domain'])
|
|
method_view = fields.Function(fields.Many2One('lims.lab.method',
|
|
'Method'), 'get_views_field')
|
|
method_domain = fields.Function(fields.Many2Many('lims.lab.method',
|
|
None, None, 'Method domain'), 'on_change_with_method_domain')
|
|
device = fields.Many2One('lims.lab.device', 'Device',
|
|
domain=[('id', 'in', Eval('device_domain'))],
|
|
states={
|
|
'required': Bool(Eval('device_domain')),
|
|
'readonly': Bool(Eval('context', {}).get('readonly', True)),
|
|
},
|
|
depends=['device_domain'])
|
|
device_view = fields.Function(fields.Many2One('lims.lab.device',
|
|
'Device'), 'get_views_field')
|
|
device_domain = fields.Function(fields.Many2Many('lims.lab.device',
|
|
None, None, 'Device domain'), 'on_change_with_device_domain')
|
|
comments = fields.Text('Comments',
|
|
states={'readonly': Bool(Eval('context', {}).get('readonly', True))})
|
|
analysis_detail = fields.One2Many('lims.entry.detail.analysis',
|
|
'service', 'Analysis detail')
|
|
confirmed = fields.Function(fields.Boolean('Confirmed'), 'get_confirmed',
|
|
searcher='search_confirmed')
|
|
confirmation_date = fields.Date('Confirmation date', readonly=True)
|
|
divide = fields.Boolean('Divide')
|
|
has_results_report = fields.Function(fields.Boolean('Results Report'),
|
|
'get_has_results_report')
|
|
manage_service_available = fields.Function(fields.Boolean(
|
|
'Available for Manage services'), 'get_manage_service_available')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(LimsService, cls).__setup__()
|
|
cls._order.insert(0, ('number', 'DESC'))
|
|
cls._error_messages.update({
|
|
'no_service_sequence': ('There is no service sequence for '
|
|
'the work year "%s".'),
|
|
'delete_service': ('You can not delete service "%s" because '
|
|
'its fraction is confirmed'),
|
|
'duplicated_analysis': ('The analysis "%(analysis)s" is assigned '
|
|
'more than once to the fraction "%(fraction)s"'),
|
|
})
|
|
|
|
@staticmethod
|
|
def default_urgent():
|
|
return False
|
|
|
|
@staticmethod
|
|
def default_priority():
|
|
return 0
|
|
|
|
@staticmethod
|
|
def default_divide():
|
|
return False
|
|
|
|
@classmethod
|
|
def validate(cls, services):
|
|
super(LimsService, cls).validate(services)
|
|
for service in services:
|
|
service.check_duplicated_analysis()
|
|
|
|
def check_duplicated_analysis(self):
|
|
LimsAnalysis = Pool().get('lims.analysis')
|
|
|
|
fraction_id = self.fraction.id
|
|
services = self.search([
|
|
('fraction', '=', fraction_id),
|
|
('id', '!=', self.id)
|
|
])
|
|
if services:
|
|
analysis_ids = []
|
|
for service in services:
|
|
if service.analysis:
|
|
analysis_ids.append(service.analysis.id)
|
|
analysis_ids.extend(LimsAnalysis.get_included_analysis(
|
|
service.analysis.id))
|
|
|
|
new_analysis_ids = [self.analysis.id]
|
|
new_analysis_ids.extend(LimsAnalysis.get_included_analysis(
|
|
self.analysis.id))
|
|
for a_id in new_analysis_ids:
|
|
if a_id in analysis_ids:
|
|
analysis = LimsAnalysis(a_id)
|
|
self.raise_user_error('duplicated_analysis', {
|
|
'analysis': analysis.rec_name,
|
|
'fraction': self.fraction.rec_name,
|
|
})
|
|
|
|
@classmethod
|
|
def create(cls, vlist):
|
|
pool = Pool()
|
|
LimsLabWorkYear = pool.get('lims.lab.workyear')
|
|
Sequence = pool.get('ir.sequence')
|
|
EntryDetailAnalysis = pool.get('lims.entry.detail.analysis')
|
|
|
|
workyear_id = LimsLabWorkYear.find()
|
|
workyear = LimsLabWorkYear(workyear_id)
|
|
sequence = workyear.get_sequence('service')
|
|
if not sequence:
|
|
cls.raise_user_error('no_service_sequence',
|
|
(workyear.rec_name,))
|
|
|
|
vlist = [x.copy() for x in vlist]
|
|
for values in vlist:
|
|
values['number'] = Sequence.get_id(sequence.id)
|
|
services = super(LimsService, cls).create(vlist)
|
|
|
|
if not Transaction().context.get('copying', False):
|
|
cls.update_analysis_detail(services)
|
|
aditional_services = cls.create_aditional_services(services)
|
|
|
|
# Aditional processing for Manage Services
|
|
if aditional_services and Transaction().context.get(
|
|
'manage_service', False):
|
|
cls.copy_analysis_comments(aditional_services)
|
|
cls.set_confirmation_date(aditional_services)
|
|
analysis_detail = EntryDetailAnalysis.search([
|
|
('service', 'in', [s.id for s in aditional_services])])
|
|
if analysis_detail:
|
|
fraction = analysis_detail[0].fraction
|
|
EntryDetailAnalysis.create_notebook_lines(analysis_detail,
|
|
fraction)
|
|
# from lims_planification
|
|
if 'trytond.modules.lims_planification' in sys.modules:
|
|
EntryDetailAnalysis.write(analysis_detail, {
|
|
'state': 'unplanned',
|
|
})
|
|
# from lims_account_invoice
|
|
if 'trytond.modules.lims_account_invoice' in sys.modules:
|
|
for aditional_service in aditional_services:
|
|
aditional_service.create_invoice_line('out')
|
|
|
|
return services
|
|
|
|
@classmethod
|
|
def write(cls, *args):
|
|
super(LimsService, cls).write(*args)
|
|
actions = iter(args)
|
|
for services, vals in zip(actions, actions):
|
|
change_detail = False
|
|
for field in ('analysis', 'laboratory', 'method', 'device'):
|
|
if vals.get(field):
|
|
change_detail = True
|
|
break
|
|
if change_detail:
|
|
cls.update_analysis_detail(services)
|
|
|
|
@classmethod
|
|
def delete(cls, services):
|
|
if Transaction().user != 0:
|
|
cls.check_delete(services)
|
|
super(LimsService, cls).delete(services)
|
|
|
|
@classmethod
|
|
def check_delete(cls, services):
|
|
for service in services:
|
|
if service.fraction and service.fraction.confirmed:
|
|
cls.raise_user_error('delete_service', (service.rec_name,))
|
|
|
|
@staticmethod
|
|
def update_analysis_detail(services):
|
|
pool = Pool()
|
|
LimsService = pool.get('lims.service')
|
|
LimsEntryDetailAnalysis = pool.get('lims.entry.detail.analysis')
|
|
|
|
for service in services:
|
|
to_delete = LimsEntryDetailAnalysis.search([
|
|
('service', '=', service.id),
|
|
])
|
|
if to_delete:
|
|
with Transaction().set_user(0, set_context=True):
|
|
LimsEntryDetailAnalysis.delete(to_delete)
|
|
|
|
if service.analysis.behavior == 'additional':
|
|
continue
|
|
|
|
to_create = []
|
|
service_context = {
|
|
'product_type': service.fraction.product_type.id,
|
|
'matrix': service.fraction.matrix.id,
|
|
}
|
|
analysis_data = []
|
|
if service.analysis.type == 'analysis':
|
|
laboratory_id = service.laboratory.id
|
|
method_id = service.method.id if service.method else None
|
|
device_id = service.device.id if service.device else None
|
|
|
|
analysis_data.append({
|
|
'id': service.analysis.id,
|
|
'origin': service.analysis.code,
|
|
'laboratory': laboratory_id,
|
|
'method': method_id,
|
|
'device': device_id,
|
|
})
|
|
else:
|
|
analysis_data.extend(LimsService._get_included_analysis(
|
|
service.analysis, service.analysis.code,
|
|
service_context))
|
|
|
|
if analysis_data:
|
|
for analysis in analysis_data:
|
|
values = {}
|
|
values['service'] = service.id
|
|
values['analysis'] = analysis['id']
|
|
values['analysis_origin'] = analysis['origin']
|
|
values['laboratory'] = analysis['laboratory']
|
|
values['method'] = analysis['method']
|
|
values['device'] = analysis['device']
|
|
to_create.append(values)
|
|
|
|
if to_create:
|
|
with Transaction().set_user(0, set_context=True):
|
|
LimsEntryDetailAnalysis.create(to_create)
|
|
|
|
@staticmethod
|
|
def _get_included_analysis(analysis, analysis_origin='',
|
|
service_context=None):
|
|
LimsTypification = Pool().get('lims.typification')
|
|
|
|
childs = []
|
|
if analysis.included_analysis:
|
|
for included in analysis.included_analysis:
|
|
if (analysis.type == 'set' and
|
|
included.included_analysis.type == 'analysis'):
|
|
origin = analysis_origin
|
|
else:
|
|
origin = (analysis_origin + ' > ' +
|
|
included.included_analysis.code)
|
|
if included.included_analysis.type == 'analysis':
|
|
|
|
laboratory_id = included.laboratory.id
|
|
|
|
typifications = LimsTypification.search([
|
|
('product_type', '=', service_context['product_type']),
|
|
('matrix', '=', service_context['matrix']),
|
|
('analysis', '=', included.included_analysis),
|
|
('by_default', '=', True),
|
|
('valid', '=', True),
|
|
])
|
|
method_id = (typifications[0].method.id if typifications
|
|
else None)
|
|
|
|
device_id = None
|
|
if included.included_analysis.devices:
|
|
for d in included.included_analysis.devices:
|
|
if (d.laboratory.id == laboratory_id
|
|
and d.by_default is True):
|
|
device_id = d.device.id
|
|
|
|
childs.append({
|
|
'id': included.included_analysis.id,
|
|
'origin': origin,
|
|
'laboratory': laboratory_id,
|
|
'method': method_id,
|
|
'device': device_id,
|
|
})
|
|
childs.extend(LimsService._get_included_analysis(
|
|
included.included_analysis, origin, service_context))
|
|
return childs
|
|
|
|
@staticmethod
|
|
def create_aditional_services(services):
|
|
cursor = Transaction().connection.cursor()
|
|
pool = Pool()
|
|
LimsEntryDetailAnalysis = pool.get('lims.entry.detail.analysis')
|
|
LimsTypification = pool.get('lims.typification')
|
|
LimsAnalysisLaboratory = pool.get('lims.analysis-laboratory')
|
|
LimsAnalysisDevice = pool.get('lims.analysis.device')
|
|
LimsService = pool.get('lims.service')
|
|
|
|
aditional_services = {}
|
|
for service in services:
|
|
entry_details = LimsEntryDetailAnalysis.search([
|
|
('service', '=', service.id),
|
|
])
|
|
for detail in entry_details:
|
|
typifications = LimsTypification.search([
|
|
('product_type', '=', service.fraction.product_type.id),
|
|
('matrix', '=', service.fraction.matrix.id),
|
|
('analysis', '=', detail.analysis.id),
|
|
('method', '=', detail.method),
|
|
('valid', '=', True),
|
|
])
|
|
if not typifications:
|
|
continue
|
|
typification = typifications[0]
|
|
|
|
if typification.additional:
|
|
if service.fraction.id not in aditional_services:
|
|
aditional_services[service.fraction.id] = {}
|
|
if (typification.additional.id not in
|
|
aditional_services[service.fraction.id]):
|
|
|
|
aditional_services[service.fraction.id][
|
|
typification.additional.id] = {
|
|
'laboratory': None,
|
|
'method': None,
|
|
'device': None,
|
|
}
|
|
|
|
if typification.additionals:
|
|
if service.fraction.id not in aditional_services:
|
|
aditional_services[service.fraction.id] = {}
|
|
for additional in typification.additionals:
|
|
if (additional.id not in
|
|
aditional_services[service.fraction.id]):
|
|
|
|
cursor.execute('SELECT laboratory '
|
|
'FROM "' + LimsAnalysisLaboratory._table + '" '
|
|
'WHERE analysis = %s', (additional.id,))
|
|
res = cursor.fetchone()
|
|
laboratory_id = res and res[0] or None
|
|
|
|
cursor.execute('SELECT method '
|
|
'FROM "' + LimsTypification._table + '" '
|
|
'WHERE product_type = %s '
|
|
'AND matrix = %s '
|
|
'AND analysis = %s '
|
|
'AND valid IS TRUE '
|
|
'AND by_default IS TRUE',
|
|
(service.fraction.product_type.id,
|
|
service.fraction.matrix.id, additional.id))
|
|
res = cursor.fetchone()
|
|
method_id = res and res[0] or None
|
|
|
|
cursor.execute('SELECT device '
|
|
'FROM "' + LimsAnalysisDevice._table + '" '
|
|
'WHERE analysis = %s '
|
|
'AND laboratory = %s '
|
|
'AND by_default IS TRUE',
|
|
(additional.id, laboratory_id))
|
|
res = cursor.fetchone()
|
|
device_id = res and res[0] or None
|
|
|
|
aditional_services[service.fraction.id][
|
|
additional.id] = {
|
|
'laboratory': laboratory_id,
|
|
'method': method_id,
|
|
'device': device_id,
|
|
}
|
|
|
|
if aditional_services:
|
|
services_default = []
|
|
for fraction_id, analysis in aditional_services.iteritems():
|
|
for analysis_id, service_data in analysis.iteritems():
|
|
if not LimsService.search([
|
|
('fraction', '=', fraction_id),
|
|
('analysis', '=', analysis_id),
|
|
]):
|
|
services_default.append({
|
|
'fraction': fraction_id,
|
|
'analysis': analysis_id,
|
|
'laboratory': service_data['laboratory'],
|
|
'method': service_data['method'],
|
|
'device': service_data['device'],
|
|
})
|
|
return LimsService.create(services_default)
|
|
|
|
@classmethod
|
|
def view_attributes(cls):
|
|
return [
|
|
('//group[@id="invisible_fields"]', 'states', {
|
|
'invisible': True,
|
|
}),
|
|
('/tree', 'colors',
|
|
If(Bool(Eval('has_results_report')), 'blue',
|
|
If(Bool(Eval('confirmed')), 'black', 'red'))),
|
|
]
|
|
|
|
@classmethod
|
|
def copy(cls, services, default=None):
|
|
if default is None:
|
|
default = {}
|
|
|
|
new_services = []
|
|
for service in sorted(services, key=lambda x: x.number):
|
|
current_default = default.copy()
|
|
current_default['confirmation_date'] = None
|
|
|
|
with Transaction().set_context(copying=True):
|
|
new_service, = super(LimsService, cls).copy([service],
|
|
default=current_default)
|
|
new_services.append(new_service)
|
|
return new_services
|
|
|
|
@staticmethod
|
|
def copy_analysis_comments(services):
|
|
pool = Pool()
|
|
Fraction = pool.get('lims.fraction')
|
|
|
|
comments = {}
|
|
for service in services:
|
|
if service.analysis.comments:
|
|
fraction_id = service.fraction.id
|
|
if fraction_id not in comments:
|
|
comments[fraction_id] = ''
|
|
if comments[fraction_id]:
|
|
comments[fraction_id] += '\n'
|
|
comments[fraction_id] += service.analysis.comments
|
|
if comments:
|
|
fractions_to_save = []
|
|
for fraction_id, comment in comments.iteritems():
|
|
fraction = Fraction(fraction_id)
|
|
if fraction.comments:
|
|
fraction.comments += '\n' + comment
|
|
else:
|
|
fraction.comments = comment
|
|
fractions_to_save.append(fraction)
|
|
if fractions_to_save:
|
|
Fraction.save(fractions_to_save)
|
|
|
|
@staticmethod
|
|
def set_confirmation_date(services, confirmation_date=None):
|
|
pool = Pool()
|
|
Date = pool.get('ir.date')
|
|
LimsService = pool.get('lims.service')
|
|
LimsEntryDetailAnalysis = pool.get('lims.entry.detail.analysis')
|
|
|
|
if not confirmation_date:
|
|
confirmation_date = Date.today()
|
|
LimsService.write(services, {
|
|
'confirmation_date': confirmation_date,
|
|
})
|
|
analysis_details = LimsEntryDetailAnalysis.search([
|
|
('service', 'in', [s.id for s in services]),
|
|
])
|
|
if analysis_details:
|
|
LimsEntryDetailAnalysis.write(analysis_details, {
|
|
'confirmation_date': confirmation_date,
|
|
})
|
|
|
|
@fields.depends('analysis', 'fraction', 'typification_domain',
|
|
'laboratory')
|
|
def on_change_analysis(self):
|
|
LimsLaboratory = Pool().get('lims.laboratory')
|
|
laboratory = None
|
|
method = None
|
|
device = None
|
|
if self.analysis:
|
|
laboratories = self.on_change_with_laboratory_domain()
|
|
if len(laboratories) == 1:
|
|
laboratory = laboratories[0]
|
|
methods = self.on_change_with_method_domain()
|
|
if len(methods) == 1:
|
|
method = methods[0]
|
|
devices = self._on_change_with_device_domain(self.analysis,
|
|
LimsLaboratory(laboratory), True)
|
|
if len(devices) == 1:
|
|
device = devices[0]
|
|
self.laboratory = laboratory
|
|
self.method = method
|
|
self.device = device
|
|
|
|
@staticmethod
|
|
def default_analysis_domain():
|
|
return Transaction().context.get('analysis_domain', [])
|
|
|
|
@fields.depends('fraction')
|
|
def on_change_with_analysis_domain(self, name=None):
|
|
if Transaction().context.get('analysis_domain'):
|
|
return Transaction().context.get('analysis_domain')
|
|
if self.fraction:
|
|
return self.fraction.on_change_with_analysis_domain()
|
|
return []
|
|
|
|
@staticmethod
|
|
def default_typification_domain():
|
|
return Transaction().context.get('typification_domain', [])
|
|
|
|
@fields.depends('fraction')
|
|
def on_change_with_typification_domain(self, name=None):
|
|
if Transaction().context.get('typification_domain'):
|
|
return Transaction().context.get('typification_domain')
|
|
if self.fraction:
|
|
return self.fraction.on_change_with_typification_domain()
|
|
return []
|
|
|
|
@fields.depends('analysis')
|
|
def on_change_with_analysis_type(self, name=None):
|
|
if self.analysis:
|
|
return self.analysis.type
|
|
return ''
|
|
|
|
@staticmethod
|
|
def default_fraction_view():
|
|
if (Transaction().context.get('fraction') > 0):
|
|
return Transaction().context.get('fraction')
|
|
return None
|
|
|
|
@fields.depends('fraction')
|
|
def on_change_with_fraction_view(self, name=None):
|
|
if self.fraction:
|
|
return self.fraction.id
|
|
return None
|
|
|
|
@staticmethod
|
|
def default_sample():
|
|
if (Transaction().context.get('sample') > 0):
|
|
return Transaction().context.get('sample')
|
|
return None
|
|
|
|
@fields.depends('fraction')
|
|
def on_change_with_sample(self, name=None):
|
|
if self.fraction:
|
|
result = self.get_fraction_field((self,), ('sample',))
|
|
return result['sample'][self.id]
|
|
return None
|
|
|
|
@staticmethod
|
|
def default_entry():
|
|
if (Transaction().context.get('entry') > 0):
|
|
return Transaction().context.get('entry')
|
|
return None
|
|
|
|
@fields.depends('fraction')
|
|
def on_change_with_entry(self, name=None):
|
|
if self.fraction:
|
|
result = self.get_fraction_field((self,), ('entry',))
|
|
return result['entry'][self.id]
|
|
return None
|
|
|
|
@staticmethod
|
|
def default_party():
|
|
if (Transaction().context.get('party') > 0):
|
|
return Transaction().context.get('party')
|
|
return None
|
|
|
|
@fields.depends('fraction')
|
|
def on_change_with_party(self, name=None):
|
|
if self.fraction:
|
|
result = self.get_fraction_field((self,), ('party',))
|
|
return result['party'][self.id]
|
|
return None
|
|
|
|
@fields.depends('analysis', 'laboratory')
|
|
def on_change_laboratory(self):
|
|
device = None
|
|
if self.analysis and self.laboratory:
|
|
devices = self._on_change_with_device_domain(self.analysis,
|
|
self.laboratory, True)
|
|
if len(devices) == 1:
|
|
device = devices[0]
|
|
self.device = device
|
|
|
|
@fields.depends('analysis')
|
|
def on_change_with_laboratory_domain(self, name=None):
|
|
cursor = Transaction().connection.cursor()
|
|
LimsAnalysisLaboratory = Pool().get('lims.analysis-laboratory')
|
|
|
|
if not self.analysis:
|
|
return []
|
|
|
|
cursor.execute('SELECT DISTINCT(laboratory) '
|
|
'FROM "' + LimsAnalysisLaboratory._table + '" '
|
|
'WHERE analysis = %s',
|
|
(self.analysis.id,))
|
|
res = cursor.fetchall()
|
|
if not res:
|
|
return []
|
|
return [x[0] for x in res]
|
|
|
|
@fields.depends('analysis', 'typification_domain')
|
|
def on_change_with_method_domain(self, name=None):
|
|
cursor = Transaction().connection.cursor()
|
|
LimsTypification = Pool().get('lims.typification')
|
|
|
|
if not self.analysis:
|
|
return []
|
|
|
|
typification_ids = ', '.join(str(t) for t in
|
|
self.on_change_with_typification_domain())
|
|
if not typification_ids:
|
|
return []
|
|
cursor.execute('SELECT DISTINCT(method) '
|
|
'FROM "' + LimsTypification._table + '" '
|
|
'WHERE id IN (' + typification_ids + ') '
|
|
'AND analysis = %s',
|
|
(self.analysis.id,))
|
|
res = cursor.fetchall()
|
|
if not res:
|
|
return []
|
|
return [x[0] for x in res]
|
|
|
|
@fields.depends('analysis', 'laboratory')
|
|
def on_change_with_device_domain(self, name=None):
|
|
return self._on_change_with_device_domain(self.analysis,
|
|
self.laboratory)
|
|
|
|
@staticmethod
|
|
def _on_change_with_device_domain(analysis=None, laboratory=None,
|
|
by_default=False):
|
|
cursor = Transaction().connection.cursor()
|
|
LimsAnalysisDevice = Pool().get('lims.analysis.device')
|
|
|
|
if not analysis or not laboratory:
|
|
return []
|
|
|
|
if by_default:
|
|
by_default_clause = 'AND by_default = TRUE'
|
|
else:
|
|
by_default_clause = ''
|
|
cursor.execute('SELECT DISTINCT(device) '
|
|
'FROM "' + LimsAnalysisDevice._table + '" '
|
|
'WHERE analysis = %s '
|
|
'AND laboratory = %s '
|
|
+ by_default_clause,
|
|
(analysis.id, laboratory.id))
|
|
res = cursor.fetchall()
|
|
if not res:
|
|
return []
|
|
return [x[0] for x in res]
|
|
|
|
@classmethod
|
|
def get_views_field(cls, services, names):
|
|
result = {}
|
|
for name in names:
|
|
field_name = name[:-5]
|
|
result[name] = {}
|
|
for s in services:
|
|
field = getattr(s, field_name, None)
|
|
result[name][s.id] = field.id if field else None
|
|
return result
|
|
|
|
@classmethod
|
|
def search_views_field(cls, name, clause):
|
|
return [(name[:-5],) + tuple(clause[1:])]
|
|
|
|
@classmethod
|
|
def search_analysis_field(cls, name, clause):
|
|
if name == 'analysis_type':
|
|
name = 'type'
|
|
return [('analysis.' + name,) + tuple(clause[1:])]
|
|
|
|
@classmethod
|
|
def search_create_date2(cls, name, clause):
|
|
cursor = Transaction().connection.cursor()
|
|
operator_ = clause[1:2][0]
|
|
cursor.execute('SELECT id '
|
|
'FROM "' + cls._table + '" '
|
|
'WHERE create_date' + operator_ + ' %s',
|
|
clause[2:3])
|
|
return [('id', 'in', [x[0] for x in cursor.fetchall()])]
|
|
|
|
@classmethod
|
|
def get_fraction_field(cls, services, names):
|
|
result = {}
|
|
for name in names:
|
|
result[name] = {}
|
|
if name == 'label':
|
|
for s in services:
|
|
result[name][s.id] = getattr(s.fraction, name, None)
|
|
else:
|
|
for s in services:
|
|
field = getattr(s.fraction, name, None)
|
|
result[name][s.id] = field.id if field else None
|
|
return result
|
|
|
|
def get_create_date2(self, name):
|
|
return self.create_date.replace(microsecond=0)
|
|
|
|
@classmethod
|
|
def search_fraction_field(cls, name, clause):
|
|
return [('fraction.' + name,) + tuple(clause[1:])]
|
|
|
|
@classmethod
|
|
def order_create_date2(cls, tables):
|
|
return cls.create_date.convert_order('create_date', tables, cls)
|
|
|
|
def _order_analysis_field(name):
|
|
def order_field(tables):
|
|
Analysis = Pool().get('lims.analysis')
|
|
field = Analysis._fields[name]
|
|
table, _ = tables[None]
|
|
analysis_tables = tables.get('analysis')
|
|
if analysis_tables is None:
|
|
analysis = Analysis.__table__()
|
|
analysis_tables = {
|
|
None: (analysis, analysis.id == table.analysis),
|
|
}
|
|
tables['analysis'] = analysis_tables
|
|
return field.convert_order(name, analysis_tables, Analysis)
|
|
return staticmethod(order_field)
|
|
# Redefine convert_order function with 'order_%s' % field
|
|
order_analysis_view = _order_analysis_field('id')
|
|
order_analysis_type = _order_analysis_field('type')
|
|
|
|
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_sample = _order_fraction_field('sample')
|
|
order_entry = _order_fraction_field('entry')
|
|
order_party = _order_fraction_field('party')
|
|
|
|
def get_confirmed(self, name=None):
|
|
if self.fraction:
|
|
return self.fraction.confirmed
|
|
return False
|
|
|
|
@classmethod
|
|
def search_confirmed(cls, name, clause):
|
|
return [('fraction.confirmed',) + tuple(clause[1:])]
|
|
|
|
@classmethod
|
|
def get_has_results_report(cls, services, names):
|
|
cursor = Transaction().connection.cursor()
|
|
LimsNotebookLine = Pool().get('lims.notebook.line')
|
|
|
|
result = {}
|
|
for name in names:
|
|
result[name] = {}
|
|
for s in services:
|
|
cursor.execute('SELECT service '
|
|
'FROM "' + LimsNotebookLine._table + '" '
|
|
'WHERE service = %s '
|
|
'AND results_report IS NOT NULL',
|
|
(s.id,))
|
|
value = False
|
|
if cursor.fetchone():
|
|
value = True
|
|
result[name][s.id] = value
|
|
return result
|
|
|
|
def get_manage_service_available(self, name=None):
|
|
LimsNotebookLine = Pool().get('lims.notebook.line')
|
|
|
|
planned_notebook_lines = LimsNotebookLine.search([
|
|
('service', '=', self.id),
|
|
('planification', '!=', None),
|
|
])
|
|
if planned_notebook_lines:
|
|
return False
|
|
return True
|
|
|
|
|
|
class LimsFraction(ModelSQL, ModelView):
|
|
'Fraction'
|
|
__name__ = 'lims.fraction'
|
|
_rec_name = 'number'
|
|
|
|
number = fields.Char('Number', select=True, readonly=True)
|
|
create_date2 = fields.Function(fields.DateTime('Create Date'),
|
|
'get_create_date2', searcher='search_create_date2')
|
|
sample = fields.Many2One('lims.sample', 'Sample', required=True,
|
|
ondelete='CASCADE', select=True, depends=['number'],
|
|
states={'readonly': Bool(Eval('number'))})
|
|
sample_view = fields.Function(fields.Many2One('lims.sample', 'Sample',
|
|
states={'invisible': Not(Bool(Eval('_parent_sample')))}),
|
|
'on_change_with_sample_view')
|
|
entry = fields.Function(fields.Many2One('lims.entry', 'Entry'),
|
|
'get_sample_field',
|
|
searcher='search_sample_field')
|
|
party = fields.Function(fields.Many2One('party.party', 'Party'),
|
|
'get_sample_field',
|
|
searcher='search_sample_field')
|
|
label = fields.Function(fields.Char('Label'), 'get_sample_field',
|
|
searcher='search_sample_field')
|
|
type = fields.Many2One('lims.fraction.type', 'Fraction type',
|
|
required=True)
|
|
storage_location = fields.Many2One('stock.location', 'Storage location',
|
|
required=True, domain=[('type', '=', 'storage')])
|
|
storage_time = fields.Integer('Storage time (in months)', required=True)
|
|
weight = fields.Float('Weight')
|
|
weight_uom = fields.Many2One('product.uom', 'Weight UoM',
|
|
domain=[('category.lims_only_available', '=', True)])
|
|
packages_quantity = fields.Integer('Packages quantity', required=True)
|
|
package_type = fields.Many2One('lims.packaging.type', 'Package type',
|
|
required=True)
|
|
size = fields.Float('Size')
|
|
size_uom = fields.Many2One('product.uom', 'Size UoM',
|
|
domain=[('category.lims_only_available', '=', True)])
|
|
expiry_date = fields.Date('Expiry date', states={'readonly': True})
|
|
discharge_date = fields.Date('Discharge date')
|
|
countersample_location = fields.Many2One('stock.location',
|
|
'Countersample location', readonly=True)
|
|
countersample_date = fields.Date('Countersample date', readonly=True)
|
|
fraction_state = fields.Many2One('lims.packaging.integrity',
|
|
'Fraction state', required=True)
|
|
services = fields.One2Many('lims.service', 'fraction', 'Services',
|
|
states={'readonly': Bool(Eval('button_manage_services_available'))},
|
|
context={
|
|
'analysis_domain': Eval('analysis_domain'),
|
|
'typification_domain': Eval('typification_domain'),
|
|
'product_type': Eval('product_type'), 'matrix': Eval('matrix'),
|
|
'fraction': Eval('id'), 'sample': Eval('sample'),
|
|
'entry': Eval('entry'), 'party': Eval('party'),
|
|
'readonly': False,
|
|
},
|
|
depends=['button_manage_services_available', 'analysis_domain',
|
|
'typification_domain', 'product_type', 'matrix', 'sample',
|
|
'entry', 'party',
|
|
])
|
|
shared = fields.Boolean('Shared')
|
|
comments = fields.Text('Comments')
|
|
analysis_domain = fields.Function(fields.Many2Many('lims.analysis',
|
|
None, None, 'Analysis domain'),
|
|
'on_change_with_analysis_domain')
|
|
typification_domain = fields.Function(fields.Many2Many(
|
|
'lims.typification', None, None, 'Typification domain'),
|
|
'on_change_with_typification_domain')
|
|
product_type = fields.Function(fields.Many2One('lims.product.type',
|
|
'Product type'),
|
|
'on_change_with_product_type')
|
|
matrix = fields.Function(fields.Many2One('lims.matrix', 'Matrix'),
|
|
'on_change_with_matrix')
|
|
button_manage_services_available = fields.Function(fields.Boolean(
|
|
'Button manage services available'),
|
|
'on_change_with_button_manage_services_available')
|
|
confirmed = fields.Boolean('Confirmed')
|
|
button_confirm_available = fields.Function(fields.Boolean(
|
|
'Button confirm available'),
|
|
'on_change_with_button_confirm_available')
|
|
current_location = fields.Function(fields.Many2One('stock.location',
|
|
'Current Location'), 'get_current_location',
|
|
searcher='search_current_location')
|
|
duplicated_analysis_message = fields.Text('Message', readonly=True,
|
|
states={'invisible': Not(Bool(Eval('duplicated_analysis_message')))})
|
|
has_results_report = fields.Function(fields.Boolean('Results Report'),
|
|
'get_has_results_report', searcher='search_has_results_report')
|
|
has_all_results_reported = fields.Function(fields.Boolean(
|
|
'All results reported'), 'get_has_all_results_reported')
|
|
waiting_confirmation = fields.Boolean('Waiting confirmation')
|
|
entry_state = fields.Function(fields.Selection([
|
|
('draft', 'Draft'),
|
|
('ongoing', 'Ongoing'),
|
|
('pending', 'Administration pending'),
|
|
('closed', 'Closed'),
|
|
], 'Entry State'), 'get_entry_state', searcher='search_entry_state')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(LimsFraction, cls).__setup__()
|
|
cls._order.insert(0, ('number', 'DESC'))
|
|
cls._buttons.update({
|
|
'manage_services': {
|
|
'invisible': ~Eval('button_manage_services_available'),
|
|
},
|
|
'complete_services': {
|
|
'invisible': ~Eval('button_manage_services_available'),
|
|
},
|
|
'confirm': {
|
|
'invisible': ~Eval('button_confirm_available'),
|
|
},
|
|
})
|
|
cls._error_messages.update({
|
|
'missing_fraction_product': ('Missing "Fraction product" '
|
|
'on Lims configuration'),
|
|
'delete_fraction': ('You can not delete fraction "%s" because '
|
|
'it is confirmed'),
|
|
'duplicated_analysis': ('The analysis "%s" is assigned more'
|
|
' than once'),
|
|
'not_services': ('You can not confirm fraction "%s" because '
|
|
'has not services'),
|
|
'not_divided': ('You can not confirm fraction because '
|
|
'is not yet divided'),
|
|
})
|
|
|
|
@staticmethod
|
|
def default_packages_quantity():
|
|
return 1
|
|
|
|
@staticmethod
|
|
def default_storage_time():
|
|
return 3
|
|
|
|
@staticmethod
|
|
def default_confirmed():
|
|
return False
|
|
|
|
@staticmethod
|
|
def default_waiting_confirmation():
|
|
return False
|
|
|
|
@classmethod
|
|
def get_next_number(cls, sample_id, f_count):
|
|
LimsSample = Pool().get('lims.sample')
|
|
|
|
samples = LimsSample.search([('id', '=', sample_id)])
|
|
sample_number = samples[0].number
|
|
fraction_number = cls.search_count([('sample', '=', sample_id)])
|
|
fraction_number += f_count
|
|
return '%s-%s' % (sample_number, fraction_number)
|
|
|
|
@classmethod
|
|
def create(cls, vlist):
|
|
vlist = [x.copy() for x in vlist]
|
|
f_count = {}
|
|
for values in vlist:
|
|
if not values['sample'] in f_count:
|
|
f_count[values['sample']] = 0
|
|
f_count[values['sample']] += 1
|
|
values['number'] = cls.get_next_number(values['sample'],
|
|
f_count[values['sample']])
|
|
return super(LimsFraction, cls).create(vlist)
|
|
|
|
@classmethod
|
|
def view_attributes(cls):
|
|
return [
|
|
('//group[@id="button_confirm"]', 'states', {
|
|
'invisible': ~Eval('button_confirm_available'),
|
|
}),
|
|
('/tree', 'colors',
|
|
If(Bool(Eval('has_results_report')), 'blue',
|
|
If(Bool(Eval('confirmed')), 'black', 'red'))),
|
|
]
|
|
|
|
@classmethod
|
|
def copy(cls, fractions, default=None):
|
|
if default is None:
|
|
default = {}
|
|
|
|
new_fractions = []
|
|
for fraction in sorted(fractions, key=lambda x: x.number):
|
|
current_default = default.copy()
|
|
current_default['confirmed'] = False
|
|
current_default['waiting_confirmation'] = False
|
|
current_default['expiry_date'] = None
|
|
current_default['countersample_date'] = None
|
|
current_default['countersample_location'] = None
|
|
|
|
new_fraction, = super(LimsFraction, cls).copy([fraction],
|
|
default=current_default)
|
|
new_fractions.append(new_fraction)
|
|
return new_fractions
|
|
|
|
@classmethod
|
|
def check_delete(cls, fractions):
|
|
for fraction in fractions:
|
|
if fraction.confirmed:
|
|
cls.raise_user_error('delete_fraction', (fraction.rec_name,))
|
|
|
|
@classmethod
|
|
def delete(cls, fractions):
|
|
cls.check_delete(fractions)
|
|
super(LimsFraction, cls).delete(fractions)
|
|
|
|
@fields.depends('type', 'storage_location')
|
|
def on_change_with_storage_time(self, name=None):
|
|
if (self.type and self.type.max_storage_time):
|
|
return self.type.max_storage_time
|
|
if (self.storage_location and self.storage_location.storage_time):
|
|
return self.storage_location.storage_time
|
|
return 3
|
|
|
|
@staticmethod
|
|
def default_analysis_domain():
|
|
return Transaction().context.get('analysis_domain', [])
|
|
|
|
@fields.depends('sample')
|
|
def on_change_with_analysis_domain(self, name=None):
|
|
if Transaction().context.get('analysis_domain'):
|
|
return Transaction().context.get('analysis_domain')
|
|
if self.sample:
|
|
return self.sample.on_change_with_analysis_domain()
|
|
return []
|
|
|
|
@staticmethod
|
|
def default_typification_domain():
|
|
return Transaction().context.get('typification_domain', [])
|
|
|
|
@fields.depends('sample')
|
|
def on_change_with_typification_domain(self, name=None):
|
|
if Transaction().context.get('typification_domain'):
|
|
return Transaction().context.get('typification_domain')
|
|
if self.sample:
|
|
return self.sample.on_change_with_typification_domain()
|
|
return []
|
|
|
|
@staticmethod
|
|
def default_product_type():
|
|
return Transaction().context.get('product_type', None)
|
|
|
|
@fields.depends('sample')
|
|
def on_change_with_product_type(self, name=None):
|
|
if Transaction().context.get('product_type'):
|
|
return Transaction().context.get('product_type')
|
|
if self.sample and self.sample.product_type:
|
|
return self.sample.product_type.id
|
|
return None
|
|
|
|
@staticmethod
|
|
def default_matrix():
|
|
return Transaction().context.get('matrix', None)
|
|
|
|
@fields.depends('sample')
|
|
def on_change_with_matrix(self, name=None):
|
|
if Transaction().context.get('matrix'):
|
|
return Transaction().context.get('matrix')
|
|
if self.sample and self.sample.matrix:
|
|
return self.sample.matrix.id
|
|
return None
|
|
|
|
@staticmethod
|
|
def default_sample_view():
|
|
if (Transaction().context.get('sample') > 0):
|
|
return Transaction().context.get('sample')
|
|
return None
|
|
|
|
@fields.depends('sample')
|
|
def on_change_with_sample_view(self, name=None):
|
|
if self.sample:
|
|
return self.sample.id
|
|
return None
|
|
|
|
@staticmethod
|
|
def default_entry():
|
|
if (Transaction().context.get('entry') > 0):
|
|
return Transaction().context.get('entry')
|
|
return None
|
|
|
|
@fields.depends('sample')
|
|
def on_change_with_entry(self, name=None):
|
|
if self.sample:
|
|
result = self.get_sample_field((self,), ('entry',))
|
|
return result['entry'][self.id]
|
|
return None
|
|
|
|
@staticmethod
|
|
def default_party():
|
|
if (Transaction().context.get('party') > 0):
|
|
return Transaction().context.get('party')
|
|
return None
|
|
|
|
@fields.depends('sample')
|
|
def on_change_with_party(self, name=None):
|
|
if self.sample:
|
|
result = self.get_sample_field((self,), ('party',))
|
|
return result['party'][self.id]
|
|
return None
|
|
|
|
@staticmethod
|
|
def default_label():
|
|
return Transaction().context.get('label', '')
|
|
|
|
@fields.depends('sample')
|
|
def on_change_with_label(self, name=None):
|
|
if self.sample:
|
|
result = self.get_sample_field((self,), ('label',))
|
|
return result['label'][self.id]
|
|
return ''
|
|
|
|
@staticmethod
|
|
def default_package_type():
|
|
if (Transaction().context.get('package_type') > 0):
|
|
return Transaction().context.get('package_type')
|
|
return None
|
|
|
|
@fields.depends('sample')
|
|
def on_change_with_package_type(self, name=None):
|
|
if self.sample:
|
|
result = self.get_sample_field((self,), ('package_type',))
|
|
return result['package_type'][self.id]
|
|
return None
|
|
|
|
@staticmethod
|
|
def default_size():
|
|
return Transaction().context.get('size', None)
|
|
|
|
@fields.depends('sample')
|
|
def on_change_with_size(self, name=None):
|
|
if self.sample:
|
|
result = self.get_sample_field((self,), ('size',))
|
|
return result['size'][self.id]
|
|
return None
|
|
|
|
@staticmethod
|
|
def default_size_uom():
|
|
if (Transaction().context.get('size_uom') > 0):
|
|
return Transaction().context.get('size_uom')
|
|
return None
|
|
|
|
@fields.depends('sample')
|
|
def on_change_with_size_uom(self, name=None):
|
|
if self.sample:
|
|
result = self.get_sample_field((self,), ('size_uom',))
|
|
return result['size_uom'][self.id]
|
|
return None
|
|
|
|
@staticmethod
|
|
def default_fraction_state():
|
|
if (Transaction().context.get('fraction_state') > 0):
|
|
return Transaction().context.get('fraction_state')
|
|
return None
|
|
|
|
@fields.depends('sample')
|
|
def on_change_with_fraction_state(self, name=None):
|
|
if self.sample:
|
|
result = self.get_sample_field((self,), ('fraction_state',))
|
|
return result['fraction_state'][self.id]
|
|
return None
|
|
|
|
@classmethod
|
|
def get_sample_field(cls, fractions, names):
|
|
result = {}
|
|
for name in names:
|
|
result[name] = {}
|
|
if name in ('label', 'size'):
|
|
for f in fractions:
|
|
result[name][f.id] = getattr(f.sample, name, None)
|
|
elif name == 'fraction_state':
|
|
for f in fractions:
|
|
field = getattr(f.sample, 'package_state', None)
|
|
result[name][f.id] = field.id if field else None
|
|
else:
|
|
for f in fractions:
|
|
field = getattr(f.sample, name, None)
|
|
result[name][f.id] = field.id if field else None
|
|
return result
|
|
|
|
@classmethod
|
|
def search_sample_field(cls, name, clause):
|
|
return [('sample.' + name,) + tuple(clause[1:])]
|
|
|
|
@classmethod
|
|
def get_entry_state(cls, fractions, name):
|
|
result = {}
|
|
for f in fractions:
|
|
result[f.id] = getattr(f.entry, 'state', None)
|
|
return result
|
|
|
|
@classmethod
|
|
def search_entry_state(cls, name, clause):
|
|
return [('sample.entry.state',) + tuple(clause[1:])]
|
|
|
|
@fields.depends('confirmed')
|
|
def on_change_with_button_manage_services_available(self, name=None):
|
|
if self.confirmed:
|
|
return True
|
|
return False
|
|
|
|
@classmethod
|
|
@ModelView.button_action('lims.wiz_lims_manage_services')
|
|
def manage_services(cls, fractions):
|
|
pass
|
|
|
|
@classmethod
|
|
@ModelView.button_action('lims.wiz_lims_complete_services')
|
|
def complete_services(cls, fractions):
|
|
pass
|
|
|
|
@fields.depends('confirmed', 'sample')
|
|
def on_change_with_button_confirm_available(self, name=None):
|
|
if (not self.confirmed and self.sample and self.sample.entry and
|
|
(self.sample.entry.state == 'ongoing')):
|
|
return True
|
|
return False
|
|
|
|
@classmethod
|
|
def check_divided_report(cls, fractions):
|
|
pool = Pool()
|
|
LimsService = pool.get('lims.service')
|
|
LimsEntryDetailAnalysis = pool.get('lims.entry.detail.analysis')
|
|
|
|
services = LimsService.search([
|
|
('fraction', 'in', [f.id for f in fractions]),
|
|
('divide', '=', True),
|
|
])
|
|
if services:
|
|
if (LimsEntryDetailAnalysis.search_count([
|
|
('service', 'in', [s.id for s in services]),
|
|
('report_grouper', '!=', 0),
|
|
]) == 0):
|
|
cls.raise_user_error('not_divided')
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
def confirm(cls, fractions):
|
|
pool = Pool()
|
|
Config = pool.get('lims.configuration')
|
|
Service = pool.get('lims.service')
|
|
EntryDetailAnalysis = pool.get('lims.entry.detail.analysis')
|
|
|
|
confirm_background = Config(1).entry_confirm_background
|
|
|
|
cls.check_divided_report(fractions)
|
|
fractions_to_save = []
|
|
for fraction in fractions:
|
|
services = Service.search([('fraction', '=', fraction.id)])
|
|
Service.copy_analysis_comments(services)
|
|
Service.set_confirmation_date(services)
|
|
fraction.create_laboratory_notebook()
|
|
analysis_detail = EntryDetailAnalysis.search([
|
|
('fraction', '=', fraction.id)])
|
|
if analysis_detail:
|
|
EntryDetailAnalysis.create_notebook_lines(analysis_detail,
|
|
fraction)
|
|
|
|
fraction.confirmed = True
|
|
if confirm_background:
|
|
fraction.waiting_confirmation = True
|
|
else:
|
|
fraction.create_stock_move()
|
|
fractions_to_save.append(fraction)
|
|
cls.save(fractions_to_save)
|
|
|
|
def create_laboratory_notebook(self):
|
|
pool = Pool()
|
|
LimsNotebook = pool.get('lims.notebook')
|
|
with Transaction().set_user(0):
|
|
notebook = LimsNotebook(
|
|
fraction=self.id,
|
|
)
|
|
notebook.save()
|
|
|
|
def create_stock_move(self):
|
|
Move = Pool().get('stock.move')
|
|
move = self._get_stock_move()
|
|
if not move:
|
|
return
|
|
with Transaction().set_context(check_current_location=False):
|
|
move.save()
|
|
Move.assign([move])
|
|
Move.do([move])
|
|
|
|
def _get_stock_move(self):
|
|
pool = Pool()
|
|
Config = pool.get('lims.configuration')
|
|
Date = pool.get('ir.date')
|
|
User = pool.get('res.user')
|
|
Location = pool.get('stock.location')
|
|
Move = pool.get('stock.move')
|
|
|
|
config_ = Config(1)
|
|
if config_.fraction_product:
|
|
product = config_.fraction_product
|
|
else:
|
|
self.raise_user_error('missing_fraction_product')
|
|
today = Date.today()
|
|
company = User(Transaction().user).company
|
|
if self.sample.entry.party.customer_location:
|
|
from_location = self.sample.entry.party.customer_location
|
|
else:
|
|
locations = Location.search([('type', '=', 'customer')])
|
|
from_location = locations[0] if len(locations) == 1 else None
|
|
|
|
with Transaction().set_user(0, set_context=True):
|
|
move = Move()
|
|
move.product = product.id
|
|
move.fraction = self.id
|
|
move.quantity = self.packages_quantity
|
|
move.uom = product.default_uom
|
|
move.from_location = from_location
|
|
move.to_location = self.storage_location
|
|
move.company = company
|
|
move.planned_date = today
|
|
move.origin = self
|
|
move.state = 'draft'
|
|
return move
|
|
|
|
@classmethod
|
|
def confirm_waiting_fractions(cls):
|
|
'''
|
|
Cron - Confirm Waiting Fractions
|
|
'''
|
|
logger = logging.getLogger('lims')
|
|
|
|
fractions = cls.search([
|
|
('waiting_confirmation', '=', True),
|
|
], order=[('id', 'ASC')])
|
|
if fractions:
|
|
logger.info('Cron - Confirming fractions:INIT')
|
|
fractions_to_save = []
|
|
for fraction in fractions:
|
|
fraction.create_stock_move()
|
|
fraction.waiting_confirmation = False
|
|
fractions_to_save.append(fraction)
|
|
cls.save(fractions_to_save)
|
|
logger.info('Cron - Confirming fractions:END')
|
|
|
|
@fields.depends('services')
|
|
def on_change_services(self, name=None):
|
|
LimsAnalysis = Pool().get('lims.analysis')
|
|
self.duplicated_analysis_message = ''
|
|
if self.services:
|
|
analysis_ids = []
|
|
for service in self.services:
|
|
if service.analysis:
|
|
new_analysis_ids = [service.analysis.id]
|
|
new_analysis_ids.extend(LimsAnalysis.get_included_analysis(
|
|
service.analysis.id))
|
|
|
|
for a_id in new_analysis_ids:
|
|
if a_id in analysis_ids:
|
|
analysis = LimsAnalysis(a_id)
|
|
self.duplicated_analysis_message = (
|
|
self.raise_user_error('duplicated_analysis',
|
|
(analysis.rec_name,),
|
|
raise_exception=False))
|
|
return
|
|
analysis_ids.extend(new_analysis_ids)
|
|
|
|
def get_create_date2(self, name):
|
|
return self.create_date.replace(microsecond=0)
|
|
|
|
@classmethod
|
|
def search_create_date2(cls, name, clause):
|
|
cursor = Transaction().connection.cursor()
|
|
operator_ = clause[1:2][0]
|
|
cursor.execute('SELECT id '
|
|
'FROM "' + cls._table + '" '
|
|
'WHERE create_date' + operator_ + ' %s',
|
|
clause[2:3])
|
|
return [('id', 'in', [x[0] for x in cursor.fetchall()])]
|
|
|
|
@fields.depends('countersample_date', 'storage_time')
|
|
def on_change_with_expiry_date(self, name=None):
|
|
if self.countersample_date:
|
|
return self.countersample_date + relativedelta(
|
|
months=self.storage_time)
|
|
return None
|
|
|
|
@classmethod
|
|
def get_current_location(cls, fractions, name=None):
|
|
cursor = Transaction().connection.cursor()
|
|
Move = Pool().get('stock.move')
|
|
|
|
result = {}
|
|
for f in fractions:
|
|
cursor.execute('SELECT to_location '
|
|
'FROM "' + Move._table + '" '
|
|
'WHERE fraction = %s '
|
|
'AND state IN (\'assigned\', \'done\') '
|
|
'ORDER BY effective_date DESC, id DESC '
|
|
'LIMIT 1', (f.id,))
|
|
location = cursor.fetchone()
|
|
result[f.id] = location[0] if location else None
|
|
return result
|
|
|
|
@classmethod
|
|
def search_current_location(cls, name, domain=None):
|
|
if not Transaction().context.get('check_current_location', True):
|
|
return []
|
|
|
|
def _search_current_location_eval_domain(line, domain):
|
|
operator_funcs = {
|
|
'=': operator.eq,
|
|
'>=': operator.ge,
|
|
'>': operator.gt,
|
|
'<=': operator.le,
|
|
'<': operator.lt,
|
|
'!=': operator.ne,
|
|
'in': lambda v, l: v in l,
|
|
'not in': lambda v, l: v not in l,
|
|
'ilike': lambda v, l: False,
|
|
}
|
|
field, op, operand = domain
|
|
value = line.get(field)
|
|
return operator_funcs[op](value, operand)
|
|
|
|
if domain and domain[1] == 'ilike':
|
|
Location = Pool().get('stock.location')
|
|
locations = Location.search([
|
|
('code', '=', domain[2]),
|
|
], order=[])
|
|
if not locations:
|
|
locations = Location.search([
|
|
('name',) + tuple(domain[1:]),
|
|
], order=[])
|
|
if not locations:
|
|
return []
|
|
domain = ('current_location', 'in', [l.id for l in locations])
|
|
|
|
all_fractions = cls.search([])
|
|
current_locations = cls.get_current_location(all_fractions).iteritems()
|
|
|
|
processed_lines = [{
|
|
'fraction': fraction,
|
|
'current_location': location,
|
|
} for fraction, location in current_locations]
|
|
|
|
record_ids = [line['fraction'] for line in processed_lines
|
|
if _search_current_location_eval_domain(line, domain)]
|
|
return [('id', 'in', record_ids)]
|
|
|
|
@classmethod
|
|
def order_create_date2(cls, tables):
|
|
return cls.create_date.convert_order('create_date', tables, cls)
|
|
|
|
def _order_sample_field(name):
|
|
def order_field(tables):
|
|
Sample = Pool().get('lims.sample')
|
|
field = Sample._fields[name]
|
|
table, _ = tables[None]
|
|
sample_tables = tables.get('sample')
|
|
if sample_tables is None:
|
|
sample = Sample.__table__()
|
|
sample_tables = {
|
|
None: (sample, sample.id == table.sample),
|
|
}
|
|
tables['sample'] = sample_tables
|
|
return field.convert_order(name, sample_tables, Sample)
|
|
return staticmethod(order_field)
|
|
# Redefine convert_order function with 'order_%s' % field
|
|
order_entry = _order_sample_field('entry')
|
|
order_party = _order_sample_field('party')
|
|
order_label = _order_sample_field('label')
|
|
order_product_type = _order_sample_field('product_type')
|
|
order_matrix = _order_sample_field('matrix')
|
|
|
|
@classmethod
|
|
def get_has_results_report(cls, fractions, names):
|
|
cursor = Transaction().connection.cursor()
|
|
pool = Pool()
|
|
LimsService = pool.get('lims.service')
|
|
LimsNotebookLine = pool.get('lims.notebook.line')
|
|
|
|
result = {}
|
|
for name in names:
|
|
result[name] = {}
|
|
for f in fractions:
|
|
cursor.execute('SELECT s.fraction '
|
|
'FROM "' + LimsService._table + '" s '
|
|
'INNER JOIN "' + LimsNotebookLine._table + '" nl '
|
|
'ON s.id = nl.service '
|
|
'WHERE s.fraction = %s '
|
|
'AND nl.results_report IS NOT NULL',
|
|
(f.id,))
|
|
value = False
|
|
if cursor.fetchone():
|
|
value = True
|
|
result[name][f.id] = value
|
|
return result
|
|
|
|
@classmethod
|
|
def search_has_results_report(cls, name, clause):
|
|
cursor = Transaction().connection.cursor()
|
|
pool = Pool()
|
|
LimsService = pool.get('lims.service')
|
|
LimsNotebookLine = pool.get('lims.notebook.line')
|
|
|
|
cursor.execute('SELECT DISTINCT(s.fraction) '
|
|
'FROM "' + LimsService._table + '" s '
|
|
'INNER JOIN "' + LimsNotebookLine._table + '" nl '
|
|
'ON s.id = nl.service '
|
|
'WHERE nl.results_report IS NOT NULL')
|
|
has_results_report = [x[0] for x in cursor.fetchall()]
|
|
|
|
field, op, operand = clause
|
|
if (op, operand) in (('=', True), ('!=', False)):
|
|
return [('id', 'in', has_results_report)]
|
|
elif (op, operand) in (('=', False), ('!=', True)):
|
|
return [('id', 'not in', has_results_report)]
|
|
else:
|
|
return []
|
|
|
|
def get_has_all_results_reported(self, name=None):
|
|
LimsNotebookLine = Pool().get('lims.notebook.line')
|
|
notebook_lines = LimsNotebookLine.search([
|
|
('analysis_detail.service.fraction', '=', self.id),
|
|
('report', '=', True),
|
|
('annulled', '=', False),
|
|
])
|
|
if not notebook_lines:
|
|
return False
|
|
for nl in notebook_lines:
|
|
if not nl.accepted:
|
|
return False
|
|
if not nl.results_report:
|
|
return False
|
|
return True
|
|
|
|
def get_formated_number(self, format):
|
|
formated_number = self.number
|
|
|
|
number_parts = self.number.split('/')
|
|
number_parts2 = number_parts[1].split('-')
|
|
if len(number_parts2) < 2: # 2014: "0000097-1/2014"
|
|
number_parts2 = number_parts[0].split('-')
|
|
sample_number = number_parts2[0]
|
|
sample_year = number_parts[1]
|
|
fraction_number = number_parts2[1]
|
|
else: # 2015: "2015/0000017-1"
|
|
sample_number = number_parts2[0]
|
|
sample_year = number_parts[0]
|
|
fraction_number = number_parts2[1]
|
|
|
|
if format == 'sn-sy-fn':
|
|
formated_number = (sample_number + '-' + sample_year + '-' +
|
|
fraction_number)
|
|
|
|
elif format == 'sy-sn-fn':
|
|
formated_number = (sample_year + '-' + sample_number + '-' +
|
|
fraction_number)
|
|
|
|
elif format == 'pt-m-sn-sy-fn':
|
|
formated_number = (self.product_type.code + '-' +
|
|
self.matrix.code + '-' + sample_number + '-' +
|
|
sample_year + '-' + fraction_number)
|
|
|
|
elif format == 'pt-m-sy-sn-fn':
|
|
formated_number = (self.product_type.code + '-' +
|
|
self.matrix.code + '-' + sample_year + '-' +
|
|
sample_number + '-' + fraction_number)
|
|
|
|
return formated_number
|
|
|
|
|
|
class LimsSample(ModelSQL, ModelView):
|
|
'Sample'
|
|
__name__ = 'lims.sample'
|
|
_rec_name = 'number'
|
|
|
|
number = fields.Char('Number', select=True, readonly=True)
|
|
create_date2 = fields.Function(fields.DateTime('Create Date'),
|
|
'get_create_date2', searcher='search_create_date2')
|
|
date = fields.DateTime('Date', required=True)
|
|
date2 = fields.Function(fields.Date('Date'), 'get_date',
|
|
searcher='search_date')
|
|
entry = fields.Many2One('lims.entry', 'Entry', required=True,
|
|
ondelete='CASCADE', select=True, depends=['number'],
|
|
states={'readonly': Bool(Eval('number'))})
|
|
entry_view = fields.Function(fields.Many2One('lims.entry', 'Entry',
|
|
states={'invisible': Not(Bool(Eval('_parent_entry')))}),
|
|
'on_change_with_entry_view')
|
|
party = fields.Function(fields.Many2One('party.party', 'Party'),
|
|
'get_entry_field',
|
|
searcher='search_entry_field')
|
|
producer = fields.Many2One('lims.sample.producer', 'Producer company',
|
|
domain=[('party', '=', Eval('party'))], depends=['party'])
|
|
label = fields.Char('Label', translate=True)
|
|
sample_client_description = fields.Char('Product described by the client',
|
|
translate=True)
|
|
product_type = fields.Many2One('lims.product.type', 'Product type',
|
|
states={'readonly': Bool(Eval('product_type_matrix_readonly'))},
|
|
required=True, domain=[
|
|
('id', 'in', Eval('product_type_domain')),
|
|
], depends=['product_type_domain', 'product_type_matrix_readonly'])
|
|
product_type_view = fields.Function(fields.Many2One('lims.product.type',
|
|
'Product type'), 'get_views_field', searcher='search_views_field')
|
|
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': Bool(Eval('product_type_matrix_readonly'))},
|
|
domain=[
|
|
('id', 'in', Eval('matrix_domain')),
|
|
], depends=['matrix_domain', 'product_type_matrix_readonly'])
|
|
matrix_view = fields.Function(fields.Many2One('lims.matrix',
|
|
'Matrix'), 'get_views_field', searcher='search_views_field')
|
|
matrix_domain = fields.Function(fields.Many2Many('lims.matrix',
|
|
None, None, 'Matrix domain'),
|
|
'on_change_with_matrix_domain')
|
|
product_type_matrix_readonly = fields.Function(fields.Boolean(
|
|
'Product type and Matrix readonly'),
|
|
'get_product_type_matrix_readonly')
|
|
package_state = fields.Many2One('lims.packaging.integrity',
|
|
'Package state')
|
|
package_type = fields.Many2One('lims.packaging.type', 'Package type')
|
|
packages_quantity = fields.Integer('Packages quantity', required=True)
|
|
size = fields.Float('Size')
|
|
size_uom = fields.Many2One('product.uom', 'Size UoM',
|
|
domain=[('category.lims_only_available', '=', True)])
|
|
restricted_entry = fields.Boolean('Restricted entry',
|
|
states={'readonly': True})
|
|
zone = fields.Many2One('lims.zone', 'Zone', required=True)
|
|
trace_report = fields.Boolean('Trace report')
|
|
fractions = fields.One2Many('lims.fraction', 'sample', 'Fractions',
|
|
context={
|
|
'analysis_domain': Eval('analysis_domain'),
|
|
'typification_domain': Eval('typification_domain'),
|
|
'product_type': Eval('product_type'), 'matrix': Eval('matrix'),
|
|
'sample': Eval('id'), 'entry': Eval('entry'),
|
|
'party': Eval('party'), 'label': Eval('label'),
|
|
'package_type': Eval('package_type'), 'size': Eval('size'),
|
|
'size_uom': Eval('size_uom'),
|
|
'fraction_state': Eval('package_state'),
|
|
},
|
|
depends=['analysis_domain', 'typification_domain', 'entry',
|
|
'party', 'label'])
|
|
report_comments = fields.Text('Report comments', translate=True)
|
|
comments = fields.Text('Comments')
|
|
variety = fields.Many2One('lims.variety', 'Variety',
|
|
domain=[('varieties.matrix', '=', Eval('matrix'))],
|
|
depends=['matrix'])
|
|
analysis_domain = fields.Function(fields.Many2Many('lims.analysis',
|
|
None, None, 'Analysis domain'), 'on_change_with_analysis_domain')
|
|
typification_domain = fields.Function(fields.Many2Many(
|
|
'lims.typification', None, None, 'Typification domain'),
|
|
'on_change_with_typification_domain')
|
|
confirmed = fields.Function(fields.Boolean('Confirmed'), 'get_confirmed')
|
|
has_results_report = fields.Function(fields.Boolean('Results Report'),
|
|
'get_has_results_report')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(LimsSample, cls).__setup__()
|
|
cls._order.insert(0, ('number', 'DESC'))
|
|
cls._error_messages.update({
|
|
'no_sample_sequence': ('There is no sample sequence for '
|
|
'the work year "%s".'),
|
|
'duplicated_label': ('The label "%s" is already present in '
|
|
'another sample'),
|
|
'delete_sample': ('You can not delete sample "%s" because '
|
|
'its entry is not in draft state'),
|
|
})
|
|
|
|
@staticmethod
|
|
def default_date():
|
|
return datetime.now()
|
|
|
|
@staticmethod
|
|
def default_restricted_entry():
|
|
return False
|
|
|
|
@staticmethod
|
|
def default_trace_report():
|
|
return False
|
|
|
|
@classmethod
|
|
def copy(cls, samples, default=None):
|
|
if default is None:
|
|
default = {}
|
|
|
|
new_samples = []
|
|
for sample in sorted(samples, key=lambda x: x.number):
|
|
new_sample, = super(LimsSample, cls).copy([sample],
|
|
default=default)
|
|
new_samples.append(new_sample)
|
|
return new_samples
|
|
|
|
def get_date(self, name):
|
|
pool = Pool()
|
|
Company = pool.get('company.company')
|
|
|
|
date = self.date
|
|
company_id = Transaction().context.get('company')
|
|
if company_id:
|
|
date = Company(company_id).convert_timezone_datetime(date)
|
|
return date.date()
|
|
|
|
def get_create_date2(self, name):
|
|
return self.create_date.replace(microsecond=0)
|
|
|
|
@classmethod
|
|
def search_date(cls, name, clause):
|
|
pool = Pool()
|
|
Company = pool.get('company.company')
|
|
cursor = Transaction().connection.cursor()
|
|
|
|
timezone = None
|
|
company_id = Transaction().context.get('company')
|
|
if company_id:
|
|
timezone = Company(company_id).timezone
|
|
timezone_datetime = 'date::timestamp AT TIME ZONE \'UTC\''
|
|
if timezone:
|
|
timezone_datetime += ' AT TIME ZONE \'' + timezone + '\''
|
|
|
|
operator_ = clause[1:2][0]
|
|
cursor.execute('SELECT id '
|
|
'FROM "' + cls._table + '" '
|
|
'WHERE (' + timezone_datetime + ')::date '
|
|
+ operator_ + ' %s::date', clause[2:3])
|
|
return [('id', 'in', [x[0] for x in cursor.fetchall()])]
|
|
|
|
@classmethod
|
|
def search_create_date2(cls, name, clause):
|
|
cursor = Transaction().connection.cursor()
|
|
operator_ = clause[1:2][0]
|
|
cursor.execute('SELECT id '
|
|
'FROM "' + cls._table + '" '
|
|
'WHERE create_date' + operator_ + ' %s',
|
|
clause[2:3])
|
|
return [('id', 'in', [x[0] for x in cursor.fetchall()])]
|
|
|
|
@classmethod
|
|
def create(cls, vlist):
|
|
pool = Pool()
|
|
LimsLabWorkYear = pool.get('lims.lab.workyear')
|
|
Sequence = pool.get('ir.sequence')
|
|
|
|
workyear_id = LimsLabWorkYear.find()
|
|
workyear = LimsLabWorkYear(workyear_id)
|
|
sequence = workyear.get_sequence('sample')
|
|
if not sequence:
|
|
cls.raise_user_error('no_sample_sequence',
|
|
(workyear.rec_name,))
|
|
|
|
vlist = [x.copy() for x in vlist]
|
|
for values in vlist:
|
|
values['number'] = Sequence.get_id(sequence.id)
|
|
samples = super(LimsSample, cls).create(vlist)
|
|
for sample in samples:
|
|
sample.warn_duplicated_label()
|
|
return samples
|
|
|
|
def warn_duplicated_label(self):
|
|
return # deactivated
|
|
if self.label:
|
|
duplicated = self.search([
|
|
('entry', '=', self.entry.id),
|
|
('label', '=', self.label),
|
|
('id', '!=', self.id),
|
|
])
|
|
if duplicated:
|
|
self.raise_user_warning('lims_sample_label@%s' %
|
|
self.number, 'duplicated_label', self.label)
|
|
|
|
@classmethod
|
|
def write(cls, *args):
|
|
super(LimsSample, cls).write(*args)
|
|
actions = iter(args)
|
|
for samples, vals in zip(actions, actions):
|
|
if vals.get('label'):
|
|
for sample in samples:
|
|
sample.warn_duplicated_label()
|
|
|
|
@fields.depends('product_type', 'matrix', 'zone')
|
|
def on_change_with_restricted_entry(self, name=None):
|
|
return (self.product_type and self.product_type.restricted_entry
|
|
and self.matrix and self.matrix.restricted_entry
|
|
and self.zone and self.zone.restricted_entry)
|
|
|
|
@fields.depends('product_type', 'matrix')
|
|
def on_change_with_analysis_domain(self, name=None):
|
|
cursor = Transaction().connection.cursor()
|
|
pool = Pool()
|
|
LimsTypification = pool.get('lims.typification')
|
|
LimsCalculatedTypification = pool.get('lims.typification.calculated')
|
|
LimsAnalysis = pool.get('lims.analysis')
|
|
|
|
if not self.product_type or not self.matrix:
|
|
return []
|
|
|
|
cursor.execute('SELECT DISTINCT(analysis) '
|
|
'FROM "' + LimsTypification._table + '" '
|
|
'WHERE product_type = %s '
|
|
'AND matrix = %s '
|
|
'AND valid',
|
|
(self.product_type.id, self.matrix.id))
|
|
typified_analysis = [a[0] for a in cursor.fetchall()]
|
|
if not typified_analysis:
|
|
return []
|
|
|
|
cursor.execute('SELECT id '
|
|
'FROM "' + LimsAnalysis._table + '" '
|
|
'WHERE type = \'analysis\' '
|
|
'AND behavior IN (\'normal\', \'internal_relation\') '
|
|
'AND disable_as_individual IS TRUE '
|
|
'AND state = \'active\'')
|
|
disabled_analysis = [a[0] for a in cursor.fetchall()]
|
|
if disabled_analysis:
|
|
typified_analysis = list(set(typified_analysis)
|
|
- set(disabled_analysis))
|
|
|
|
cursor.execute('SELECT DISTINCT(analysis) '
|
|
'FROM "' + LimsCalculatedTypification._table + '" '
|
|
'WHERE product_type = %s '
|
|
'AND matrix = %s',
|
|
(self.product_type.id, self.matrix.id))
|
|
typified_sets_groups = [a[0] for a in cursor.fetchall()]
|
|
|
|
cursor.execute('SELECT id '
|
|
'FROM "' + LimsAnalysis._table + '" '
|
|
'WHERE behavior = \'additional\' '
|
|
'AND state = \'active\'')
|
|
additional_analysis = [a[0] for a in cursor.fetchall()]
|
|
|
|
return typified_analysis + typified_sets_groups + additional_analysis
|
|
|
|
@fields.depends('product_type', 'matrix')
|
|
def on_change_with_typification_domain(self, name=None):
|
|
cursor = Transaction().connection.cursor()
|
|
LimsTypification = Pool().get('lims.typification')
|
|
|
|
if not self.product_type or not self.matrix:
|
|
return []
|
|
|
|
cursor.execute('SELECT id '
|
|
'FROM "' + LimsTypification._table + '" '
|
|
'WHERE product_type = %s '
|
|
'AND matrix = %s '
|
|
'AND valid',
|
|
(self.product_type.id, self.matrix.id))
|
|
res = cursor.fetchall()
|
|
if not res:
|
|
return []
|
|
return [x[0] for x in res]
|
|
|
|
@staticmethod
|
|
def default_product_type_domain():
|
|
cursor = Transaction().connection.cursor()
|
|
LimsTypification = Pool().get('lims.typification')
|
|
|
|
cursor.execute('SELECT DISTINCT(product_type) '
|
|
'FROM "' + LimsTypification._table + '" '
|
|
'WHERE valid')
|
|
res = cursor.fetchall()
|
|
if not res:
|
|
return []
|
|
return [x[0] for x in res]
|
|
|
|
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()
|
|
LimsTypification = Pool().get('lims.typification')
|
|
|
|
if not self.product_type:
|
|
return []
|
|
|
|
cursor.execute('SELECT DISTINCT(matrix) '
|
|
'FROM "' + LimsTypification._table + '" '
|
|
'WHERE product_type = %s '
|
|
'AND valid',
|
|
(self.product_type.id,))
|
|
res = cursor.fetchall()
|
|
if not res:
|
|
return []
|
|
return [x[0] for x in res]
|
|
|
|
def get_product_type_matrix_readonly(self, name=None):
|
|
pool = Pool()
|
|
LimsService = pool.get('lims.service')
|
|
if LimsService.search_count([('sample', '=', self.id)]) != 0:
|
|
return True
|
|
return False
|
|
|
|
@classmethod
|
|
def check_delete(cls, samples):
|
|
for sample in samples:
|
|
if sample.entry and sample.entry.state != 'draft':
|
|
cls.raise_user_error('delete_sample', (sample.rec_name,))
|
|
|
|
@classmethod
|
|
def delete(cls, samples):
|
|
cls.check_delete(samples)
|
|
super(LimsSample, cls).delete(samples)
|
|
|
|
@staticmethod
|
|
def default_entry_view():
|
|
if (Transaction().context.get('entry') > 0):
|
|
return Transaction().context.get('entry')
|
|
return None
|
|
|
|
@fields.depends('entry')
|
|
def on_change_with_entry_view(self, name=None):
|
|
if self.entry:
|
|
return self.entry.id
|
|
return None
|
|
|
|
@staticmethod
|
|
def default_party():
|
|
if (Transaction().context.get('party') > 0):
|
|
return Transaction().context.get('party')
|
|
return None
|
|
|
|
@staticmethod
|
|
def default_zone():
|
|
Party = Pool().get('party.party')
|
|
|
|
if (Transaction().context.get('party') > 0):
|
|
party = Party(Transaction().context.get('party'))
|
|
if party.entry_zone:
|
|
return party.entry_zone.id
|
|
|
|
@fields.depends('entry')
|
|
def on_change_with_party(self, name=None):
|
|
if self.entry:
|
|
result = self.get_entry_field((self,), ('party',))
|
|
return result['party'][self.id]
|
|
return None
|
|
|
|
@classmethod
|
|
def get_views_field(cls, samples, names):
|
|
result = {}
|
|
for name in names:
|
|
field_name = name[:-5]
|
|
result[name] = {}
|
|
for s in samples:
|
|
field = getattr(s, field_name, None)
|
|
result[name][s.id] = field.id if field else None
|
|
return result
|
|
|
|
@classmethod
|
|
def search_views_field(cls, name, clause):
|
|
return [(name[:-5],) + tuple(clause[1:])]
|
|
|
|
@classmethod
|
|
def get_entry_field(cls, samples, names):
|
|
result = {}
|
|
for name in names:
|
|
result[name] = {}
|
|
for s in samples:
|
|
field = getattr(s.entry, name, None)
|
|
result[name][s.id] = field.id if field else None
|
|
return result
|
|
|
|
@classmethod
|
|
def search_entry_field(cls, name, clause):
|
|
return [('entry.' + name,) + tuple(clause[1:])]
|
|
|
|
@staticmethod
|
|
def order_product_type_view(tables):
|
|
ProductType = Pool().get('lims.product.type')
|
|
field = ProductType._fields['id']
|
|
table, _ = tables[None]
|
|
product_type_tables = tables.get('product_type')
|
|
if product_type_tables is None:
|
|
product_type = ProductType.__table__()
|
|
product_type_tables = {
|
|
None: (product_type, product_type.id == table.product_type),
|
|
}
|
|
tables['product_type'] = product_type_tables
|
|
return field.convert_order('id', product_type_tables, ProductType)
|
|
|
|
@staticmethod
|
|
def order_matrix_view(tables):
|
|
Matrix = Pool().get('lims.matrix')
|
|
field = Matrix._fields['id']
|
|
table, _ = tables[None]
|
|
matrix_tables = tables.get('matrix')
|
|
if matrix_tables is None:
|
|
matrix = Matrix.__table__()
|
|
matrix_tables = {
|
|
None: (matrix, matrix.id == table.matrix),
|
|
}
|
|
tables['matrix'] = matrix_tables
|
|
return field.convert_order('id', matrix_tables, Matrix)
|
|
|
|
def get_confirmed(self, name=None):
|
|
if not self.fractions:
|
|
return False
|
|
for fraction in self.fractions:
|
|
if not fraction.confirmed:
|
|
return False
|
|
return True
|
|
|
|
@classmethod
|
|
def view_attributes(cls):
|
|
return [('/tree', 'colors',
|
|
If(Bool(Eval('has_results_report')), 'blue',
|
|
If(Bool(Eval('confirmed')), 'black', 'red')))]
|
|
|
|
@classmethod
|
|
def order_create_date2(cls, tables):
|
|
return cls.create_date.convert_order('create_date', tables, cls)
|
|
|
|
@staticmethod
|
|
def order_party(tables):
|
|
Entry = Pool().get('lims.entry')
|
|
field = Entry._fields['party']
|
|
table, _ = tables[None]
|
|
entry_tables = tables.get('entry')
|
|
if entry_tables is None:
|
|
entry = Entry.__table__()
|
|
entry_tables = {
|
|
None: (entry, entry.id == table.entry),
|
|
}
|
|
tables['entry'] = entry_tables
|
|
return field.convert_order('party', entry_tables, Entry)
|
|
|
|
@classmethod
|
|
def get_has_results_report(cls, samples, names):
|
|
cursor = Transaction().connection.cursor()
|
|
pool = Pool()
|
|
LimsFraction = pool.get('lims.fraction')
|
|
LimsService = pool.get('lims.service')
|
|
LimsNotebookLine = pool.get('lims.notebook.line')
|
|
|
|
result = {}
|
|
for name in names:
|
|
result[name] = {}
|
|
for s in samples:
|
|
cursor.execute('SELECT f.sample '
|
|
'FROM "' + LimsFraction._table + '" f '
|
|
'INNER JOIN "' + LimsService._table + '" s '
|
|
'ON f.id = s.fraction '
|
|
'INNER JOIN "' + LimsNotebookLine._table + '" nl '
|
|
'ON s.id = nl.service '
|
|
'WHERE f.sample = %s '
|
|
'AND nl.results_report IS NOT NULL',
|
|
(s.id,))
|
|
value = False
|
|
if cursor.fetchone():
|
|
value = True
|
|
result[name][s.id] = value
|
|
return result
|
|
|
|
|
|
class LimsNotebook(ModelSQL, ModelView):
|
|
'Laboratory Notebook'
|
|
__name__ = 'lims.notebook'
|
|
_rec_name = 'fraction'
|
|
|
|
fraction = fields.Many2One('lims.fraction', 'Fraction', required=True,
|
|
readonly=True, ondelete='CASCADE', select=True)
|
|
lines = fields.One2Many('lims.notebook.line', 'notebook', 'Lines')
|
|
product_type = fields.Function(fields.Many2One('lims.product.type',
|
|
'Product type'), 'get_sample_field', searcher='search_sample_field')
|
|
matrix = fields.Function(fields.Many2One('lims.matrix', 'Matrix'),
|
|
'get_sample_field', searcher='search_sample_field')
|
|
party = fields.Function(fields.Many2One('party.party', 'Party'),
|
|
'get_sample_field', searcher='search_sample_field')
|
|
party_code = fields.Function(fields.Char('Party'), 'get_party_code',
|
|
searcher='search_party_code')
|
|
label = fields.Function(fields.Char('Label'), 'get_sample_field',
|
|
searcher='search_sample_field')
|
|
date = fields.Function(fields.DateTime('Date'), 'get_sample_field',
|
|
searcher='search_sample_field')
|
|
date2 = fields.Function(fields.Date('Date'), 'get_sample_field',
|
|
searcher='search_sample_field')
|
|
fraction_type = fields.Function(fields.Many2One('lims.fraction.type',
|
|
'Fraction type'), 'get_fraction_field',
|
|
searcher='search_fraction_field')
|
|
fraction_comments = fields.Function(fields.Text('Fraction Comments'),
|
|
'get_fraction_field')
|
|
shared = fields.Function(fields.Boolean('Shared'), 'get_fraction_field',
|
|
searcher='search_fraction_field')
|
|
current_location = fields.Function(fields.Many2One('stock.location',
|
|
'Current Location'), 'get_current_location',
|
|
searcher='search_current_location')
|
|
divided_report = fields.Function(fields.Boolean('Divided report'),
|
|
'get_divided_report')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(LimsNotebook, cls).__setup__()
|
|
cls._order.insert(0, ('fraction', 'DESC'))
|
|
|
|
def get_rec_name(self, name):
|
|
if self.fraction:
|
|
return self.fraction.rec_name
|
|
|
|
@classmethod
|
|
def search_rec_name(cls, name, clause):
|
|
return [('fraction',) + tuple(clause[1:])]
|
|
|
|
@classmethod
|
|
def get_sample_field(cls, notebooks, names):
|
|
result = {}
|
|
for name in names:
|
|
result[name] = {}
|
|
if name in ('label', 'date', 'date2'):
|
|
for n in notebooks:
|
|
result[name][n.id] = getattr(n.fraction.sample, name, None)
|
|
else:
|
|
for n in notebooks:
|
|
field = getattr(n.fraction.sample, name, None)
|
|
result[name][n.id] = field.id if field else None
|
|
return result
|
|
|
|
@classmethod
|
|
def search_sample_field(cls, name, clause):
|
|
return [('fraction.sample.' + name,) + tuple(clause[1:])]
|
|
|
|
@classmethod
|
|
def get_party_code(cls, notebooks, name):
|
|
result = {}
|
|
for n in notebooks:
|
|
result[n.id] = n.party.code
|
|
return result
|
|
|
|
@classmethod
|
|
def search_party_code(cls, name, clause):
|
|
return [('fraction.sample.entry.party.code',) + tuple(clause[1:])]
|
|
|
|
@classmethod
|
|
def get_fraction_field(cls, notebooks, names):
|
|
result = {}
|
|
for name in names:
|
|
result[name] = {}
|
|
if name == 'fraction_type':
|
|
for n in notebooks:
|
|
field = getattr(n.fraction, 'type', None)
|
|
result[name][n.id] = field.id if field else None
|
|
elif name == 'fraction_comments':
|
|
for n in notebooks:
|
|
result[name][n.id] = getattr(n.fraction, 'comments', None)
|
|
else:
|
|
for n in notebooks:
|
|
result[name][n.id] = getattr(n.fraction, name, None)
|
|
return result
|
|
|
|
@classmethod
|
|
def search_fraction_field(cls, name, clause):
|
|
if name == 'fraction_type':
|
|
name = 'type'
|
|
return [('fraction.' + name,) + tuple(clause[1:])]
|
|
|
|
def get_divided_report(self, name):
|
|
if not self.fraction or not self.fraction.services:
|
|
return False
|
|
for s in self.fraction.services:
|
|
if s.divide:
|
|
return True
|
|
return False
|
|
|
|
@classmethod
|
|
def get_current_location(cls, notebooks, name=None):
|
|
cursor = Transaction().connection.cursor()
|
|
Move = Pool().get('stock.move')
|
|
|
|
result = {}
|
|
for n in notebooks:
|
|
cursor.execute('SELECT to_location '
|
|
'FROM "' + Move._table + '" '
|
|
'WHERE fraction = %s '
|
|
'AND state IN (\'assigned\', \'done\') '
|
|
'ORDER BY effective_date DESC, id DESC '
|
|
'LIMIT 1', (n.fraction.id,))
|
|
location = cursor.fetchone()
|
|
result[n.id] = location[0] if location else None
|
|
return result
|
|
|
|
@classmethod
|
|
def search_current_location(cls, name, domain=None):
|
|
|
|
def _search_current_location_eval_domain(line, domain):
|
|
operator_funcs = {
|
|
'=': operator.eq,
|
|
'>=': operator.ge,
|
|
'>': operator.gt,
|
|
'<=': operator.le,
|
|
'<': operator.lt,
|
|
'!=': operator.ne,
|
|
'in': lambda v, l: v in l,
|
|
'not in': lambda v, l: v not in l,
|
|
'ilike': lambda v, l: False,
|
|
}
|
|
field, op, operand = domain
|
|
value = line.get(field)
|
|
return operator_funcs[op](value, operand)
|
|
|
|
if domain and domain[1] == 'ilike':
|
|
Location = Pool().get('stock.location')
|
|
locations = Location.search([
|
|
('code', '=', domain[2]),
|
|
], order=[])
|
|
if not locations:
|
|
locations = Location.search([
|
|
('name',) + tuple(domain[1:]),
|
|
], order=[])
|
|
if not locations:
|
|
return []
|
|
domain = ('current_location', 'in', [l.id for l in locations])
|
|
|
|
all_notebooks = cls.search([])
|
|
current_locations = cls.get_current_location(all_notebooks).iteritems()
|
|
|
|
processed_lines = [{
|
|
'fraction': fraction,
|
|
'current_location': location,
|
|
} for fraction, location in current_locations]
|
|
|
|
record_ids = [line['notebook'] for line in processed_lines
|
|
if _search_current_location_eval_domain(line, domain)]
|
|
return [('id', 'in', record_ids)]
|
|
|
|
@classmethod
|
|
def view_attributes(cls):
|
|
return [
|
|
('/tree', 'colors',
|
|
If(Len(Eval('fraction_comments')) > 0, 'blue', 'black')),
|
|
]
|
|
|
|
|
|
class LimsNotebookLine(ModelSQL, ModelView):
|
|
'Laboratory Notebook Line'
|
|
__name__ = 'lims.notebook.line'
|
|
_rec_name = 'analysis'
|
|
|
|
notebook = fields.Many2One('lims.notebook', 'Laboratory notebook',
|
|
ondelete='CASCADE', select=True, required=True)
|
|
analysis_detail = fields.Many2One('lims.entry.detail.analysis',
|
|
'Analysis detail', select=True)
|
|
service = fields.Many2One('lims.service', 'Service', readonly=True,
|
|
ondelete='CASCADE', select=True)
|
|
analysis = fields.Many2One('lims.analysis', 'Analysis', required=True,
|
|
readonly=True)
|
|
repetition = fields.Integer('Repetition', readonly=True)
|
|
start_date = fields.Date('Start date', readonly=True)
|
|
end_date = fields.Date('End date', states={
|
|
'readonly': Or(~Bool(Eval('start_date')), Bool(Eval('accepted'))),
|
|
}, depends=['start_date', 'accepted'])
|
|
laboratory = fields.Many2One('lims.laboratory', 'Laboratory',
|
|
readonly=True)
|
|
method = fields.Many2One('lims.lab.method', 'Method',
|
|
required=True, domain=['OR', ('id', '=', Eval('method')),
|
|
('id', 'in', Eval('method_domain'))],
|
|
depends=['method_domain'])
|
|
method_view = fields.Function(fields.Many2One('lims.lab.method',
|
|
'Method'), 'get_views_field')
|
|
method_domain = fields.Function(fields.Many2Many('lims.lab.method',
|
|
None, None, 'Method domain'),
|
|
'on_change_with_method_domain')
|
|
device = fields.Many2One('lims.lab.device', 'Device',
|
|
domain=['OR', ('id', '=', Eval('device')),
|
|
('id', 'in', Eval('device_domain'))],
|
|
depends=['device_domain'])
|
|
device_view = fields.Function(fields.Many2One('lims.lab.device',
|
|
'Device'), 'get_views_field')
|
|
device_domain = fields.Function(fields.Many2Many('lims.lab.device',
|
|
None, None, 'Device domain'), 'on_change_with_device_domain')
|
|
analysis_origin = fields.Char('Analysis origin', readonly=True)
|
|
initial_concentration = fields.Char('Initial concentration')
|
|
final_concentration = fields.Char('Final concentration')
|
|
laboratory_professionals = fields.Many2Many(
|
|
'lims.notebook.line-laboratory.professional', 'notebook_line',
|
|
'professional', 'Preparation professionals')
|
|
initial_unit = fields.Many2One('product.uom', 'Initial unit',
|
|
domain=[('category.lims_only_available', '=', True)],
|
|
states={'readonly': Bool(Eval('accepted'))}, depends=['accepted'])
|
|
final_unit = fields.Many2One('product.uom', 'Final unit',
|
|
domain=[('category.lims_only_available', '=', True)],
|
|
states={'readonly': Bool(Eval('accepted'))}, depends=['accepted'])
|
|
result_modifier = fields.Selection([
|
|
('eq', '='),
|
|
('low', '<'),
|
|
('nd', 'nd'),
|
|
('na', 'na'),
|
|
('pos', 'Positive'),
|
|
('neg', 'Negative'),
|
|
('ni', 'ni'),
|
|
('abs', 'Absence'),
|
|
('pre', 'Presence'),
|
|
], 'Result modifier', sort=False,
|
|
states={'readonly': Bool(Eval('accepted'))}, depends=['accepted'])
|
|
result_modifier_string = result_modifier.translated('result_modifier')
|
|
converted_result_modifier = fields.Selection([
|
|
('eq', '='),
|
|
('low', '<'),
|
|
('nd', 'nd'),
|
|
('pos', 'Positive'),
|
|
('neg', 'Negative'),
|
|
('ni', 'ni'),
|
|
], 'Converted result modifier', sort=False,
|
|
states={'readonly': Bool(Eval('accepted'))}, depends=['accepted'])
|
|
converted_result_modifier_string = converted_result_modifier.translated(
|
|
'converted_result_modifier')
|
|
result = fields.Char('Result',
|
|
states={'readonly': Bool(Eval('accepted'))}, depends=['accepted'])
|
|
converted_result = fields.Char('Converted result',
|
|
states={'readonly': Bool(Eval('accepted'))}, depends=['accepted'])
|
|
detection_limit = fields.Char('Detection limit',
|
|
states={'readonly': Bool(Eval('accepted'))}, depends=['accepted'])
|
|
quantification_limit = fields.Char('Quantification limit',
|
|
states={'readonly': Bool(Eval('accepted'))}, depends=['accepted'])
|
|
check_result_limits = fields.Function(fields.Boolean(
|
|
'Validate limits directly on the result'), 'get_typification_field')
|
|
chromatogram = fields.Char('Chromatogram')
|
|
professionals = fields.One2Many('lims.notebook.line.professional',
|
|
'notebook_line', 'Analytic professionals')
|
|
comments = fields.Text('Entry comments')
|
|
theoretical_concentration = fields.Char('Theoretical concentration')
|
|
concentration_level = fields.Many2One('lims.concentration.level',
|
|
'Concentration level')
|
|
decimals = fields.Integer('Decimals')
|
|
backup = fields.Char('Backup')
|
|
reference = fields.Char('Reference')
|
|
literal_result = fields.Char('Literal result', translate=True,
|
|
states={'readonly': Bool(Eval('accepted'))}, depends=['accepted'])
|
|
rm_correction_formula = fields.Char('RM Correction Formula')
|
|
report = fields.Boolean('Report')
|
|
uncertainty = fields.Char('Uncertainty')
|
|
verification = fields.Char('Verification')
|
|
analysis_order = fields.Function(fields.Integer('Order'),
|
|
'get_analysis_order')
|
|
dilution_factor = fields.Float('Dilution factor')
|
|
accepted = fields.Boolean('Accepted')
|
|
acceptance_date = fields.DateTime('Acceptance date',
|
|
states={'readonly': True})
|
|
not_accepted_message = fields.Text('Message', readonly=True,
|
|
states={'invisible': Not(Bool(Eval('not_accepted_message')))})
|
|
annulled = fields.Boolean('Annulled', states={'readonly': True})
|
|
annulment_date = fields.DateTime('Annulment date',
|
|
states={'readonly': True})
|
|
results_report = fields.Many2One('lims.results_report', 'Results Report',
|
|
readonly=True)
|
|
planification = fields.Many2One('lims.planification', 'Planification',
|
|
readonly=True)
|
|
urgent = fields.Function(fields.Boolean('Urgent'), 'get_service_field',
|
|
searcher='search_service_field')
|
|
priority = fields.Function(fields.Integer('Priority'),
|
|
'get_service_field', searcher='search_service_field')
|
|
report_date = fields.Function(fields.Date('Date agreed for result'),
|
|
'get_service_field', searcher='search_service_field')
|
|
fraction = fields.Function(fields.Many2One('lims.fraction', 'Fraction'),
|
|
'get_service_field', searcher='search_service_field')
|
|
fraction_type = fields.Function(fields.Many2One('lims.fraction.type',
|
|
'Fraction type'), 'get_fraction_field',
|
|
searcher='search_fraction_field')
|
|
party = fields.Function(fields.Many2One('party.party', 'Party'),
|
|
'get_fraction_field', searcher='search_fraction_field')
|
|
product_type = fields.Function(fields.Many2One('lims.product.type',
|
|
'Product type'), 'get_sample_field', searcher='search_sample_field')
|
|
matrix = fields.Function(fields.Many2One('lims.matrix', 'Matrix'),
|
|
'get_sample_field', searcher='search_sample_field')
|
|
label = fields.Function(fields.Char('Label'), 'get_sample_field',
|
|
searcher='search_sample_field')
|
|
date = fields.Function(fields.DateTime('Date'), 'get_sample_field',
|
|
searcher='search_sample_field')
|
|
date2 = fields.Function(fields.Date('Date'), 'get_sample_field',
|
|
searcher='search_sample_field')
|
|
report_type = fields.Function(fields.Char('Report type'),
|
|
'get_typification_field', searcher='search_typification_field')
|
|
report_result_type = fields.Function(fields.Char('Result type'),
|
|
'get_typification_field', searcher='search_typification_field')
|
|
results_estimated_waiting = fields.Integer(
|
|
'Estimated number of days for results', states={'readonly': True})
|
|
results_estimated_date = fields.Function(fields.Date(
|
|
'Estimated date of result'), 'get_results_estimated_date')
|
|
department = fields.Many2One('company.department', 'Department',
|
|
readonly=True)
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(LimsNotebookLine, cls).__setup__()
|
|
cls._order.insert(0, ('analysis_order', 'ASC'))
|
|
cls._order.insert(1, ('repetition', 'ASC'))
|
|
cls._error_messages.update({
|
|
'end_date': 'The end date cannot be lower than start date',
|
|
'end_date_wrong': ('End date should not be greater than the '
|
|
'current date'),
|
|
'accepted': 'The analysis "%s" is already accepted',
|
|
'not_accepted_1': 'The analysis is not reported',
|
|
'not_accepted_2': 'The analysis is annulled',
|
|
'not_accepted_3': 'The analysis has not End date',
|
|
'not_accepted_4': 'The analysis has not Result / Converted result',
|
|
'not_accepted_5': 'The Converted result modifier is invalid',
|
|
'not_accepted_6': 'The Result modifier is invalid',
|
|
'not_accepted_7': ('The Converted result / Converted result '
|
|
'modifier is invalid'),
|
|
'accepted_1': 'The analysis is already reported (%s)',
|
|
})
|
|
|
|
@staticmethod
|
|
def default_repetition():
|
|
return 0
|
|
|
|
@staticmethod
|
|
def default_result_modifier():
|
|
return 'eq'
|
|
|
|
@staticmethod
|
|
def default_converted_result_modifier():
|
|
return 'eq'
|
|
|
|
@staticmethod
|
|
def default_decimals():
|
|
return 2
|
|
|
|
@staticmethod
|
|
def default_report():
|
|
return True
|
|
|
|
@staticmethod
|
|
def default_dilution_factor():
|
|
return 1.0
|
|
|
|
@staticmethod
|
|
def default_accepted():
|
|
return False
|
|
|
|
@staticmethod
|
|
def default_annulled():
|
|
return False
|
|
|
|
@classmethod
|
|
def write(cls, *args):
|
|
super(LimsNotebookLine, cls).write(*args)
|
|
actions = iter(args)
|
|
for lines, vals in zip(actions, actions):
|
|
if vals.get('not_accepted_message'):
|
|
cls.write(lines, {
|
|
'not_accepted_message': None,
|
|
})
|
|
if 'accepted' in vals:
|
|
cls.update_detail_analysis(lines, vals['accepted'])
|
|
|
|
@staticmethod
|
|
def update_detail_analysis(lines, accepted):
|
|
LimsEntryDetailAnalysis = Pool().get('lims.entry.detail.analysis')
|
|
|
|
details = [nl.analysis_detail.id for nl in lines]
|
|
if accepted:
|
|
analysis_details = LimsEntryDetailAnalysis.search([
|
|
('id', 'in', details),
|
|
])
|
|
if analysis_details:
|
|
LimsEntryDetailAnalysis.write(analysis_details, {
|
|
'state': 'done',
|
|
})
|
|
else:
|
|
analysis_details = LimsEntryDetailAnalysis.search([
|
|
('id', 'in', details),
|
|
('analysis.behavior', '!=', 'internal_relation'),
|
|
])
|
|
if analysis_details:
|
|
LimsEntryDetailAnalysis.write(analysis_details, {
|
|
'state': 'planned',
|
|
})
|
|
analysis_details = LimsEntryDetailAnalysis.search([
|
|
('id', 'in', details),
|
|
('analysis.behavior', '=', 'internal_relation'),
|
|
])
|
|
if analysis_details:
|
|
LimsEntryDetailAnalysis.write(analysis_details, {
|
|
'state': 'unplanned',
|
|
})
|
|
|
|
@classmethod
|
|
def validate(cls, notebook_lines):
|
|
super(LimsNotebookLine, cls).validate(notebook_lines)
|
|
for line in notebook_lines:
|
|
line.check_end_date()
|
|
line.check_accepted()
|
|
|
|
def check_end_date(self):
|
|
if self.end_date:
|
|
if not self.start_date or self.end_date < self.start_date:
|
|
self.raise_user_error('end_date')
|
|
if not self.start_date or self.end_date > datetime.now().date():
|
|
self.raise_user_error('end_date_wrong')
|
|
|
|
def check_accepted(self):
|
|
if self.accepted:
|
|
accepted_lines = self.search([
|
|
('notebook', '=', self.notebook.id),
|
|
('analysis', '=', self.analysis.id),
|
|
('accepted', '=', True),
|
|
('id', '!=', self.id),
|
|
])
|
|
if accepted_lines:
|
|
self.raise_user_error('accepted', (self.analysis.rec_name,))
|
|
|
|
@classmethod
|
|
def get_analysis_order(cls, notebook_lines, name):
|
|
result = {}
|
|
for nl in notebook_lines:
|
|
analysis = getattr(nl, 'analysis', None)
|
|
result[nl.id] = analysis.order if analysis else None
|
|
return result
|
|
|
|
@staticmethod
|
|
def order_analysis_order(tables):
|
|
LimsAnalysis = Pool().get('lims.analysis')
|
|
field = LimsAnalysis._fields['order']
|
|
table, _ = tables[None]
|
|
analysis_tables = tables.get('analysis')
|
|
if analysis_tables is None:
|
|
analysis = LimsAnalysis.__table__()
|
|
analysis_tables = {
|
|
None: (analysis, analysis.id == table.analysis),
|
|
}
|
|
tables['analysis'] = analysis_tables
|
|
return field.convert_order('order', analysis_tables, LimsAnalysis)
|
|
|
|
@classmethod
|
|
def get_views_field(cls, notebook_lines, names):
|
|
result = {}
|
|
for name in names:
|
|
field_name = name[:-5]
|
|
result[name] = {}
|
|
for nl in notebook_lines:
|
|
field = getattr(nl, field_name, None)
|
|
result[name][nl.id] = field.id if field else None
|
|
return result
|
|
|
|
@classmethod
|
|
def get_service_field(cls, notebook_lines, names):
|
|
result = {}
|
|
for name in names:
|
|
result[name] = {}
|
|
if name == 'fraction':
|
|
for nl in notebook_lines:
|
|
field = getattr(nl.service, name, None)
|
|
result[name][nl.id] = field.id if field else None
|
|
else:
|
|
for nl in notebook_lines:
|
|
result[name][nl.id] = getattr(nl.service, name, None)
|
|
return result
|
|
|
|
@classmethod
|
|
def search_service_field(cls, name, clause):
|
|
return [('service.' + name,) + tuple(clause[1:])]
|
|
|
|
@classmethod
|
|
def get_fraction_field(cls, notebook_lines, names):
|
|
result = {}
|
|
for name in names:
|
|
result[name] = {}
|
|
if name == 'fraction_type':
|
|
for nl in notebook_lines:
|
|
fraction = getattr(nl.service, 'fraction', None)
|
|
if fraction:
|
|
field = getattr(fraction, 'type', None)
|
|
result[name][nl.id] = field.id if field else None
|
|
else:
|
|
result[name][nl.id] = None
|
|
else:
|
|
for nl in notebook_lines:
|
|
fraction = getattr(nl.service, 'fraction', None)
|
|
if fraction:
|
|
field = getattr(fraction, name, None)
|
|
result[name][nl.id] = field.id if field else None
|
|
else:
|
|
result[name][nl.id] = None
|
|
return result
|
|
|
|
@classmethod
|
|
def search_fraction_field(cls, name, clause):
|
|
if name == 'fraction_type':
|
|
name = 'type'
|
|
return [('service.fraction.' + name,) + tuple(clause[1:])]
|
|
|
|
@classmethod
|
|
def get_sample_field(cls, notebook_lines, names):
|
|
result = {}
|
|
for name in names:
|
|
result[name] = {}
|
|
for nl in notebook_lines:
|
|
result[name][nl.id] = None
|
|
fraction = getattr(nl.service, 'fraction', None)
|
|
if fraction:
|
|
sample = getattr(fraction, 'sample', None)
|
|
if sample:
|
|
field = getattr(fraction, name, None)
|
|
if name in ('label', 'date', 'date2'):
|
|
result[name][nl.id] = field
|
|
else:
|
|
result[name][nl.id] = field.id if field else None
|
|
return result
|
|
|
|
@classmethod
|
|
def search_sample_field(cls, name, clause):
|
|
return [('service.fraction.sample.' + name,) + tuple(clause[1:])]
|
|
|
|
def get_rec_name(self, name):
|
|
if self.analysis:
|
|
return self.analysis.rec_name
|
|
|
|
@classmethod
|
|
def search_rec_name(cls, name, clause):
|
|
return [('analysis',) + tuple(clause[1:])]
|
|
|
|
@classmethod
|
|
def view_attributes(cls):
|
|
return [('/tree', 'colors',
|
|
If(Bool(Eval('report_date')), 'red', 'black'))]
|
|
|
|
@classmethod
|
|
def fields_view_get(cls, view_id=None, view_type='form'):
|
|
pool = Pool()
|
|
User = pool.get('res.user')
|
|
Config = pool.get('lims.configuration')
|
|
UiView = pool.get('ir.ui.view')
|
|
|
|
result = super(LimsNotebookLine, cls).fields_view_get(view_id=view_id,
|
|
view_type=view_type)
|
|
|
|
# All Notebook Lines view
|
|
if view_id and UiView(view_id).name == 'notebook_line_all_list':
|
|
return result
|
|
|
|
notebook_view = User(Transaction().user).notebook_view
|
|
if not notebook_view:
|
|
notebook_view = Config(1).default_notebook_view
|
|
if not notebook_view:
|
|
return result
|
|
|
|
if view_type == 'tree':
|
|
xml = '<?xml version="1.0"?>\n' \
|
|
'<tree editable="bottom">\n'
|
|
fields = set()
|
|
for column in notebook_view.columns:
|
|
fields.add(column.field.name)
|
|
attrs = []
|
|
if column.field.name in ('acceptance_date', 'annulment_date'):
|
|
attrs.append('widget="date"')
|
|
xml += ('<field name="%s" %s/>\n'
|
|
% (column.field.name, ' '.join(attrs)))
|
|
for depend in getattr(cls, column.field.name).depends:
|
|
fields.add(depend)
|
|
for field in ('report_date', 'result', 'converted_result',
|
|
'result_modifier', 'converted_result_modifier',
|
|
'literal_result', 'backup', 'verification', 'uncertainty',
|
|
'accepted', 'acceptance_date', 'end_date', 'report',
|
|
'annulled', 'annulment_date'):
|
|
fields.add(field)
|
|
xml += '</tree>'
|
|
result['arch'] = xml
|
|
result['fields'] = cls.fields_get(fields_names=list(fields))
|
|
return result
|
|
|
|
@fields.depends('result', 'converted_result', 'converted_result_modifier',
|
|
'backup', 'verification', 'uncertainty', 'end_date')
|
|
def on_change_result(self):
|
|
self.converted_result = None
|
|
self.converted_result_modifier = 'eq'
|
|
self.backup = None
|
|
self.verification = None
|
|
self.uncertainty = None
|
|
self.end_date = None
|
|
|
|
@fields.depends('accepted', 'report', 'annulled', 'result',
|
|
'converted_result', 'literal_result', 'result_modifier',
|
|
'converted_result_modifier', 'end_date', 'acceptance_date')
|
|
def on_change_accepted(self):
|
|
self.not_accepted_message = ''
|
|
if self.accepted:
|
|
if not self.report:
|
|
self.accepted = False
|
|
self.not_accepted_message = self.raise_user_error(
|
|
'not_accepted_1', raise_exception=False)
|
|
elif self.annulled:
|
|
self.accepted = False
|
|
self.not_accepted_message = self.raise_user_error(
|
|
'not_accepted_2', raise_exception=False)
|
|
elif not self.end_date:
|
|
self.accepted = False
|
|
self.not_accepted_message = self.raise_user_error(
|
|
'not_accepted_3', raise_exception=False)
|
|
elif not (self.result or self.converted_result
|
|
or self.literal_result
|
|
or self.result_modifier in
|
|
('nd', 'pos', 'neg', 'ni', 'abs', 'pre')
|
|
or self.converted_result_modifier in
|
|
('nd', 'pos', 'neg', 'ni', 'abs', 'pre')):
|
|
self.accepted = False
|
|
self.not_accepted_message = self.raise_user_error(
|
|
'not_accepted_4', raise_exception=False)
|
|
else:
|
|
if (self.converted_result and self.converted_result_modifier
|
|
not in ('ni', 'eq', 'low')):
|
|
self.accepted = False
|
|
self.not_accepted_message = self.raise_user_error(
|
|
'not_accepted_5', raise_exception=False)
|
|
elif (self.result and self.result_modifier
|
|
not in ('ni', 'eq', 'low')):
|
|
self.accepted = False
|
|
self.not_accepted_message = self.raise_user_error(
|
|
'not_accepted_6', raise_exception=False)
|
|
elif (self.result_modifier == 'ni' and
|
|
not self.literal_result and
|
|
(not self.converted_result_modifier or
|
|
not self.converted_result) and
|
|
self.converted_result_modifier != 'nd'):
|
|
self.accepted = False
|
|
self.not_accepted_message = self.raise_user_error(
|
|
'not_accepted_7', raise_exception=False)
|
|
else:
|
|
self.acceptance_date = datetime.now()
|
|
else:
|
|
LimsResultsReportVersionDetailLine = Pool().get(
|
|
'lims.results_report.version.detail.line')
|
|
report_lines = LimsResultsReportVersionDetailLine.search([
|
|
('notebook_line', '=', self.id),
|
|
('report_version_detail.state', '!=', 'annulled'),
|
|
])
|
|
if report_lines:
|
|
self.accepted = True
|
|
report_detail = report_lines[0].report_version_detail
|
|
self.not_accepted_message = self.raise_user_error('accepted_1',
|
|
(report_detail.report_version.results_report.number,),
|
|
raise_exception=False)
|
|
else:
|
|
self.acceptance_date = None
|
|
|
|
@fields.depends('result_modifier', 'annulled', 'annulment_date', 'report')
|
|
def on_change_result_modifier(self):
|
|
if self.result_modifier == 'na' and not self.annulled:
|
|
self.annulled = True
|
|
self.annulment_date = datetime.now()
|
|
self.report = False
|
|
elif self.result_modifier != 'na' and self.annulled:
|
|
self.annulled = False
|
|
self.annulment_date = None
|
|
self.report = True
|
|
|
|
@classmethod
|
|
def get_typification_field(cls, notebook_lines, names):
|
|
LimsTypification = Pool().get('lims.typification')
|
|
result = dict((name, {}) for name in names)
|
|
for nl in notebook_lines:
|
|
typifications = LimsTypification.search([
|
|
('product_type', '=', nl.notebook.product_type.id),
|
|
('matrix', '=', nl.notebook.matrix.id),
|
|
('analysis', '=', nl.analysis.id),
|
|
('method', '=', nl.method.id),
|
|
('valid', '=', True),
|
|
])
|
|
typification = (typifications[0] if len(typifications) == 1
|
|
else None)
|
|
for name in names:
|
|
if typification:
|
|
result[name][nl.id] = getattr(typification, name, None)
|
|
else:
|
|
if name == 'report_type':
|
|
result[name][nl.id] = 'normal'
|
|
elif name == 'report_result_type':
|
|
result[name][nl.id] = 'result'
|
|
elif name == 'check_result_limits':
|
|
result[name][nl.id] = False
|
|
else:
|
|
result[name][nl.id] = None
|
|
return result
|
|
|
|
@classmethod
|
|
def search_typification_field(cls, name, clause):
|
|
cursor = Transaction().connection.cursor()
|
|
pool = Pool()
|
|
Notebook = pool.get('lims.notebook')
|
|
Fraction = pool.get('lims.fraction')
|
|
LimsSample = pool.get('lims.sample')
|
|
LimsTypification = pool.get('lims.typification')
|
|
|
|
operator_ = clause[1:2][0]
|
|
cursor.execute('SELECT nl.id '
|
|
'FROM "' + cls._table + '" nl '
|
|
'INNER JOIN "' + Notebook._table + '" n '
|
|
'ON nl.notebook = n.id '
|
|
'INNER JOIN "' + Fraction._table + '" f '
|
|
'ON n.fraction = f.id '
|
|
'INNER JOIN "' + LimsSample._table + '" s '
|
|
'ON f.sample = s.id '
|
|
'INNER JOIN "' + LimsTypification._table + '" t '
|
|
'ON (nl.analysis = t.analysis AND nl.method = t.method '
|
|
'AND s.product_type = t.product_type AND t.matrix = t.matrix) '
|
|
'WHERE t.valid = TRUE '
|
|
'AND t.' + name + ' ' + operator_ + ' %s',
|
|
clause[2:3])
|
|
return [('id', 'in', [x[0] for x in cursor.fetchall()])]
|
|
|
|
@fields.depends('method')
|
|
def on_change_with_results_estimated_waiting(self, name=None):
|
|
if self.method:
|
|
return self.method.results_estimated_waiting
|
|
return None
|
|
|
|
@classmethod
|
|
def get_results_estimated_date(cls, notebook_lines, name):
|
|
result = {}
|
|
for nl in notebook_lines:
|
|
result[nl.id] = None
|
|
detail = getattr(nl, 'analysis_detail', None)
|
|
if not detail:
|
|
continue
|
|
confirmation_date = getattr(detail, 'confirmation_date', None)
|
|
if not confirmation_date:
|
|
continue
|
|
estimated_waiting = getattr(nl, 'results_estimated_waiting', None)
|
|
if not estimated_waiting:
|
|
continue
|
|
result[nl.id] = cls._get_results_estimated_date(confirmation_date,
|
|
estimated_waiting)
|
|
return result
|
|
|
|
@staticmethod
|
|
def _get_results_estimated_date(confirmation_date, estimated_waiting):
|
|
date = confirmation_date
|
|
number = 0
|
|
while number < estimated_waiting:
|
|
date += timedelta(1)
|
|
if date.weekday() < 5:
|
|
number += 1
|
|
return date
|
|
|
|
@fields.depends('analysis')
|
|
def on_change_with_method_domain(self, name=None):
|
|
methods = []
|
|
if self.analysis and self.analysis.methods:
|
|
methods = [m.id for m in self.analysis.methods]
|
|
return methods
|
|
|
|
@fields.depends('analysis', 'laboratory')
|
|
def on_change_with_device_domain(self, name=None):
|
|
cursor = Transaction().connection.cursor()
|
|
LimsAnalysisDevice = Pool().get('lims.analysis.device')
|
|
|
|
if not self.analysis or not self.laboratory:
|
|
return []
|
|
|
|
cursor.execute('SELECT DISTINCT(device) '
|
|
'FROM "' + LimsAnalysisDevice._table + '" '
|
|
'WHERE analysis = %s '
|
|
'AND laboratory = %s',
|
|
(self.analysis.id, self.laboratory.id))
|
|
res = cursor.fetchall()
|
|
if not res:
|
|
return []
|
|
return [x[0] for x in res]
|
|
|
|
|
|
class LimsNotebookLineAllFields(ModelSQL, ModelView):
|
|
'Laboratory Notebook Line'
|
|
__name__ = 'lims.notebook.line.all_fields'
|
|
|
|
line = fields.Many2One('lims.notebook.line', 'Notebook Line')
|
|
fraction = fields.Many2One('lims.fraction', 'Fraction', readonly=True)
|
|
fraction_type = fields.Many2One('lims.fraction.type', 'Fraction type',
|
|
readonly=True)
|
|
party = fields.Many2One('party.party', 'Party', readonly=True)
|
|
party_code = fields.Char('Party', readonly=True)
|
|
product_type = fields.Many2One('lims.product.type', 'Product type',
|
|
readonly=True)
|
|
matrix = fields.Many2One('lims.matrix', 'Matrix', readonly=True)
|
|
label = fields.Char('Label', readonly=True)
|
|
date = fields.DateTime('Date', readonly=True)
|
|
analysis = fields.Many2One('lims.analysis', 'Analysis', readonly=True)
|
|
repetition = fields.Integer('Repetition', readonly=True)
|
|
start_date = fields.Date('Start date', readonly=True)
|
|
end_date = fields.Date('End date', readonly=True)
|
|
laboratory = fields.Many2One('lims.laboratory', 'Laboratory',
|
|
readonly=True)
|
|
method = fields.Many2One('lims.lab.method', 'Method', readonly=True)
|
|
device = fields.Many2One('lims.lab.device', 'Device', readonly=True)
|
|
service = fields.Many2One('lims.service', 'Service', readonly=True)
|
|
analysis_origin = fields.Char('Analysis origin', readonly=True)
|
|
urgent = fields.Boolean('Urgent', readonly=True)
|
|
priority = fields.Integer('Priority', readonly=True)
|
|
report_date = fields.Date('Date agreed for result', readonly=True)
|
|
initial_concentration = fields.Char('Initial concentration', readonly=True)
|
|
final_concentration = fields.Char('Final concentration', readonly=True)
|
|
laboratory_professionals = fields.Function(fields.Many2Many(
|
|
'lims.laboratory.professional', None, None,
|
|
'Preparation professionals'), 'get_line_field',
|
|
searcher='search_line_field')
|
|
initial_unit = fields.Many2One('product.uom', 'Initial unit',
|
|
readonly=True)
|
|
final_unit = fields.Many2One('product.uom', 'Final unit', readonly=True)
|
|
result_modifier = fields.Selection([
|
|
('eq', '='),
|
|
('low', '<'),
|
|
('nd', 'nd'),
|
|
('na', 'na'),
|
|
('pos', 'Positive'),
|
|
('neg', 'Negative'),
|
|
('ni', 'ni'),
|
|
('abs', 'Absence'),
|
|
('pre', 'Presence'),
|
|
], 'Result modifier', readonly=True)
|
|
converted_result_modifier = fields.Selection([
|
|
('eq', '='),
|
|
('low', '<'),
|
|
('nd', 'nd'),
|
|
('pos', 'Positive'),
|
|
('neg', 'Negative'),
|
|
('ni', 'ni'),
|
|
], 'Converted result modifier', readonly=True)
|
|
result_modifier_string = result_modifier.translated('result_modifier')
|
|
converted_result_modifier_string = converted_result_modifier.translated(
|
|
'converted_result_modifier')
|
|
result = fields.Char('Result', readonly=True)
|
|
converted_result = fields.Char('Converted result', readonly=True)
|
|
detection_limit = fields.Char('Detection limit', readonly=True)
|
|
quantification_limit = fields.Char('Quantification limit', readonly=True)
|
|
chromatogram = fields.Char('Chromatogram', readonly=True)
|
|
professionals = fields.Function(fields.One2Many(
|
|
'lims.notebook.line.professional', None,
|
|
'Analytic professionals'), 'get_line_field',
|
|
searcher='search_line_field')
|
|
comments = fields.Text('Entry comments', readonly=True)
|
|
theoretical_concentration = fields.Char('Theoretical concentration',
|
|
readonly=True)
|
|
concentration_level = fields.Many2One('lims.concentration.level',
|
|
'Concentration level', readonly=True)
|
|
decimals = fields.Integer('Decimals', readonly=True)
|
|
backup = fields.Char('Backup', readonly=True)
|
|
reference = fields.Char('Reference', readonly=True)
|
|
literal_result = fields.Char('Literal result', readonly=True)
|
|
rm_correction_formula = fields.Char('RM Correction Formula', readonly=True)
|
|
report = fields.Boolean('Report', readonly=True)
|
|
uncertainty = fields.Char('Uncertainty', readonly=True)
|
|
verification = fields.Char('Verification', readonly=True)
|
|
dilution_factor = fields.Float('Dilution factor', readonly=True)
|
|
accepted = fields.Boolean('Accepted', readonly=True)
|
|
acceptance_date = fields.DateTime('Acceptance date', readonly=True)
|
|
annulled = fields.Boolean('Annulled', readonly=True)
|
|
annulment_date = fields.DateTime('Annulment date', readonly=True)
|
|
results_report = fields.Many2One('lims.results_report', 'Results Report',
|
|
readonly=True)
|
|
planification = fields.Many2One('lims.planification', 'Planification',
|
|
readonly=True)
|
|
confirmation_date = fields.Date('Confirmation date', readonly=True)
|
|
results_estimated_waiting = fields.Integer(
|
|
'Estimated number of days for results')
|
|
results_estimated_date = fields.Function(fields.Date(
|
|
'Estimated date of result'), 'get_line_field')
|
|
department = fields.Many2One('company.department', 'Department',
|
|
readonly=True)
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(LimsNotebookLineAllFields, cls).__setup__()
|
|
cls._order.insert(0, ('fraction', 'DESC'))
|
|
cls._order.insert(1, ('analysis', 'ASC'))
|
|
cls._order.insert(2, ('repetition', 'ASC'))
|
|
|
|
@staticmethod
|
|
def table_query():
|
|
pool = Pool()
|
|
line = pool.get('lims.notebook.line').__table__()
|
|
detail = pool.get('lims.entry.detail.analysis').__table__()
|
|
service = pool.get('lims.service').__table__()
|
|
fraction = pool.get('lims.fraction').__table__()
|
|
sample = pool.get('lims.sample').__table__()
|
|
entry = pool.get('lims.entry').__table__()
|
|
party = pool.get('party.party').__table__()
|
|
|
|
join1 = Join(line, service)
|
|
join1.condition = join1.right.id == line.service
|
|
join2 = Join(join1, fraction)
|
|
join2.condition = join2.right.id == join1.right.fraction
|
|
join3 = Join(join2, sample)
|
|
join3.condition = join3.right.id == join2.right.sample
|
|
join4 = Join(join3, entry)
|
|
join4.condition = join4.right.id == join3.right.entry
|
|
join5 = Join(join4, party)
|
|
join5.condition = join5.right.id == join4.right.party
|
|
join6 = Join(join5, detail)
|
|
join6.condition = join6.right.id == join1.left.analysis_detail
|
|
|
|
columns = [
|
|
line.id,
|
|
line.create_uid,
|
|
line.create_date,
|
|
line.write_uid,
|
|
line.write_date,
|
|
line.id.as_('line'),
|
|
service.fraction,
|
|
entry.party,
|
|
party.code.as_('party_code'),
|
|
sample.product_type,
|
|
sample.matrix,
|
|
sample.label,
|
|
fraction.type.as_('fraction_type'),
|
|
sample.date,
|
|
line.analysis,
|
|
line.repetition,
|
|
line.start_date,
|
|
line.end_date,
|
|
line.laboratory,
|
|
line.method,
|
|
line.device,
|
|
line.service,
|
|
line.analysis_origin,
|
|
service.urgent,
|
|
service.priority,
|
|
service.report_date,
|
|
line.initial_concentration,
|
|
line.final_concentration,
|
|
line.initial_unit,
|
|
line.final_unit,
|
|
line.result_modifier,
|
|
line.converted_result_modifier,
|
|
line.result,
|
|
line.converted_result,
|
|
line.detection_limit,
|
|
line.quantification_limit,
|
|
line.dilution_factor,
|
|
line.chromatogram,
|
|
line.comments,
|
|
line.theoretical_concentration,
|
|
line.concentration_level,
|
|
line.decimals,
|
|
line.backup,
|
|
line.reference,
|
|
line.literal_result,
|
|
line.rm_correction_formula,
|
|
line.report,
|
|
line.uncertainty,
|
|
line.verification,
|
|
line.accepted,
|
|
line.acceptance_date,
|
|
line.annulled,
|
|
line.annulment_date,
|
|
line.results_report,
|
|
line.planification,
|
|
detail.confirmation_date,
|
|
line.results_estimated_waiting,
|
|
line.department,
|
|
]
|
|
where = Literal(True)
|
|
return join6.select(*columns, where=where)
|
|
|
|
@classmethod
|
|
def get_line_field(cls, notebook_lines, names):
|
|
result = dict((name, {}) for name in names)
|
|
for nl in notebook_lines:
|
|
for name in names:
|
|
field = getattr(nl.line, name, None)
|
|
if isinstance(field, ModelSQL):
|
|
result[name][nl.id] = field.id if field else None
|
|
elif isinstance(field, tuple):
|
|
result[name][nl.id] = [f.id for f in field]
|
|
else:
|
|
result[name][nl.id] = field
|
|
return result
|
|
|
|
@classmethod
|
|
def search_line_field(cls, name, clause):
|
|
return [('line.' + name,) + tuple(clause[1:])]
|
|
|
|
|
|
class LimsNotebookLineLaboratoryProfessional(ModelSQL):
|
|
'Laboratory Notebook Line - Laboratory Professional'
|
|
__name__ = 'lims.notebook.line-laboratory.professional'
|
|
|
|
notebook_line = fields.Many2One('lims.notebook.line', 'Notebook Line',
|
|
ondelete='CASCADE', select=True, required=True)
|
|
professional = fields.Many2One('lims.laboratory.professional',
|
|
'Laboratory professional', ondelete='CASCADE', select=True,
|
|
required=True)
|
|
|
|
|
|
class LimsNotebookLineProfessional(ModelSQL, ModelView):
|
|
'Laboratory Notebook Line Professional'
|
|
__name__ = 'lims.notebook.line.professional'
|
|
|
|
notebook_line = fields.Many2One('lims.notebook.line', 'Notebook Line',
|
|
ondelete='CASCADE', select=True, required=True)
|
|
professional = fields.Many2One('lims.laboratory.professional',
|
|
'Laboratory professional', required=True)
|
|
|
|
|
|
class LimsEntry(Workflow, ModelSQL, ModelView):
|
|
'Entry'
|
|
__name__ = 'lims.entry'
|
|
_rec_name = 'number'
|
|
|
|
number = fields.Char('Number', select=True, readonly=True)
|
|
create_date2 = fields.Function(fields.DateTime('Create Date'),
|
|
'get_create_date2', searcher='search_create_date2')
|
|
date = fields.DateTime('Date')
|
|
date2 = fields.Function(fields.Date('Date'), 'get_date',
|
|
searcher='search_date')
|
|
party = fields.Many2One('party.party', 'Party', required=True,
|
|
states={'readonly': Eval('state') != 'draft'}, depends=['state'])
|
|
invoice_party = fields.Many2One('party.party', 'Invoice party',
|
|
domain=[('id', 'in', Eval('invoice_party_domain'))],
|
|
depends=['invoice_party_domain', 'state'], required=True,
|
|
states={'readonly': Eval('state') != 'draft'})
|
|
invoice_party_view = fields.Function(fields.Many2One('party.party',
|
|
'Invoice party'), 'get_views_field',
|
|
searcher='search_views_field')
|
|
invoice_party_domain = fields.Function(fields.Many2Many('party.party',
|
|
None, None, 'Invoice party domain'),
|
|
'on_change_with_invoice_party_domain')
|
|
invoice_contacts = fields.One2Many('lims.entry.invoice_contacts',
|
|
'entry', 'Invoice contacts')
|
|
report_contacts = fields.One2Many('lims.entry.report_contacts',
|
|
'entry', 'Report contacts')
|
|
acknowledgment_contacts = fields.One2Many(
|
|
'lims.entry.acknowledgment_contacts', 'entry',
|
|
'Acknowledgment contacts')
|
|
carrier = fields.Many2One('carrier', 'Carrier')
|
|
package_type = fields.Many2One('lims.packaging.type', 'Package type')
|
|
package_state = fields.Many2One('lims.packaging.integrity',
|
|
'Package state')
|
|
packages_quantity = fields.Integer('Packages quantity')
|
|
email_report = fields.Boolean('Email report')
|
|
single_sending_report = fields.Boolean('Single sending of report')
|
|
english_report = fields.Boolean('English report')
|
|
no_acknowledgment_of_receipt = fields.Boolean(
|
|
'No acknowledgment of receipt')
|
|
samples = fields.One2Many('lims.sample', 'entry', 'Samples',
|
|
context={
|
|
'entry': Eval('id'), 'party': Eval('party'),
|
|
}, depends=['party'])
|
|
invoice_comments = fields.Text('Invoice comments')
|
|
report_comments = fields.Text('Report comments', translate=True)
|
|
transfer_comments = fields.Text('Transfer comments')
|
|
comments = fields.Text('Comments')
|
|
pending_reason = fields.Many2One('lims.entry.suspension.reason',
|
|
'Pending reason', states={
|
|
'invisible': Not(Bool(Equal(Eval('state'), 'pending'))),
|
|
'required': Bool(Equal(Eval('state'), 'pending')),
|
|
}, depends=['state'])
|
|
state = fields.Selection([
|
|
('draft', 'Draft'),
|
|
('ongoing', 'Ongoing'),
|
|
('pending', 'Administration pending'),
|
|
('closed', 'Closed'),
|
|
], 'State', required=True, readonly=True)
|
|
ack_report_cache = fields.Binary('Acknowledgment report cache',
|
|
readonly=True)
|
|
ack_report_format = fields.Char('Acknowledgment report format',
|
|
readonly=True)
|
|
confirmed = fields.Function(fields.Boolean('Confirmed'), 'get_confirmed')
|
|
sent_date = fields.DateTime('Sent date', readonly=True)
|
|
result_cron = fields.Selection([
|
|
('', ''),
|
|
('failed_print', 'Failed to print'),
|
|
('failed_send', 'Failed to send'),
|
|
('sent', 'Sent'),
|
|
], 'Result cron', sort=False, readonly=True)
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(LimsEntry, cls).__setup__()
|
|
cls._order.insert(0, ('number', 'DESC'))
|
|
cls._transitions |= set((
|
|
('draft', 'ongoing'),
|
|
('draft', 'pending'),
|
|
('pending', 'ongoing'),
|
|
('ongoing', 'closed'),
|
|
))
|
|
cls._buttons.update({
|
|
'create_sample': {
|
|
'invisible': ~Eval('state').in_(['draft']),
|
|
},
|
|
'confirm': {
|
|
'invisible': ~Eval('state').in_(['draft', 'pending']),
|
|
},
|
|
'on_hold': {
|
|
'invisible': ~Eval('state').in_(['draft']),
|
|
},
|
|
})
|
|
cls._error_messages.update({
|
|
'no_entry_sequence': ('There is no entry sequence for '
|
|
'the work year "%s".'),
|
|
'delete_entry': ('You can not delete entry "%s" because '
|
|
'it is not in draft state'),
|
|
'not_fraction': ('You can not confirm entry "%s" because '
|
|
'has not fractions'),
|
|
'missing_entry_contacts': ('Missing contacts in entry "%s"'),
|
|
'enac_acredited': ('The analysis marked with * are not '
|
|
'covered by the Accreditation.'),
|
|
'english_report': ('Do not forget to load the translations '
|
|
'into English'),
|
|
})
|
|
|
|
@staticmethod
|
|
def default_date():
|
|
return datetime.now()
|
|
|
|
@staticmethod
|
|
def default_email_report():
|
|
return False
|
|
|
|
@staticmethod
|
|
def default_single_sending_report():
|
|
return False
|
|
|
|
@staticmethod
|
|
def default_english_report():
|
|
return False
|
|
|
|
@staticmethod
|
|
def default_no_acknowledgment_of_receipt():
|
|
return False
|
|
|
|
@staticmethod
|
|
def default_result_cron():
|
|
return ''
|
|
|
|
@staticmethod
|
|
def default_state():
|
|
return 'draft'
|
|
|
|
def get_date(self, name):
|
|
pool = Pool()
|
|
Company = pool.get('company.company')
|
|
|
|
date = self.date
|
|
company_id = Transaction().context.get('company')
|
|
if company_id:
|
|
date = Company(company_id).convert_timezone_datetime(date)
|
|
return date.date()
|
|
|
|
def get_create_date2(self, name):
|
|
return self.create_date.replace(microsecond=0)
|
|
|
|
@classmethod
|
|
def search_date(cls, name, clause):
|
|
pool = Pool()
|
|
Company = pool.get('company.company')
|
|
cursor = Transaction().connection.cursor()
|
|
|
|
timezone = None
|
|
company_id = Transaction().context.get('company')
|
|
if company_id:
|
|
timezone = Company(company_id).timezone
|
|
timezone_datetime = 'date::timestamp AT TIME ZONE \'UTC\''
|
|
if timezone:
|
|
timezone_datetime += ' AT TIME ZONE \'' + timezone + '\''
|
|
|
|
operator_ = clause[1:2][0]
|
|
cursor.execute('SELECT id '
|
|
'FROM "' + cls._table + '" '
|
|
'WHERE (' + timezone_datetime + ')::date '
|
|
+ operator_ + ' %s::date', clause[2:3])
|
|
return [('id', 'in', [x[0] for x in cursor.fetchall()])]
|
|
|
|
@classmethod
|
|
def search_create_date2(cls, name, clause):
|
|
cursor = Transaction().connection.cursor()
|
|
operator_ = clause[1:2][0]
|
|
cursor.execute('SELECT id '
|
|
'FROM "' + cls._table + '" '
|
|
'WHERE create_date' + operator_ + ' %s',
|
|
clause[2:3])
|
|
return [('id', 'in', [x[0] for x in cursor.fetchall()])]
|
|
|
|
@fields.depends('party', 'invoice_party', 'invoice_contacts',
|
|
'report_contacts', 'acknowledgment_contacts')
|
|
def on_change_party(self):
|
|
pool = Pool()
|
|
ReportContacts = pool.get('lims.entry.report_contacts')
|
|
AcknowledgmentContacts = pool.get('lims.entry.acknowledgment_contacts')
|
|
|
|
email = False
|
|
single_sending = False
|
|
english = False
|
|
no_ack = False
|
|
invoice_contacts = []
|
|
a_report_contacts = []
|
|
report_contacts = []
|
|
a_acknowledgment_contacts = []
|
|
acknowledgment_contacts = []
|
|
parties = []
|
|
if self.party:
|
|
parties.append(self.party.id)
|
|
if self.invoice_party:
|
|
parties.append(self.invoice_party.id)
|
|
|
|
if self.invoice_contacts:
|
|
for c in self.invoice_contacts:
|
|
if c.contact.party.id in parties:
|
|
invoice_contacts.append(c)
|
|
if self.report_contacts:
|
|
for c in self.report_contacts:
|
|
if c.contact.party.id in parties:
|
|
report_contacts.append(c)
|
|
a_report_contacts.append(c.contact)
|
|
if self.acknowledgment_contacts:
|
|
for c in self.acknowledgment_contacts:
|
|
if c.contact.party.id in parties:
|
|
acknowledgment_contacts.append(c)
|
|
a_acknowledgment_contacts.append(c.contact)
|
|
|
|
if self.party:
|
|
email = self.party.email_report
|
|
single_sending = self.party.single_sending_report
|
|
english = self.party.english_report
|
|
no_ack = self.party.no_acknowledgment_of_receipt
|
|
if self.party.addresses:
|
|
for c in self.party.addresses:
|
|
if (c.report_contact_default and c not
|
|
in a_report_contacts):
|
|
value = ReportContacts(**ReportContacts.default_get(
|
|
ReportContacts._fields.keys()))
|
|
value.contact = c
|
|
report_contacts.append(value)
|
|
if (c.acknowledgment_contact_default and c not
|
|
in a_acknowledgment_contacts):
|
|
value = AcknowledgmentContacts(
|
|
**AcknowledgmentContacts.default_get(
|
|
AcknowledgmentContacts._fields.keys()))
|
|
value.contact = c
|
|
acknowledgment_contacts.append(value)
|
|
|
|
self.email_report = email
|
|
self.single_sending_report = single_sending
|
|
self.english_report = english
|
|
self.no_acknowledgment_of_receipt = no_ack
|
|
self.invoice_contacts = invoice_contacts
|
|
self.report_contacts = report_contacts
|
|
self.acknowledgment_contacts = acknowledgment_contacts
|
|
if self.party and not self.invoice_party:
|
|
invoice_party_domain = self.on_change_with_invoice_party_domain()
|
|
if len(invoice_party_domain) == 1:
|
|
self.invoice_party = invoice_party_domain[0]
|
|
self.on_change_invoice_party()
|
|
|
|
@fields.depends('party', 'invoice_party', 'invoice_contacts',
|
|
'report_contacts', 'acknowledgment_contacts')
|
|
def on_change_invoice_party(self):
|
|
pool = Pool()
|
|
InvoiceContacts = pool.get('lims.entry.invoice_contacts')
|
|
|
|
a_invoice_contacts = []
|
|
invoice_contacts = []
|
|
report_contacts = []
|
|
acknowledgment_contacts = []
|
|
parties = []
|
|
if self.party:
|
|
parties.append(self.party.id)
|
|
if self.invoice_party:
|
|
parties.append(self.invoice_party.id)
|
|
|
|
if self.invoice_contacts:
|
|
for c in self.invoice_contacts:
|
|
if c.contact.party.id in parties:
|
|
invoice_contacts.append(c)
|
|
a_invoice_contacts.append(c.contact)
|
|
if self.report_contacts:
|
|
for c in self.report_contacts:
|
|
if c.contact.party.id in parties:
|
|
report_contacts.append(c)
|
|
if self.acknowledgment_contacts:
|
|
for c in self.acknowledgment_contacts:
|
|
if c.contact.party.id in parties:
|
|
acknowledgment_contacts.append(c)
|
|
|
|
if self.invoice_party:
|
|
if self.invoice_party.addresses:
|
|
for c in self.invoice_party.addresses:
|
|
if (c.invoice_contact_default and c not
|
|
in a_invoice_contacts):
|
|
value = InvoiceContacts(**InvoiceContacts.default_get(
|
|
InvoiceContacts._fields.keys()))
|
|
value.contact = c
|
|
invoice_contacts.append(value)
|
|
|
|
self.invoice_contacts = invoice_contacts
|
|
self.report_contacts = report_contacts
|
|
self.acknowledgment_contacts = acknowledgment_contacts
|
|
|
|
@classmethod
|
|
def get_views_field(cls, parties, names):
|
|
result = {}
|
|
for name in names:
|
|
field_name = name[:-5]
|
|
result[name] = {}
|
|
for p in parties:
|
|
field = getattr(p, field_name, None)
|
|
result[name][p.id] = field.id if field else None
|
|
return result
|
|
|
|
@classmethod
|
|
def search_views_field(cls, name, clause):
|
|
return [(name[:-5],) + tuple(clause[1:])]
|
|
|
|
@fields.depends('party')
|
|
def on_change_with_invoice_party_domain(self, name=None):
|
|
Config = Pool().get('lims.configuration')
|
|
|
|
config_ = Config(1)
|
|
parties = []
|
|
if self.party:
|
|
parties.append(self.party.id)
|
|
if config_.invoice_party_relation_type:
|
|
parties.extend([r.to.id for r in self.party.relations
|
|
if r.type == config_.invoice_party_relation_type])
|
|
return parties
|
|
|
|
@classmethod
|
|
def create(cls, vlist):
|
|
pool = Pool()
|
|
LimsLabWorkYear = pool.get('lims.lab.workyear')
|
|
Sequence = pool.get('ir.sequence.strict')
|
|
|
|
workyear_id = LimsLabWorkYear.find()
|
|
workyear = LimsLabWorkYear(workyear_id)
|
|
sequence = workyear.get_sequence('entry')
|
|
if not sequence:
|
|
cls.raise_user_error('no_entry_sequence',
|
|
(workyear.rec_name,))
|
|
|
|
vlist = [x.copy() for x in vlist]
|
|
for values in vlist:
|
|
values['number'] = Sequence.get_id(sequence.id)
|
|
return super(LimsEntry, cls).create(vlist)
|
|
|
|
@classmethod
|
|
def view_attributes(cls):
|
|
return [('/tree', 'colors',
|
|
If(Bool(Eval('confirmed')), 'black', 'red'))]
|
|
|
|
@classmethod
|
|
def copy(cls, entries, default=None):
|
|
if default is None:
|
|
default = {}
|
|
|
|
new_entries = []
|
|
for entry in entries:
|
|
invoice_contacts = [{
|
|
'contact': c.contact.id,
|
|
} for c in entry.invoice_contacts]
|
|
report_contacts = [{
|
|
'contact': c.contact.id,
|
|
} for c in entry.report_contacts]
|
|
acknowledgment_contacts = [{
|
|
'contact': c.contact.id,
|
|
} for c in entry.acknowledgment_contacts]
|
|
current_default = default.copy()
|
|
current_default['state'] = 'draft'
|
|
current_default['ack_report_cache'] = None
|
|
current_default['ack_report_format'] = None
|
|
current_default['sent_date'] = None
|
|
current_default['result_cron'] = ''
|
|
current_default['invoice_contacts'] = [('create',
|
|
invoice_contacts)]
|
|
current_default['report_contacts'] = [('create',
|
|
report_contacts)]
|
|
current_default['acknowledgment_contacts'] = [('create',
|
|
acknowledgment_contacts)]
|
|
|
|
new_entry, = super(LimsEntry, cls).copy([entry],
|
|
default=current_default)
|
|
new_entries.append(new_entry)
|
|
return new_entries
|
|
|
|
@classmethod
|
|
@ModelView.button_action('lims.wiz_lims_create_sample')
|
|
def create_sample(cls, entries):
|
|
pass
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
@Workflow.transition('ongoing')
|
|
def confirm(cls, entries):
|
|
for entry in entries:
|
|
entry.check_contacts()
|
|
entry.warn_english_report()
|
|
entry._confirm()
|
|
|
|
@classmethod
|
|
def cron_acknowledgment_of_receipt(cls):
|
|
'''
|
|
Cron - Acknowledgment Of Receipt (Samples)
|
|
'''
|
|
logging.getLogger('lims').info(
|
|
'Cron - Acknowledgment Of Receipt (Samples):INIT')
|
|
pool = Pool()
|
|
LimsForwardAcknowledgmentOfReceipt = pool.get(
|
|
'lims.entry.acknowledgment.forward', type='wizard')
|
|
LimsEntry = pool.get('lims.entry')
|
|
entries = LimsEntry.search([
|
|
('result_cron', '!=', 'sent'),
|
|
('no_acknowledgment_of_receipt', '=', False),
|
|
('state', '=', 'ongoing'),
|
|
])
|
|
session_id, _, _ = LimsForwardAcknowledgmentOfReceipt.create()
|
|
acknowledgment_forward = LimsForwardAcknowledgmentOfReceipt(session_id)
|
|
with Transaction().set_context(active_ids=[entry.id for entry
|
|
in entries]):
|
|
data = acknowledgment_forward.transition_start()
|
|
if data:
|
|
logging.getLogger('lims').info('data:%s' % data) # debug
|
|
logging.getLogger('lims').info(
|
|
'Cron - Acknowledgment Of Receipt (Samples):END')
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
def on_hold(cls, entries):
|
|
pool = Pool()
|
|
LimsEntrySuspensionReason = pool.get('lims.entry.suspension.reason')
|
|
|
|
for entry in entries:
|
|
entry.check_contacts()
|
|
default_pending_reason = None
|
|
reasons = LimsEntrySuspensionReason.search([
|
|
('by_default', '=', True),
|
|
])
|
|
if reasons:
|
|
default_pending_reason = reasons[0].id
|
|
cls.pending_reason.states['required'] = False
|
|
cls.write(entries, {
|
|
'state': 'pending',
|
|
'pending_reason': default_pending_reason,
|
|
})
|
|
cls.pending_reason.states['required'] = (
|
|
Bool(Equal(Eval('state'), 'pending')))
|
|
|
|
@classmethod
|
|
@Workflow.transition('closed')
|
|
def close(cls, entries):
|
|
pass
|
|
|
|
def check_contacts(self):
|
|
if (not self.invoice_contacts
|
|
or not self.report_contacts
|
|
or not self.acknowledgment_contacts):
|
|
self.raise_user_error('missing_entry_contacts', (self.rec_name,))
|
|
|
|
def warn_english_report(self):
|
|
if self.english_report:
|
|
self.raise_user_warning('lims_english_report@%s' %
|
|
self.number, 'english_report')
|
|
|
|
def print_report(self):
|
|
if self.ack_report_cache:
|
|
return
|
|
LimsAcknowledgmentOfReceipt = Pool().get(
|
|
'lims.entry.acknowledgment.report', type='report')
|
|
success = False
|
|
try:
|
|
LimsAcknowledgmentOfReceipt.execute([self.id], {})
|
|
success = True
|
|
except Exception:
|
|
logging.getLogger('lims').error(
|
|
'Unable to print report Acknowledgment of receipt for '
|
|
'Entry:%s' % (self.number))
|
|
return success
|
|
|
|
def mail_acknowledgment_of_receipt(self):
|
|
if not self.ack_report_cache:
|
|
return
|
|
|
|
from_addr = config.get('email', 'from')
|
|
to_addrs = [c.contact.email for c in self.acknowledgment_contacts]
|
|
if not (from_addr and to_addrs):
|
|
return
|
|
|
|
subject, body = self.subject_body()
|
|
attachment_data = self.attachment()
|
|
msg = self.create_msg(from_addr, to_addrs, subject,
|
|
body, attachment_data)
|
|
return self.send_msg(from_addr, to_addrs, msg)
|
|
|
|
def subject_body(self):
|
|
pool = Pool()
|
|
Config = pool.get('lims.configuration')
|
|
User = pool.get('res.user')
|
|
Lang = pool.get('ir.lang')
|
|
|
|
config = Config(1)
|
|
|
|
lang = User(Transaction().user).language
|
|
if not lang:
|
|
lang, = Lang.search([
|
|
('code', '=', 'en'),
|
|
], limit=1)
|
|
|
|
with Transaction().set_context(language=lang.code):
|
|
subject = unicode('%s %s' % (config.mail_ack_subject,
|
|
self.number)).strip()
|
|
body = unicode(config.mail_ack_body)
|
|
|
|
return subject, body
|
|
|
|
def attachment(self):
|
|
data = {
|
|
'content': self.ack_report_cache,
|
|
'format': self.ack_report_format,
|
|
'mimetype': self.ack_report_format == 'pdf' and 'pdf'
|
|
or 'vnd.oasis.opendocument.text',
|
|
'filename': (unicode(self.number) + '.'
|
|
+ str(self.ack_report_format)),
|
|
'name': unicode(self.number),
|
|
}
|
|
return data
|
|
|
|
def create_msg(self, from_addr, to_addrs, subject, body, attachment_data):
|
|
if not to_addrs:
|
|
return None
|
|
|
|
msg = MIMEMultipart()
|
|
msg['From'] = from_addr
|
|
hidden = True # TODO: HARDCODE!
|
|
if not hidden:
|
|
msg['To'] = ', '.join(to_addrs)
|
|
msg['Subject'] = subject
|
|
|
|
msg_body = MIMEBase('text', 'plain')
|
|
msg_body.set_payload(body.encode('UTF-8'), 'UTF-8')
|
|
msg.attach(msg_body)
|
|
|
|
attachment = MIMEApplication(
|
|
attachment_data['content'],
|
|
Name=attachment_data['filename'], _subtype="pdf")
|
|
attachment.add_header('content-disposition', 'attachment',
|
|
filename=('utf-8', '', attachment_data['filename']))
|
|
msg.attach(attachment)
|
|
|
|
return msg
|
|
|
|
def send_msg(self, from_addr, to_addrs, msg):
|
|
to_addrs = list(set(to_addrs))
|
|
success = False
|
|
try:
|
|
server = get_smtp_server()
|
|
server.sendmail(from_addr, to_addrs, msg.as_string())
|
|
server.quit()
|
|
success = True
|
|
except Exception:
|
|
logging.getLogger('lims').error(
|
|
'Unable to deliver mail for entry %s' % (self.number))
|
|
return success
|
|
|
|
def _confirm(self):
|
|
LimsFraction = Pool().get('lims.fraction')
|
|
fractions = LimsFraction.search([
|
|
('entry', '=', self.id),
|
|
('confirmed', '=', False),
|
|
], order=[
|
|
('sample', 'ASC'), ('id', 'ASC'),
|
|
])
|
|
if not fractions:
|
|
Company = Pool().get('company.company')
|
|
companies = Company.search([])
|
|
if self.party.id not in [c.party.id for c in companies]:
|
|
self.raise_user_error('not_fraction', (self.rec_name,))
|
|
LimsFraction.confirm(fractions)
|
|
|
|
@classmethod
|
|
def check_delete(cls, entries):
|
|
for entry in entries:
|
|
if entry.state != 'draft':
|
|
cls.raise_user_error('delete_entry', (entry.rec_name,))
|
|
|
|
@classmethod
|
|
def delete(cls, entries):
|
|
cls.check_delete(entries)
|
|
super(LimsEntry, cls).delete(entries)
|
|
|
|
def get_confirmed(self, name=None):
|
|
if not self.samples:
|
|
return False
|
|
for sample in self.samples:
|
|
if not sample.fractions:
|
|
return False
|
|
for fraction in sample.fractions:
|
|
if not fraction.confirmed:
|
|
return False
|
|
return True
|
|
|
|
@classmethod
|
|
def order_create_date2(cls, tables):
|
|
return cls.create_date.convert_order('create_date', tables, cls)
|
|
|
|
|
|
class LimsEntryInvoiceContact(ModelSQL, ModelView):
|
|
'Entry Invoice Contact'
|
|
__name__ = 'lims.entry.invoice_contacts'
|
|
|
|
entry = fields.Many2One('lims.entry', 'Entry',
|
|
ondelete='CASCADE', select=True, required=True)
|
|
contact = fields.Many2One('party.address', 'Contact', required=True,
|
|
domain=[
|
|
('party', 'in', [Eval('_parent_entry', {}).get('party'),
|
|
Eval('_parent_entry', {}).get('invoice_party')]),
|
|
('invoice_contact', '=', True),
|
|
])
|
|
|
|
|
|
class LimsEntryReportContact(ModelSQL, ModelView):
|
|
'Entry Report Contact'
|
|
__name__ = 'lims.entry.report_contacts'
|
|
|
|
entry = fields.Many2One('lims.entry', 'Entry',
|
|
ondelete='CASCADE', select=True, required=True)
|
|
contact = fields.Many2One('party.address', 'Contact', required=True,
|
|
domain=[
|
|
('party', 'in', [Eval('_parent_entry', {}).get('party'),
|
|
Eval('_parent_entry', {}).get('invoice_party')]),
|
|
('report_contact', '=', True),
|
|
])
|
|
|
|
|
|
class LimsEntryAcknowledgmentContact(ModelSQL, ModelView):
|
|
'Entry Acknowledgment Contact'
|
|
__name__ = 'lims.entry.acknowledgment_contacts'
|
|
|
|
entry = fields.Many2One('lims.entry', 'Entry',
|
|
ondelete='CASCADE', select=True, required=True)
|
|
contact = fields.Many2One('party.address', 'Contact', required=True,
|
|
domain=[
|
|
('party', 'in', [Eval('_parent_entry', {}).get('party'),
|
|
Eval('_parent_entry', {}).get('invoice_party')]),
|
|
('acknowledgment_contact', '=', True),
|
|
])
|
|
|
|
|
|
class LimsAnalysisFamily(ModelSQL, ModelView):
|
|
'Analysis Family'
|
|
__name__ = 'lims.analysis.family'
|
|
_rec_name = 'description'
|
|
|
|
code = fields.Char('Code', required=True)
|
|
description = fields.Char('Description', required=True)
|
|
party = fields.Many2One('party.party', 'Certificant party')
|
|
certificants = fields.One2Many('lims.analysis.family.certificant',
|
|
'family', 'Product Type - Matrix')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(LimsAnalysisFamily, cls).__setup__()
|
|
t = cls.__table__()
|
|
cls._sql_constraints += [
|
|
('code_uniq', Unique(t, t.code),
|
|
'Analysis family code must be unique'),
|
|
]
|
|
|
|
def get_rec_name(self, name):
|
|
if self.code:
|
|
return self.code + ' - ' + self.description
|
|
else:
|
|
return self.description
|
|
|
|
@classmethod
|
|
def search_rec_name(cls, name, clause):
|
|
field = None
|
|
for field in ('code', 'description'):
|
|
records = cls.search([(field,) + tuple(clause[1:])], limit=1)
|
|
if records:
|
|
break
|
|
if records:
|
|
return [(field,) + tuple(clause[1:])]
|
|
return [(cls._rec_name,) + tuple(clause[1:])]
|
|
|
|
|
|
class LimsAnalysisFamilyCertificant(ModelSQL, ModelView):
|
|
'Product Type - Matrix'
|
|
__name__ = 'lims.analysis.family.certificant'
|
|
|
|
family = fields.Many2One('lims.analysis.family', 'Family', required=True)
|
|
product_type = fields.Many2One('lims.product.type', 'Product type',
|
|
required=True)
|
|
matrix = fields.Many2One('lims.matrix', 'Matrix', required=True)
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(LimsAnalysisFamilyCertificant, cls).__setup__()
|
|
t = cls.__table__()
|
|
cls._sql_constraints += [
|
|
('product_matrix_uniq',
|
|
Unique(t, t.family, t.product_type, t.matrix),
|
|
'This record already exists'),
|
|
]
|
|
|
|
|
|
class LimsZone(ModelSQL, ModelView):
|
|
'Zone/Region'
|
|
__name__ = 'lims.zone'
|
|
_rec_name = 'description'
|
|
|
|
code = fields.Char('Code', required=True)
|
|
description = fields.Char('Description', required=True)
|
|
restricted_entry = fields.Boolean('Restricted entry')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(LimsZone, cls).__setup__()
|
|
t = cls.__table__()
|
|
cls._sql_constraints += [
|
|
('code_uniq', Unique(t, t.code),
|
|
'Zone code must be unique'),
|
|
]
|
|
|
|
def get_rec_name(self, name):
|
|
if self.code:
|
|
return self.code + ' - ' + self.description
|
|
else:
|
|
return self.description
|
|
|
|
@classmethod
|
|
def search_rec_name(cls, name, clause):
|
|
field = None
|
|
for field in ('code', 'description'):
|
|
records = cls.search([(field,) + tuple(clause[1:])], limit=1)
|
|
if records:
|
|
break
|
|
if records:
|
|
return [(field,) + tuple(clause[1:])]
|
|
return [(cls._rec_name,) + tuple(clause[1:])]
|
|
|
|
|
|
class LimsVariety(ModelSQL, ModelView):
|
|
'Variety'
|
|
__name__ = 'lims.variety'
|
|
_rec_name = 'description'
|
|
|
|
code = fields.Char('Code', required=True)
|
|
description = fields.Char('Description', required=True)
|
|
varieties = fields.One2Many('lims.matrix.variety', 'variety',
|
|
'Product Type - Matrix')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(LimsVariety, cls).__setup__()
|
|
t = cls.__table__()
|
|
cls._sql_constraints += [
|
|
('code_uniq', Unique(t, t.code),
|
|
'Variety code must be unique'),
|
|
]
|
|
|
|
def get_rec_name(self, name):
|
|
if self.code:
|
|
return self.code + ' - ' + self.description
|
|
else:
|
|
return self.description
|
|
|
|
@classmethod
|
|
def search_rec_name(cls, name, clause):
|
|
field = None
|
|
for field in ('code', 'description'):
|
|
records = cls.search([(field,) + tuple(clause[1:])], limit=1)
|
|
if records:
|
|
break
|
|
if records:
|
|
return [(field,) + tuple(clause[1:])]
|
|
return [(cls._rec_name,) + tuple(clause[1:])]
|
|
|
|
|
|
class LimsMatrixVariety(ModelSQL, ModelView):
|
|
'Product Type - Matrix - Variety'
|
|
__name__ = 'lims.matrix.variety'
|
|
|
|
product_type = fields.Many2One('lims.product.type', 'Product type',
|
|
required=True)
|
|
matrix = fields.Many2One('lims.matrix', 'Matrix', required=True)
|
|
variety = fields.Many2One('lims.variety', 'Variety', required=True)
|
|
|
|
|
|
class LimsPackagingIntegrity(ModelSQL, ModelView):
|
|
'Packaging Integrity'
|
|
__name__ = 'lims.packaging.integrity'
|
|
_rec_name = 'description'
|
|
|
|
code = fields.Char('Code', required=True)
|
|
description = fields.Char('Description', required=True, translate=True)
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(LimsPackagingIntegrity, cls).__setup__()
|
|
t = cls.__table__()
|
|
cls._sql_constraints += [
|
|
('code_uniq', Unique(t, t.code),
|
|
'Packaging integrity code must be unique'),
|
|
]
|
|
|
|
def get_rec_name(self, name):
|
|
if self.code:
|
|
return self.code + ' - ' + self.description
|
|
else:
|
|
return self.description
|
|
|
|
@classmethod
|
|
def search_rec_name(cls, name, clause):
|
|
field = None
|
|
for field in ('code', 'description'):
|
|
records = cls.search([(field,) + tuple(clause[1:])], limit=1)
|
|
if records:
|
|
break
|
|
if records:
|
|
return [(field,) + tuple(clause[1:])]
|
|
return [(cls._rec_name,) + tuple(clause[1:])]
|
|
|
|
|
|
class LimsEntrySuspensionReason(ModelSQL, ModelView):
|
|
'Entry Suspension Reason'
|
|
__name__ = 'lims.entry.suspension.reason'
|
|
_rec_name = 'description'
|
|
|
|
code = fields.Char('Code', required=True)
|
|
description = fields.Char('Description', required=True)
|
|
by_default = fields.Boolean('By default')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(LimsEntrySuspensionReason, cls).__setup__()
|
|
t = cls.__table__()
|
|
cls._sql_constraints += [
|
|
('code_uniq', Unique(t, t.code),
|
|
'Suspension reason code must be unique'),
|
|
]
|
|
cls._error_messages.update({
|
|
'default_suspension_reason': 'There is already a default '
|
|
'suspension reason',
|
|
})
|
|
|
|
@staticmethod
|
|
def default_by_default():
|
|
return False
|
|
|
|
def get_rec_name(self, name):
|
|
if self.code:
|
|
return self.code + ' - ' + self.description
|
|
else:
|
|
return self.description
|
|
|
|
@classmethod
|
|
def search_rec_name(cls, name, clause):
|
|
field = None
|
|
for field in ('code', 'description'):
|
|
records = cls.search([(field,) + tuple(clause[1:])], limit=1)
|
|
if records:
|
|
break
|
|
if records:
|
|
return [(field,) + tuple(clause[1:])]
|
|
return [(cls._rec_name,) + tuple(clause[1:])]
|
|
|
|
@classmethod
|
|
def validate(cls, reasons):
|
|
super(LimsEntrySuspensionReason, cls).validate(reasons)
|
|
for sr in reasons:
|
|
sr.check_default()
|
|
|
|
def check_default(self):
|
|
if self.by_default:
|
|
reasons = self.search([
|
|
('by_default', '=', True),
|
|
('id', '!=', self.id),
|
|
])
|
|
if reasons:
|
|
self.raise_user_error('default_suspension_reason')
|
|
|
|
|
|
class LimsEntryDetailAnalysis(ModelSQL, ModelView):
|
|
'Entry Detail Analysis'
|
|
__name__ = 'lims.entry.detail.analysis'
|
|
|
|
service = fields.Many2One('lims.service', 'Service', required=True,
|
|
ondelete='CASCADE', select=True, readonly=True)
|
|
service_view = fields.Function(fields.Many2One('lims.service',
|
|
'Service', states={'invisible': Not(Bool(Eval('_parent_service')))}),
|
|
'on_change_with_service_view')
|
|
create_date2 = fields.Function(fields.DateTime('Create Date'),
|
|
'get_create_date2', searcher='search_create_date2')
|
|
fraction = fields.Function(fields.Many2One('lims.fraction', 'Fraction'),
|
|
'get_service_field', searcher='search_service_field')
|
|
sample = fields.Function(fields.Many2One('lims.sample', 'Sample'),
|
|
'get_service_field', searcher='search_service_field')
|
|
entry = fields.Function(fields.Many2One('lims.entry', 'Entry'),
|
|
'get_service_field', searcher='search_service_field')
|
|
party = fields.Function(fields.Many2One('party.party', 'Party'),
|
|
'get_service_field', searcher='search_service_field')
|
|
analysis = fields.Many2One('lims.analysis', 'Analysis', required=True,
|
|
states={'readonly': True})
|
|
analysis_type = fields.Function(fields.Selection([
|
|
('analysis', 'Analysis'),
|
|
('set', 'Set'),
|
|
('group', 'Group'),
|
|
], 'Type', sort=False),
|
|
'on_change_with_analysis_type')
|
|
laboratory = fields.Many2One('lims.laboratory', 'Laboratory',
|
|
states={'readonly': True})
|
|
method = fields.Many2One('lims.lab.method', 'Method',
|
|
states={'readonly': True})
|
|
device = fields.Many2One('lims.lab.device', 'Device',
|
|
states={'readonly': True})
|
|
analysis_origin = fields.Char('Analysis origin',
|
|
states={'readonly': True})
|
|
confirmation_date = fields.Date('Confirmation date', readonly=True)
|
|
report_grouper = fields.Integer('Report Grouper')
|
|
results_report = fields.Function(fields.Many2One('lims.results_report',
|
|
'Results Report'), 'get_results_report')
|
|
report = fields.Function(fields.Boolean('Report'), 'get_report',
|
|
searcher='search_report')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(LimsEntryDetailAnalysis, cls).__setup__()
|
|
cls._order.insert(0, ('service', 'DESC'))
|
|
cls._error_messages.update({
|
|
'delete_detail': ('You can not delete the analysis detail because '
|
|
'its fraction is confirmed'),
|
|
})
|
|
|
|
@classmethod
|
|
def copy(cls, details, default=None):
|
|
if default is None:
|
|
default = {}
|
|
current_default = default.copy()
|
|
current_default['confirmation_date'] = None
|
|
return super(LimsEntryDetailAnalysis, cls).copy(details,
|
|
default=current_default)
|
|
|
|
@classmethod
|
|
def check_delete(cls, details):
|
|
for detail in details:
|
|
if detail.fraction and detail.fraction.confirmed:
|
|
cls.raise_user_error('delete_detail')
|
|
|
|
@classmethod
|
|
def delete(cls, details):
|
|
if Transaction().user != 0:
|
|
cls.check_delete(details)
|
|
super(LimsEntryDetailAnalysis, cls).delete(details)
|
|
|
|
@classmethod
|
|
def create_notebook_lines(cls, details, fraction):
|
|
cursor = Transaction().connection.cursor()
|
|
pool = Pool()
|
|
Typification = pool.get('lims.typification')
|
|
Method = pool.get('lims.lab.method')
|
|
AnalysisLaboratory = pool.get('lims.analysis-laboratory')
|
|
Fraction = pool.get('lims.fraction')
|
|
Notebook = pool.get('lims.notebook')
|
|
Company = pool.get('company.company')
|
|
|
|
lines_create = []
|
|
|
|
for detail in details:
|
|
cursor.execute('SELECT default_repetitions, '
|
|
'initial_concentration, final_concentration, start_uom, '
|
|
'end_uom, detection_limit, quantification_limit, '
|
|
'calc_decimals, report '
|
|
'FROM "' + Typification._table + '" '
|
|
'WHERE product_type = %s '
|
|
'AND matrix = %s '
|
|
'AND analysis = %s '
|
|
'AND method = %s '
|
|
'AND valid',
|
|
(fraction.product_type.id, fraction.matrix.id,
|
|
detail.analysis.id, detail.method.id))
|
|
typifications = cursor.fetchall()
|
|
typification = (typifications[0] if len(typifications) == 1
|
|
else None)
|
|
if typification:
|
|
repetitions = typification[0]
|
|
initial_concentration = unicode(typification[1] or '')
|
|
final_concentration = unicode(typification[2] or '')
|
|
initial_unit = typification[3]
|
|
final_unit = typification[4]
|
|
detection_limit = str(typification[5])
|
|
quantification_limit = str(typification[6])
|
|
decimals = typification[7]
|
|
report = typification[8]
|
|
else:
|
|
repetitions = 0
|
|
initial_concentration = None
|
|
final_concentration = None
|
|
initial_unit = None
|
|
final_unit = None
|
|
detection_limit = None
|
|
quantification_limit = None
|
|
decimals = 2
|
|
report = False
|
|
|
|
cursor.execute('SELECT results_estimated_waiting '
|
|
'FROM "' + Method._table + '" '
|
|
'WHERE id = %s', (detail.method.id,))
|
|
res = cursor.fetchone()
|
|
results_estimated_waiting = res and res[0] or None
|
|
|
|
cursor.execute('SELECT department '
|
|
'FROM "' + AnalysisLaboratory._table + '" '
|
|
'WHERE analysis = %s '
|
|
'AND laboratory = %s',
|
|
(detail.analysis.id, detail.laboratory.id))
|
|
res = cursor.fetchone()
|
|
department = res and res[0] or None
|
|
|
|
for i in range(0, repetitions + 1):
|
|
notebook_line = {
|
|
'analysis_detail': detail.id,
|
|
'service': detail.service.id,
|
|
'analysis': detail.analysis.id,
|
|
'analysis_origin': detail.analysis_origin,
|
|
'repetition': i,
|
|
'laboratory': detail.laboratory.id,
|
|
'method': detail.method.id,
|
|
'device': detail.device.id if detail.device else None,
|
|
'initial_concentration': initial_concentration,
|
|
'final_concentration': final_concentration,
|
|
'initial_unit': initial_unit,
|
|
'final_unit': final_unit,
|
|
'detection_limit': detection_limit,
|
|
'quantification_limit': quantification_limit,
|
|
'decimals': decimals,
|
|
'report': report,
|
|
'results_estimated_waiting': results_estimated_waiting,
|
|
'department': department,
|
|
}
|
|
lines_create.append(notebook_line)
|
|
|
|
if not lines_create:
|
|
companies = Company.search([])
|
|
if fraction.party.id not in [c.party.id for c in companies]:
|
|
Fraction.raise_user_error('not_services',
|
|
(fraction.rec_name,))
|
|
|
|
with Transaction().set_user(0):
|
|
notebook = Notebook.search([
|
|
('fraction', '=', fraction.id),
|
|
])
|
|
Notebook.write(notebook, {
|
|
'lines': [('create', lines_create)],
|
|
})
|
|
|
|
@staticmethod
|
|
def default_service_view():
|
|
if (Transaction().context.get('service') > 0):
|
|
return Transaction().context.get('service')
|
|
return None
|
|
|
|
@fields.depends('service')
|
|
def on_change_with_service_view(self, name=None):
|
|
if self.service:
|
|
return self.service.id
|
|
return None
|
|
|
|
@staticmethod
|
|
def default_fraction():
|
|
if (Transaction().context.get('fraction') > 0):
|
|
return Transaction().context.get('fraction')
|
|
return None
|
|
|
|
@staticmethod
|
|
def default_sample():
|
|
if (Transaction().context.get('sample') > 0):
|
|
return Transaction().context.get('sample')
|
|
return None
|
|
|
|
@staticmethod
|
|
def default_entry():
|
|
if (Transaction().context.get('entry') > 0):
|
|
return Transaction().context.get('entry')
|
|
return None
|
|
|
|
@staticmethod
|
|
def default_party():
|
|
if (Transaction().context.get('party') > 0):
|
|
return Transaction().context.get('party')
|
|
return None
|
|
|
|
@staticmethod
|
|
def default_report_grouper():
|
|
return 0
|
|
|
|
@fields.depends('analysis')
|
|
def on_change_with_analysis_type(self, name=None):
|
|
if self.analysis:
|
|
return self.analysis.type
|
|
return ''
|
|
|
|
@classmethod
|
|
def get_service_field(cls, details, names):
|
|
result = {}
|
|
for name in names:
|
|
result[name] = {}
|
|
for d in details:
|
|
field = getattr(d.service, name, None)
|
|
result[name][d.id] = field.id if field else None
|
|
return result
|
|
|
|
@classmethod
|
|
def get_create_date2(cls, details, name):
|
|
result = {}
|
|
for d in details:
|
|
result[d.id] = d.create_date.replace(microsecond=0)
|
|
return result
|
|
|
|
@classmethod
|
|
def search_create_date2(cls, name, clause):
|
|
cursor = Transaction().connection.cursor()
|
|
operator_ = clause[1:2][0]
|
|
cursor.execute('SELECT id '
|
|
'FROM "' + cls._table + '" '
|
|
'WHERE create_date' + operator_ + ' %s',
|
|
clause[2:3])
|
|
return [('id', 'in', [x[0] for x in cursor.fetchall()])]
|
|
|
|
@classmethod
|
|
def search_service_field(cls, name, clause):
|
|
return [('service.' + name,) + tuple(clause[1:])]
|
|
|
|
@classmethod
|
|
def order_create_date2(cls, tables):
|
|
return cls.create_date.convert_order('create_date', tables, cls)
|
|
|
|
def _order_service_field(name):
|
|
def order_field(tables):
|
|
Service = Pool().get('lims.service')
|
|
field = Service._fields[name]
|
|
table, _ = tables[None]
|
|
service_tables = tables.get('service')
|
|
if service_tables is None:
|
|
service = Service.__table__()
|
|
service_tables = {
|
|
None: (service, service.id == table.service),
|
|
}
|
|
tables['service'] = service_tables
|
|
return field.convert_order(name, service_tables, Service)
|
|
return staticmethod(order_field)
|
|
# Redefine convert_order function with 'order_%s' % field
|
|
order_fraction = _order_service_field('fraction')
|
|
order_sample = _order_service_field('sample')
|
|
order_entry = _order_service_field('entry')
|
|
order_party = _order_service_field('party')
|
|
|
|
@classmethod
|
|
def get_results_report(cls, details, name):
|
|
cursor = Transaction().connection.cursor()
|
|
LimsNotebookLine = Pool().get('lims.notebook.line')
|
|
|
|
result = {}
|
|
for d in details:
|
|
cursor.execute('SELECT results_report '
|
|
'FROM "' + LimsNotebookLine._table + '" '
|
|
'WHERE analysis_detail = %s '
|
|
'AND results_report IS NOT NULL '
|
|
'ORDER BY id ASC LIMIT 1',
|
|
(d.id,))
|
|
value = cursor.fetchone()
|
|
result[d.id] = value[0] if value else None
|
|
return result
|
|
|
|
@classmethod
|
|
def get_report(cls, details, name):
|
|
cursor = Transaction().connection.cursor()
|
|
LimsNotebookLine = Pool().get('lims.notebook.line')
|
|
|
|
result = {}
|
|
for d in details:
|
|
cursor.execute('SELECT report '
|
|
'FROM "' + LimsNotebookLine._table + '" '
|
|
'WHERE analysis_detail = %s '
|
|
'ORDER BY id DESC LIMIT 1',
|
|
(d.id,))
|
|
value = cursor.fetchone()
|
|
result[d.id] = value[0] if value else False
|
|
return result
|
|
|
|
@classmethod
|
|
def search_report(cls, name, clause):
|
|
cursor = Transaction().connection.cursor()
|
|
LimsNotebookLine = Pool().get('lims.notebook.line')
|
|
|
|
cursor.execute('SELECT detail.id '
|
|
'FROM "' + cls._table + '" detail '
|
|
'INNER JOIN ( '
|
|
'SELECT DISTINCT ON (analysis_detail) '
|
|
'analysis_detail, report '
|
|
'FROM "' + LimsNotebookLine._table + '" '
|
|
'ORDER BY analysis_detail, id DESC '
|
|
') last_nbl '
|
|
'ON detail.id = last_nbl.analysis_detail '
|
|
'WHERE last_nbl.report = TRUE')
|
|
to_report = cursor.fetchall()
|
|
|
|
field, op, operand = clause
|
|
if (op, operand) in (('=', True), ('!=', False)):
|
|
return [('id', 'in', to_report)]
|
|
elif (op, operand) in (('=', False), ('!=', True)):
|
|
return [('id', 'not in', to_report)]
|
|
else:
|
|
return []
|
|
|
|
|
|
class LimsVolumeConversion(ModelSQL, ModelView):
|
|
'Volume Conversion'
|
|
__name__ = 'lims.volume.conversion'
|
|
|
|
brix = fields.Float('Brix', required=True, digits=(16,
|
|
Eval('brix_digits', 2)), depends=['brix_digits'])
|
|
density = fields.Float('Density', required=True, digits=(16,
|
|
Eval('density_digits', 2)), depends=['density_digits'])
|
|
soluble_solids = fields.Float('Soluble solids', required=True,
|
|
digits=(16, Eval('soluble_solids_digits', 2)),
|
|
depends=['soluble_solids_digits'])
|
|
brix_digits = fields.Function(fields.Integer('Brix digits'),
|
|
'get_configuration_field')
|
|
density_digits = fields.Function(fields.Integer('Density digits'),
|
|
'get_configuration_field')
|
|
soluble_solids_digits = fields.Function(fields.Integer(
|
|
'Soluble solids digits'), 'get_configuration_field')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(LimsVolumeConversion, cls).__setup__()
|
|
cls._order.insert(0, ('brix', 'ASC'))
|
|
|
|
@staticmethod
|
|
def default_brix_digits():
|
|
Config = Pool().get('lims.configuration')
|
|
config = Config(1)
|
|
return getattr(config, 'brix_digits', 2)
|
|
|
|
@staticmethod
|
|
def default_density_digits():
|
|
Config = Pool().get('lims.configuration')
|
|
config = Config(1)
|
|
return getattr(config, 'density_digits', 2)
|
|
|
|
@staticmethod
|
|
def default_soluble_solids_digits():
|
|
Config = Pool().get('lims.configuration')
|
|
config = Config(1)
|
|
return getattr(config, 'soluble_solids_digits', 2)
|
|
|
|
@classmethod
|
|
def get_configuration_field(cls, volume_conversions, names):
|
|
Config = Pool().get('lims.configuration')
|
|
config = Config(1)
|
|
|
|
result = {}
|
|
for name in names:
|
|
value = getattr(config, name, 2)
|
|
result[name] = dict((vc.id, value)
|
|
for vc in volume_conversions)
|
|
return result
|
|
|
|
@classmethod
|
|
def brixToDensity(cls, brix):
|
|
if not brix:
|
|
return None
|
|
brix = float(brix)
|
|
|
|
values = cls.search([
|
|
('brix', '=', brix),
|
|
], limit=1)
|
|
if values:
|
|
return values[0].density
|
|
|
|
intrpltn = {
|
|
'x_a': 0,
|
|
'y_a': 0,
|
|
'x_b': 0,
|
|
'y_b': 0,
|
|
}
|
|
lower_values = cls.search([
|
|
('brix', '<', brix),
|
|
], order=[('brix', 'DESC')], limit=1)
|
|
if not lower_values:
|
|
return None
|
|
intrpltn['x_a'] = lower_values[0].brix
|
|
intrpltn['y_a'] = lower_values[0].density
|
|
|
|
upper_values = cls.search([
|
|
('brix', '>', brix),
|
|
], order=[('brix', 'ASC')], limit=1)
|
|
if not upper_values:
|
|
return None
|
|
intrpltn['x_b'] = upper_values[0].brix
|
|
intrpltn['y_b'] = upper_values[0].density
|
|
|
|
value = (intrpltn['y_a'] + (brix - intrpltn['x_a']) * (
|
|
(intrpltn['y_b'] - intrpltn['y_a']) /
|
|
(intrpltn['x_b'] - intrpltn['x_a'])))
|
|
return value
|
|
|
|
@classmethod
|
|
def brixToSolubleSolids(cls, brix):
|
|
if not brix:
|
|
return None
|
|
brix = float(brix)
|
|
|
|
values = cls.search([
|
|
('brix', '=', brix),
|
|
], limit=1)
|
|
if values:
|
|
return values[0].soluble_solids
|
|
|
|
intrpltn = {
|
|
'x_a': 0,
|
|
'y_a': 0,
|
|
'x_b': 0,
|
|
'y_b': 0,
|
|
}
|
|
lower_values = cls.search([
|
|
('brix', '<', brix),
|
|
], order=[('brix', 'DESC')], limit=1)
|
|
if not lower_values:
|
|
return None
|
|
intrpltn['x_a'] = lower_values[0].brix
|
|
intrpltn['y_a'] = lower_values[0].soluble_solids
|
|
|
|
upper_values = cls.search([
|
|
('brix', '>', brix),
|
|
], order=[('brix', 'ASC')], limit=1)
|
|
if not upper_values:
|
|
return None
|
|
intrpltn['x_b'] = upper_values[0].brix
|
|
intrpltn['y_b'] = upper_values[0].soluble_solids
|
|
|
|
value = (intrpltn['y_a'] + (brix - intrpltn['x_a']) * (
|
|
(intrpltn['y_b'] - intrpltn['y_a']) /
|
|
(intrpltn['x_b'] - intrpltn['x_a'])))
|
|
return value
|
|
|
|
|
|
class LimsUomConversion(ModelSQL, ModelView):
|
|
'Uom Conversion'
|
|
__name__ = 'lims.uom.conversion'
|
|
|
|
initial_uom = fields.Many2One('product.uom', 'Initial UoM', required=True,
|
|
domain=[('category.lims_only_available', '=', True)])
|
|
final_uom = fields.Many2One('product.uom', 'Final UoM', required=True,
|
|
domain=[('category.lims_only_available', '=', True)])
|
|
initial_uom_volume = fields.Boolean('Volume involved in Initial UoM')
|
|
final_uom_volume = fields.Boolean('Volume involved in Final UoM')
|
|
conversion_formula = fields.Char('Conversion formula')
|
|
|
|
@classmethod
|
|
def get_conversion_formula(cls, initial_uom, final_uom):
|
|
if not initial_uom or not final_uom:
|
|
return None
|
|
values = cls.search([
|
|
('initial_uom', '=', initial_uom),
|
|
('final_uom', '=', final_uom),
|
|
])
|
|
if values:
|
|
return values[0].conversion_formula
|
|
return None
|
|
|
|
|
|
class LimsRangeType(ModelSQL, ModelView):
|
|
'Origins'
|
|
__name__ = 'lims.range.type'
|
|
|
|
code = fields.Char('Code', required=True)
|
|
name = fields.Char('Name', required=True)
|
|
use = fields.Selection([
|
|
('results_verification', 'Results verification'),
|
|
('uncertainty_calc', 'Uncertainty calculation'),
|
|
('repeatability_calc', 'Repeatability calculation'),
|
|
('result_range', 'Result and Ranges'),
|
|
], 'Use', sort=False, required=True)
|
|
use_string = use.translated('use')
|
|
by_default = fields.Boolean('By default')
|
|
resultrange_title = fields.Char('Column Title in Results report',
|
|
translate=True, states={'invisible': Eval('use') != 'result_range'},
|
|
depends=['use'])
|
|
resultrange_comments = fields.Char('Comments in Results report',
|
|
translate=True, states={'invisible': Eval('use') != 'result_range'},
|
|
depends=['use'])
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(LimsRangeType, cls).__setup__()
|
|
cls._error_messages.update({
|
|
'default_range_type': 'There is already a default origin'
|
|
' for this use',
|
|
})
|
|
|
|
@staticmethod
|
|
def default_by_default():
|
|
return False
|
|
|
|
@classmethod
|
|
def validate(cls, range_types):
|
|
super(LimsRangeType, cls).validate(range_types)
|
|
for rt in range_types:
|
|
rt.check_default()
|
|
|
|
def check_default(self):
|
|
if self.by_default:
|
|
range_types = self.search([
|
|
('use', '=', self.use),
|
|
('by_default', '=', True),
|
|
('id', '!=', self.id),
|
|
])
|
|
if range_types:
|
|
self.raise_user_error('default_range_type')
|
|
|
|
|
|
class LimsRange(ModelSQL, ModelView):
|
|
'Range'
|
|
__name__ = 'lims.range'
|
|
|
|
range_type = fields.Many2One('lims.range.type', 'Origin', required=True)
|
|
analysis = fields.Many2One('lims.analysis', 'Analysis', required=True)
|
|
product_type = fields.Many2One('lims.product.type', 'Product type',
|
|
required=True)
|
|
matrix = fields.Many2One('lims.matrix', 'Matrix', required=True)
|
|
uom = fields.Many2One('product.uom', 'UoM', required=True,
|
|
domain=[('category.lims_only_available', '=', True)])
|
|
concentration = fields.Char('Concentration', required=True)
|
|
min = fields.Float('Minimum', digits=(16, 3))
|
|
max = fields.Float('Maximum', digits=(16, 3))
|
|
reference = fields.Char('Reference', translate=True)
|
|
min95 = fields.Float('Minimum 95', digits=(16, 3))
|
|
max95 = fields.Float('Maximum 95', digits=(16, 3))
|
|
low_level = fields.Float('Low level', digits=(16, 3),
|
|
states={'required': Bool(Eval('low_level_value'))},
|
|
depends=['low_level_value'])
|
|
middle_level = fields.Float('Middle level', digits=(16, 3),
|
|
states={'required': Bool(Eval('middle_level_value'))},
|
|
depends=['middle_level_value'])
|
|
high_level = fields.Float('High level', digits=(16, 3),
|
|
states={'required': Bool(Eval('high_level_value'))},
|
|
depends=['high_level_value'])
|
|
low_level_value = fields.Float('Low level value', digits=(16, 3))
|
|
middle_level_value = fields.Float('Middle level value', digits=(16, 3))
|
|
high_level_value = fields.Float('High level value', digits=(16, 3))
|
|
factor = fields.Float('Factor', digits=(16, 3))
|
|
low_level_coefficient_variation = fields.Float(
|
|
'Low level coefficient of variation', digits=(16, 3))
|
|
middle_level_coefficient_variation = fields.Float(
|
|
'Middle level coefficient of variation', digits=(16, 3))
|
|
high_level_coefficient_variation = fields.Float(
|
|
'High level coefficient of variation', digits=(16, 3))
|
|
|
|
|
|
class LimsControlTendency(ModelSQL, ModelView):
|
|
'Control Chart Tendency'
|
|
__name__ = 'lims.control.tendency'
|
|
|
|
product_type = fields.Many2One('lims.product.type', 'Product type',
|
|
required=True, states={
|
|
'readonly': Bool(Eval('context', {}).get('readonly', False))})
|
|
matrix = fields.Many2One('lims.matrix', 'Matrix', required=True,
|
|
states={'readonly': Bool(Eval('context', {}).get('readonly', False))})
|
|
fraction_type = fields.Many2One('lims.fraction.type', 'Fraction type',
|
|
required=True, states={
|
|
'readonly': Bool(Eval('context', {}).get('readonly', False))})
|
|
analysis = fields.Many2One('lims.analysis', 'Analysis', required=True,
|
|
states={'readonly': Bool(Eval('context', {}).get('readonly', False))})
|
|
concentration_level = fields.Many2One('lims.concentration.level',
|
|
'Concentration level', states={
|
|
'readonly': Bool(Eval('context', {}).get('readonly', False))})
|
|
mean = fields.Float('Mean', required=True,
|
|
states={'readonly': Bool(Eval('context', {}).get('readonly', False))},
|
|
digits=(16, Eval('digits', 2)), depends=['digits'])
|
|
deviation = fields.Float('Standard Deviation', required=True,
|
|
digits=(16, Eval('digits', 2)), depends=['digits'])
|
|
one_sd = fields.Function(fields.Float('1 SD', digits=(16,
|
|
Eval('digits', 2)), depends=['deviation', 'digits']), 'get_one_sd')
|
|
two_sd = fields.Function(fields.Float('2 SD', digits=(16,
|
|
Eval('digits', 2)), depends=['deviation', 'digits']), 'get_two_sd')
|
|
three_sd = fields.Function(fields.Float('3 SD', digits=(16,
|
|
Eval('digits', 2)), depends=['deviation', 'digits']), 'get_three_sd')
|
|
cv = fields.Function(fields.Float('CV (%)', digits=(16, Eval('digits', 2)),
|
|
depends=['deviation', 'mean', 'digits']), 'get_cv')
|
|
min_cv = fields.Float('Minimum CV (%)', digits=(16, Eval('digits', 2)),
|
|
depends=['digits'])
|
|
max_cv = fields.Float('Maximum CV (%)', digits=(16, Eval('digits', 2)),
|
|
depends=['digits'])
|
|
min_cv_corr_fact = fields.Float('Correction factor for Minimum CV',
|
|
digits=(16, Eval('digits', 2)), depends=['digits'])
|
|
max_cv_corr_fact = fields.Float('Correction factor for Maximum CV',
|
|
digits=(16, Eval('digits', 2)), depends=['digits'])
|
|
one_sd_adj = fields.Function(fields.Float('1 SD Adjusted',
|
|
digits=(16, Eval('digits', 2)), depends=['cv', 'one_sd', 'min_cv',
|
|
'max_cv', 'min_cv_corr_fact', 'max_cv_corr_fact', 'digits']),
|
|
'get_one_sd_adj')
|
|
two_sd_adj = fields.Function(fields.Float('2 SD Adjusted',
|
|
digits=(16, Eval('digits', 2)), depends=['cv', 'two_sd', 'min_cv',
|
|
'max_cv', 'min_cv_corr_fact', 'max_cv_corr_fact', 'digits']),
|
|
'get_two_sd_adj')
|
|
three_sd_adj = fields.Function(fields.Float('3 SD Adjusted',
|
|
digits=(16, Eval('digits', 2)), depends=['cv', 'three_sd', 'min_cv',
|
|
'max_cv', 'min_cv_corr_fact', 'max_cv_corr_fact', 'digits']),
|
|
'get_three_sd_adj')
|
|
ucl = fields.Function(fields.Float('UCL', digits=(16, Eval('digits', 2)),
|
|
depends=['mean', 'three_sd_adj', 'digits']), 'get_ucl')
|
|
uwl = fields.Function(fields.Float('UWL', digits=(16, Eval('digits', 2)),
|
|
depends=['mean', 'two_sd_adj', 'digits']), 'get_uwl')
|
|
upl = fields.Function(fields.Float('UPL', digits=(16, Eval('digits', 2)),
|
|
depends=['mean', 'one_sd_adj', 'digits']), 'get_upl')
|
|
lcl = fields.Function(fields.Float('LCL', digits=(16, Eval('digits', 2)),
|
|
depends=['mean', 'three_sd_adj', 'digits']), 'get_lcl')
|
|
lwl = fields.Function(fields.Float('LWL', digits=(16, Eval('digits', 2)),
|
|
depends=['mean', 'two_sd_adj', 'digits']), 'get_lwl')
|
|
lpl = fields.Function(fields.Float('LPL', digits=(16, Eval('digits', 2)),
|
|
depends=['mean', 'one_sd_adj', 'digits']), 'get_lpl')
|
|
cl = fields.Function(fields.Float('CL', digits=(16, Eval('digits', 2)),
|
|
depends=['mean', 'digits']), 'get_cl')
|
|
details = fields.One2Many('lims.control.tendency.detail', 'tendency',
|
|
'Details', readonly=True)
|
|
rule_1_count = fields.Integer('Rule 1', readonly=True)
|
|
rule_2_count = fields.Integer('Rule 2', readonly=True)
|
|
rule_3_count = fields.Integer('Rule 3', readonly=True)
|
|
rule_4_count = fields.Integer('Rule 4', readonly=True)
|
|
digits = fields.Integer('Digits')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(LimsControlTendency, cls).__setup__()
|
|
cls._order.insert(0, ('rule_4_count', 'DESC'))
|
|
cls._order.insert(1, ('rule_3_count', 'DESC'))
|
|
cls._order.insert(2, ('rule_2_count', 'DESC'))
|
|
cls._order.insert(3, ('rule_1_count', 'DESC'))
|
|
cls._order.insert(4, ('product_type', 'ASC'))
|
|
cls._order.insert(5, ('matrix', 'ASC'))
|
|
cls._order.insert(6, ('analysis', 'ASC'))
|
|
cls._order.insert(7, ('concentration_level', 'ASC'))
|
|
|
|
@staticmethod
|
|
def default_rule_1_count():
|
|
return 0
|
|
|
|
@staticmethod
|
|
def default_rule_2_count():
|
|
return 0
|
|
|
|
@staticmethod
|
|
def default_rule_3_count():
|
|
return 0
|
|
|
|
@staticmethod
|
|
def default_rule_4_count():
|
|
return 0
|
|
|
|
@staticmethod
|
|
def default_digits():
|
|
return 2
|
|
|
|
@staticmethod
|
|
def default_min_cv():
|
|
return 1
|
|
|
|
@staticmethod
|
|
def default_max_cv():
|
|
return 1
|
|
|
|
@staticmethod
|
|
def default_min_cv_corr_fact():
|
|
return 1
|
|
|
|
@staticmethod
|
|
def default_max_cv_corr_fact():
|
|
return 1
|
|
|
|
def get_one_sd(self, name=None):
|
|
return round(self.deviation, self.digits)
|
|
|
|
def get_two_sd(self, name=None):
|
|
return round(self.deviation * 2, self.digits)
|
|
|
|
def get_three_sd(self, name=None):
|
|
return round(self.deviation * 3, self.digits)
|
|
|
|
def get_cv(self, name=None):
|
|
if self.mean:
|
|
return round((self.deviation / self.mean) * 100, self.digits)
|
|
|
|
def get_one_sd_adj(self, name=None):
|
|
if self.cv < self.min_cv:
|
|
return round(self.one_sd, self.digits)
|
|
elif self.cv < self.max_cv:
|
|
if self.min_cv_corr_fact:
|
|
return round(self.one_sd / self.min_cv_corr_fact, self.digits)
|
|
else:
|
|
if self.max_cv_corr_fact:
|
|
return round(self.one_sd / self.max_cv_corr_fact, self.digits)
|
|
|
|
def get_two_sd_adj(self, name=None):
|
|
if self.cv < self.min_cv:
|
|
return round(self.two_sd, self.digits)
|
|
elif self.cv < self.max_cv:
|
|
if self.min_cv_corr_fact:
|
|
return round(self.two_sd / self.min_cv_corr_fact, self.digits)
|
|
else:
|
|
if self.max_cv_corr_fact:
|
|
return round(self.two_sd / self.max_cv_corr_fact, self.digits)
|
|
|
|
def get_three_sd_adj(self, name=None):
|
|
if self.cv < self.min_cv:
|
|
return round(self.three_sd, self.digits)
|
|
elif self.cv < self.max_cv:
|
|
if self.min_cv_corr_fact:
|
|
return round(self.three_sd / self.min_cv_corr_fact,
|
|
self.digits)
|
|
else:
|
|
if self.max_cv_corr_fact:
|
|
return round(self.three_sd / self.max_cv_corr_fact,
|
|
self.digits)
|
|
|
|
def get_ucl(self, name=None):
|
|
return round(self.mean + self.three_sd_adj, self.digits)
|
|
|
|
def get_uwl(self, name=None):
|
|
return round(self.mean + self.two_sd_adj, self.digits)
|
|
|
|
def get_upl(self, name=None):
|
|
return round(self.mean + self.one_sd_adj, self.digits)
|
|
|
|
def get_lcl(self, name=None):
|
|
return round(self.mean - self.three_sd_adj, self.digits)
|
|
|
|
def get_lwl(self, name=None):
|
|
return round(self.mean - self.two_sd_adj, self.digits)
|
|
|
|
def get_lpl(self, name=None):
|
|
return round(self.mean - self.one_sd_adj, self.digits)
|
|
|
|
def get_cl(self, name=None):
|
|
return round(self.mean, self.digits)
|
|
|
|
|
|
class LimsControlTendencyDetail(ModelSQL, ModelView):
|
|
'Control Chart Tendency Detail'
|
|
__name__ = 'lims.control.tendency.detail'
|
|
|
|
tendency = fields.Many2One('lims.control.tendency', 'Tendency',
|
|
ondelete='CASCADE', select=True, required=True)
|
|
notebook_line = fields.Many2One('lims.notebook.line', 'Notebook Line')
|
|
date = fields.Date('Date')
|
|
fraction = fields.Many2One('lims.fraction', 'Fraction')
|
|
device = fields.Many2One('lims.lab.device', 'Device')
|
|
result = fields.Float('Result')
|
|
rule = fields.Char('Rule')
|
|
rules = fields.One2Many('lims.control.tendency.detail.rule',
|
|
'detail', 'Rules')
|
|
rules2 = fields.Function(fields.Char('Rules', depends=['rules']),
|
|
'get_rules2')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(LimsControlTendencyDetail, cls).__setup__()
|
|
cls._order.insert(0, ('date', 'ASC'))
|
|
cls._order.insert(1, ('fraction', 'ASC'))
|
|
cls._order.insert(2, ('device', 'ASC'))
|
|
|
|
def get_rules2(self, name=None):
|
|
rules = ''
|
|
if self.rules:
|
|
rules = ', '.join(str(r.rule) for r in self.rules)
|
|
return rules
|
|
|
|
@classmethod
|
|
def view_attributes(cls):
|
|
return [('/tree', 'colors',
|
|
If(Equal(Eval('rule', ''), '1'), 'green',
|
|
If(Equal(Eval('rule', ''), '2'), 'blue',
|
|
If(Equal(Eval('rule', ''), '3'), 'brown',
|
|
If(Equal(Eval('rule', ''), '4'), 'red', 'black')))))]
|
|
|
|
|
|
class LimsControlTendencyDetailRule(ModelSQL):
|
|
'Control Chart Tendency Detail Rule'
|
|
__name__ = 'lims.control.tendency.detail.rule'
|
|
|
|
detail = fields.Many2One('lims.control.tendency.detail', 'Detail',
|
|
ondelete='CASCADE', select=True, required=True)
|
|
rule = fields.Char('Rule')
|
|
|
|
|
|
class LimsConcentrationLevel(ModelSQL, ModelView):
|
|
'Concentration Level'
|
|
__name__ = 'lims.concentration.level'
|
|
_rec_name = 'description'
|
|
|
|
code = fields.Char('Code', required=True)
|
|
description = fields.Char('Description', required=True)
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(LimsConcentrationLevel, cls).__setup__()
|
|
t = cls.__table__()
|
|
cls._sql_constraints += [
|
|
('code_uniq', Unique(t, t.code),
|
|
'Concentration level code must be unique'),
|
|
]
|
|
|
|
def get_rec_name(self, name):
|
|
if self.code:
|
|
return self.code + ' - ' + self.description
|
|
else:
|
|
return self.description
|
|
|
|
@classmethod
|
|
def search_rec_name(cls, name, clause):
|
|
field = None
|
|
for field in ('code', 'description'):
|
|
records = cls.search([(field,) + tuple(clause[1:])], limit=1)
|
|
if records:
|
|
break
|
|
if records:
|
|
return [(field,) + tuple(clause[1:])]
|
|
return [(cls._rec_name,) + tuple(clause[1:])]
|
|
|
|
|
|
class LimsResultsReport(ModelSQL, ModelView):
|
|
'Results Report'
|
|
__name__ = 'lims.results_report'
|
|
_rec_name = 'number'
|
|
|
|
number = fields.Char('Number', select=True, readonly=True)
|
|
versions = fields.One2Many('lims.results_report.version',
|
|
'results_report', 'Laboratories')
|
|
report_grouper = fields.Integer('Report Grouper')
|
|
generation_type = fields.Char('Generation type')
|
|
cie_fraction_type = fields.Boolean('QA', readonly=True)
|
|
party = fields.Many2One('party.party', 'Party', readonly=True)
|
|
notebook = fields.Many2One('lims.notebook', 'Laboratory notebook')
|
|
report_cache = fields.Binary('Report cache', readonly=True)
|
|
report_format = fields.Char('Report format', readonly=True)
|
|
report_cache_eng = fields.Binary('Report cache', readonly=True)
|
|
report_format_eng = fields.Char('Report format', readonly=True)
|
|
single_sending_report = fields.Function(fields.Boolean(
|
|
'Single sending'), 'get_single_sending_report',
|
|
searcher='search_single_sending_report')
|
|
single_sending_report_ready = fields.Function(fields.Boolean(
|
|
'Single sending Ready'), 'get_single_sending_report_ready')
|
|
english_report = fields.Boolean('English report')
|
|
create_date2 = fields.Function(fields.DateTime('Create Date'),
|
|
'get_create_date2', searcher='search_create_date2')
|
|
write_date2 = fields.DateTime('Write Date', readonly=True)
|
|
attachments = fields.One2Many('ir.attachment', 'resource', 'Attachments')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(LimsResultsReport, cls).__setup__()
|
|
cls._order.insert(0, ('number', 'DESC'))
|
|
cls._error_messages.update({
|
|
'no_sequence': ('There is no results report sequence for '
|
|
'the work year "%s".'),
|
|
'missing_module': 'Missing PyPDF2 module',
|
|
'empty_report': 'The report has not details to print',
|
|
})
|
|
|
|
@staticmethod
|
|
def default_report_grouper():
|
|
return 0
|
|
|
|
@classmethod
|
|
def create(cls, vlist):
|
|
pool = Pool()
|
|
LimsLabWorkYear = pool.get('lims.lab.workyear')
|
|
Sequence = pool.get('ir.sequence.strict')
|
|
|
|
workyear_id = LimsLabWorkYear.find()
|
|
workyear = LimsLabWorkYear(workyear_id)
|
|
sequence = workyear.get_sequence('results_report')
|
|
if not sequence:
|
|
cls.raise_user_error('no_sequence',
|
|
(workyear.rec_name,))
|
|
|
|
vlist = [x.copy() for x in vlist]
|
|
for values in vlist:
|
|
values['number'] = Sequence.get_id(sequence.id)
|
|
return super(LimsResultsReport, cls).create(vlist)
|
|
|
|
@classmethod
|
|
def write(cls, *args):
|
|
actions = iter(args)
|
|
for reports, vals in zip(actions, actions):
|
|
fields_check = cls._get_modified_fields()
|
|
for field in fields_check:
|
|
if field in vals:
|
|
vals['write_date2'] = CurrentTimestamp()
|
|
break
|
|
super(LimsResultsReport, cls).write(*args)
|
|
|
|
@staticmethod
|
|
def _get_modified_fields():
|
|
return [
|
|
'number',
|
|
'versions',
|
|
'report_grouper',
|
|
'generation_type',
|
|
'cie_fraction_type',
|
|
'party',
|
|
'notebook',
|
|
'english_report',
|
|
'attachments',
|
|
]
|
|
|
|
def get_single_sending_report(self, name):
|
|
pool = Pool()
|
|
LimsNotebook = pool.get('lims.notebook')
|
|
|
|
if self.notebook:
|
|
with Transaction().set_user(0):
|
|
notebook = LimsNotebook(self.notebook.id)
|
|
return notebook.fraction.sample.entry.single_sending_report
|
|
return False
|
|
|
|
@classmethod
|
|
def search_single_sending_report(cls, name, clause):
|
|
return [('notebook.fraction.sample.entry.' + name,) +
|
|
tuple(clause[1:])]
|
|
|
|
def get_single_sending_report_ready(self, name):
|
|
pool = Pool()
|
|
LimsNotebook = pool.get('lims.notebook')
|
|
LimsEntryDetailAnalysis = pool.get('lims.entry.detail.analysis')
|
|
|
|
if not self.single_sending_report:
|
|
return False
|
|
with Transaction().set_user(0):
|
|
notebook = LimsNotebook(self.notebook.id)
|
|
if LimsEntryDetailAnalysis.search([
|
|
('fraction', '=', notebook.fraction.id),
|
|
('report', '=', True),
|
|
('report_grouper', '=', self.report_grouper),
|
|
('state', '!=', 'reported'),
|
|
]):
|
|
return False
|
|
return True
|
|
|
|
def get_create_date2(self, name):
|
|
return self.create_date.replace(microsecond=0)
|
|
|
|
@classmethod
|
|
def search_create_date2(cls, name, clause):
|
|
cursor = Transaction().connection.cursor()
|
|
operator_ = clause[1:2][0]
|
|
cursor.execute('SELECT id '
|
|
'FROM "' + cls._table + '" '
|
|
'WHERE create_date' + operator_ + ' %s',
|
|
clause[2:3])
|
|
return [('id', 'in', [x[0] for x in cursor.fetchall()])]
|
|
|
|
@classmethod
|
|
def order_create_date2(cls, tables):
|
|
return cls.create_date.convert_order('create_date', tables, cls)
|
|
|
|
|
|
class LimsResultsReportVersion(ModelSQL, ModelView):
|
|
'Results Report Version'
|
|
__name__ = 'lims.results_report.version'
|
|
_rec_name = 'number'
|
|
|
|
results_report = fields.Many2One('lims.results_report', 'Results Report',
|
|
required=True, ondelete='CASCADE', select=True)
|
|
number = fields.Char('Number', select=True, readonly=True)
|
|
laboratory = fields.Many2One('lims.laboratory', 'Laboratory',
|
|
required=True, readonly=True)
|
|
details = fields.One2Many('lims.results_report.version.detail',
|
|
'report_version', 'Detail lines')
|
|
report_type = fields.Function(fields.Char('Report type'),
|
|
'get_report_type')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(LimsResultsReportVersion, cls).__setup__()
|
|
cls._order.insert(0, ('number', 'DESC'))
|
|
|
|
def get_report_type(self, name):
|
|
LimsResultsReportVersionDetail = Pool().get(
|
|
'lims.results_report.version.detail')
|
|
valid_detail = LimsResultsReportVersionDetail.search([
|
|
('report_version.id', '=', self.id),
|
|
], order=[('id', 'DESC')], limit=1)
|
|
if valid_detail:
|
|
return valid_detail[0].report_type
|
|
return None
|
|
|
|
@classmethod
|
|
def get_number(cls, results_report_id, laboratory_id):
|
|
pool = Pool()
|
|
LimsResultsReport = pool.get('lims.results_report')
|
|
LimsLaboratory = pool.get('lims.laboratory')
|
|
|
|
results_reports = LimsResultsReport.search([
|
|
('id', '=', results_report_id),
|
|
])
|
|
report_number = results_reports[0].number
|
|
|
|
laboratories = LimsLaboratory.search([
|
|
('id', '=', laboratory_id),
|
|
])
|
|
laboratory_code = laboratories[0].code
|
|
|
|
return '%s-%s' % (report_number, laboratory_code)
|
|
|
|
@classmethod
|
|
def create(cls, vlist):
|
|
vlist = [x.copy() for x in vlist]
|
|
for values in vlist:
|
|
values['number'] = cls.get_number(values['results_report'],
|
|
values['laboratory'])
|
|
return super(LimsResultsReportVersion, cls).create(vlist)
|
|
|
|
|
|
class LimsResultsReportVersionDetail(ModelSQL, ModelView):
|
|
'Results Report Version Detail'
|
|
__name__ = 'lims.results_report.version.detail'
|
|
_rec_name = 'report_version'
|
|
|
|
report_version = fields.Many2One('lims.results_report.version',
|
|
'Report', required=True, readonly=True,
|
|
ondelete='CASCADE', select=True)
|
|
laboratory = fields.Function(fields.Many2One('lims.laboratory',
|
|
'Laboratory'), 'get_version_field', searcher='search_version_field')
|
|
number = fields.Char('Version', select=True, readonly=True)
|
|
valid = fields.Boolean('Active', readonly=True)
|
|
state = fields.Selection([
|
|
('draft', 'Draft'),
|
|
('revised', 'Revised'),
|
|
('annulled', 'Annulled'),
|
|
], 'State', readonly=True)
|
|
notebook_lines = fields.One2Many('lims.results_report.version.detail.line',
|
|
'report_version_detail', 'Lines', depends=['state'],
|
|
states={'readonly': Eval('state') != 'draft'})
|
|
report_section = fields.Function(fields.Char('Section'),
|
|
'get_report_section')
|
|
report_type_forced = fields.Selection([
|
|
('none', 'None'),
|
|
('normal', 'Normal'),
|
|
('polisample', 'Polisample'),
|
|
], 'Forced Report type', sort=False, depends=['state'],
|
|
states={'readonly': Eval('state') != 'draft'})
|
|
report_type = fields.Function(fields.Selection([
|
|
('normal', 'Normal'),
|
|
('polisample', 'Polisample'),
|
|
], 'Report type', sort=False), 'on_change_with_report_type')
|
|
report_result_type_forced = fields.Selection([
|
|
('none', 'None'),
|
|
('result', 'Result'),
|
|
('both', 'Both'),
|
|
('result_range', 'Result and Ranges'),
|
|
], 'Forced Result type', sort=False, depends=['state'],
|
|
states={'readonly': Eval('state') != 'draft'})
|
|
report_result_type = fields.Function(fields.Selection([
|
|
('result', 'Result'),
|
|
('both', 'Both'),
|
|
('result_range', 'Result and Ranges'),
|
|
], 'Result type', sort=False), 'on_change_with_report_result_type')
|
|
english_report = fields.Function(fields.Boolean('English report'),
|
|
'get_report_field', searcher='search_report_field')
|
|
signer = fields.Many2One('lims.laboratory.professional', 'Signer',
|
|
states={'readonly': Eval('state') != 'draft'},
|
|
domain=[('id', 'in', Eval('signer_domain'))],
|
|
depends=['state', 'signer_domain'])
|
|
signer_domain = fields.Function(fields.Many2Many(
|
|
'lims.laboratory.professional', None, None, 'Signer domain'),
|
|
'on_change_with_signer_domain')
|
|
comments = fields.Text('Comments', translate=True, depends=['state'],
|
|
states={'readonly': ~Eval('state').in_(['draft', 'revised'])})
|
|
report_cache = fields.Binary('Report cache', readonly=True)
|
|
report_format = fields.Char('Report format', readonly=True)
|
|
report_cache_eng = fields.Binary('Report cache', readonly=True)
|
|
report_format_eng = fields.Char('Report format', readonly=True)
|
|
report_cache_odt = fields.Binary('Transcription Report cache',
|
|
readonly=True)
|
|
report_format_odt = fields.Char('Transcription Report format',
|
|
readonly=True)
|
|
report_cache_odt_eng = fields.Binary('Transcription Report cache',
|
|
readonly=True)
|
|
report_format_odt_eng = fields.Char('Transcription Report format',
|
|
readonly=True)
|
|
annulment_reason = fields.Text('Annulment reason', readonly=True)
|
|
annulment_date = fields.DateTime('Annulment date', readonly=True)
|
|
date = fields.Function(fields.Date('Date'), 'get_date',
|
|
searcher='search_date')
|
|
party = fields.Function(fields.Many2One('party.party', 'Party'),
|
|
'get_report_field', searcher='search_report_field')
|
|
cie_fraction_type = fields.Function(fields.Boolean('QA'),
|
|
'get_report_field', searcher='search_report_field')
|
|
create_date2 = fields.Function(fields.DateTime('Create Date'),
|
|
'get_create_date2', searcher='search_create_date2')
|
|
write_date2 = fields.Function(fields.DateTime('Write Date'),
|
|
'get_write_date2', searcher='search_write_date2')
|
|
resultrange_origin = fields.Many2One('lims.range.type', 'Origin',
|
|
domain=[('use', '=', 'result_range')],
|
|
depends=['report_result_type', 'state'], states={
|
|
'invisible': Eval('report_result_type') != 'result_range',
|
|
'required': Eval('report_result_type') == 'result_range',
|
|
'readonly': Eval('state') != 'draft',
|
|
})
|
|
fraction_comments = fields.Function(fields.Text('Fraction comments'),
|
|
'get_fraction_comments')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(LimsResultsReportVersionDetail, cls).__setup__()
|
|
cls._order.insert(0, ('report_version', 'DESC'))
|
|
cls._order.insert(1, ('number', 'DESC'))
|
|
cls._buttons.update({
|
|
'revise': {
|
|
'invisible': (Eval('state') != 'draft'),
|
|
},
|
|
'annul': {
|
|
'invisible': Or(Eval('state') != 'revised', ~Eval('valid')),
|
|
},
|
|
'revise_all_lang': {
|
|
'invisible': Not(If(Bool(Eval('english_report')),
|
|
Bool(And(
|
|
~Bool(Eval('report_cache_eng')),
|
|
Bool(Eval('report_cache')),
|
|
)),
|
|
Bool(And(
|
|
~Bool(Eval('report_cache')),
|
|
Bool(Eval('report_cache_eng')),
|
|
)),
|
|
)),
|
|
},
|
|
})
|
|
cls._error_messages.update({
|
|
'delete_detail': ('You can not delete a detail that is not in '
|
|
'draft state'),
|
|
'multiple_reports': 'Please, select only one report to print',
|
|
'annulled_report': 'This report is annulled',
|
|
'empty_report': 'The report has not lines to print',
|
|
'replace_number': u'Supplants the Results Report N° %s',
|
|
'quantification_limit': '< LoQ = %s',
|
|
'detection_limit': '(LoD = %s %s)',
|
|
'uncertainty': u'(U± %s %s)',
|
|
'obs_uncert': 'U = Uncertainty.',
|
|
'neg': 'Negative',
|
|
'pos': 'Positive',
|
|
'nd': 'Not detected',
|
|
'pre': 'Presence',
|
|
'abs': 'Absence',
|
|
'enac_all_acredited': ('Uncertainty for the analysis covered '
|
|
'by the Accreditation is available.'),
|
|
'enac_acredited': ('The analysis marked with * are not '
|
|
'covered by the Accreditation. Uncertainty for the '
|
|
'analysis covered by the Accreditation is available.'),
|
|
'concentration_label_1': ('(Expressed at the concentration of '
|
|
'the received sample)'),
|
|
'concentration_label_2': u'(Expressed at %s° Brix)',
|
|
'concentration_label_3': '(Expressed at %s)',
|
|
'final_unit_label_1': 'Expressed at %s %% Alcohol',
|
|
'final_unit_label_2': 'Expressed at %s',
|
|
'final_unit_label_3': 'Expressed at %s Bx',
|
|
'final_unit_label_4': 'Expressed at dry matter',
|
|
'obs_ql': 'LoQ= Limit of Quantitation.',
|
|
'obs_dl': 'LoD= Limit of Detection.',
|
|
'caa_min': 'min: %s',
|
|
'caa_max': 'max: %s',
|
|
'obs_rm_c_f': ('Elements results are reported without recovery '
|
|
'correction.'),
|
|
'data_not_specified': 'NOT SPECIFIED BY THE CLIENT',
|
|
})
|
|
|
|
@staticmethod
|
|
def default_report_type_forced():
|
|
return 'none'
|
|
|
|
@staticmethod
|
|
def default_report_result_type_forced():
|
|
return 'none'
|
|
|
|
@classmethod
|
|
def get_next_number(cls, report_version_id, d_count):
|
|
detail_number = cls.search_count([
|
|
('report_version', '=', report_version_id),
|
|
])
|
|
detail_number += d_count
|
|
return '%s' % detail_number
|
|
|
|
@classmethod
|
|
def create(cls, vlist):
|
|
vlist = [x.copy() for x in vlist]
|
|
d_count = {}
|
|
for values in vlist:
|
|
key = values['report_version']
|
|
if not key in d_count:
|
|
d_count[key] = 0
|
|
d_count[key] += 1
|
|
values['number'] = cls.get_next_number(key, d_count[key])
|
|
return super(LimsResultsReportVersionDetail, cls).create(vlist)
|
|
|
|
@staticmethod
|
|
def default_valid():
|
|
return False
|
|
|
|
@staticmethod
|
|
def default_state():
|
|
return 'draft'
|
|
|
|
def get_report_section(self, name):
|
|
if self.laboratory:
|
|
return self.laboratory.section
|
|
return None
|
|
|
|
@fields.depends('report_type_forced')
|
|
def on_change_with_report_type(self, name=None):
|
|
if self.report_type_forced != 'none':
|
|
return self.report_type_forced
|
|
report_type = {
|
|
'normal': 0,
|
|
'polisample': 0,
|
|
}
|
|
cursor = Transaction().connection.cursor()
|
|
|
|
cursor.execute('SELECT COUNT(*), t.report_type '
|
|
'FROM lims_results_report_version_detail_l d, '
|
|
'lims_notebook_line l, lims_typification t, '
|
|
'lims_notebook n, lims_fraction f, lims_sample s '
|
|
'WHERE d.report_version_detail = %s '
|
|
'AND d.notebook_line = l.id '
|
|
'AND s.product_type = t.product_type '
|
|
'AND s.matrix = t.matrix '
|
|
'AND l.analysis = t.analysis '
|
|
'AND l.method = t.method '
|
|
'AND t.valid = true '
|
|
'AND l.notebook = n.id '
|
|
'AND n.fraction = f.id '
|
|
'AND f.sample = s.id '
|
|
'GROUP BY t.report_type',
|
|
(self.id, ))
|
|
res = cursor.fetchall()
|
|
for type_ in res:
|
|
if type_[0]:
|
|
report_type[type_[1]] = type_[0]
|
|
|
|
if report_type['polisample'] > report_type['normal']:
|
|
return 'polisample'
|
|
return 'normal'
|
|
|
|
@fields.depends('report_result_type_forced')
|
|
def on_change_with_report_result_type(self, name=None):
|
|
if self.report_result_type_forced != 'none':
|
|
return self.report_result_type_forced
|
|
report_res_type = {
|
|
'result': 0,
|
|
'both': 0,
|
|
}
|
|
cursor = Transaction().connection.cursor()
|
|
|
|
cursor.execute('SELECT COUNT(*), t.report_result_type '
|
|
'FROM lims_results_report_version_detail_l d, '
|
|
'lims_notebook_line l, lims_typification t, '
|
|
'lims_notebook n, lims_fraction f, lims_sample s '
|
|
'WHERE d.report_version_detail = %s '
|
|
'AND d.notebook_line = l.id '
|
|
'AND s.product_type = t.product_type '
|
|
'AND s.matrix = t.matrix '
|
|
'AND l.analysis = t.analysis '
|
|
'AND l.method = t.method '
|
|
'AND t.valid = true '
|
|
'AND l.notebook = n.id '
|
|
'AND n.fraction = f.id '
|
|
'AND f.sample = s.id '
|
|
'GROUP BY t.report_result_type',
|
|
(self.id, ))
|
|
res = cursor.fetchall()
|
|
for type_ in res:
|
|
if type_[0]:
|
|
report_res_type[type_[1]] = type_[0]
|
|
|
|
if report_res_type['both'] > report_res_type['result']:
|
|
return 'both'
|
|
return 'result'
|
|
|
|
@fields.depends('report_result_type_forced', 'resultrange_origin')
|
|
def on_change_report_result_type_forced(self):
|
|
pool = Pool()
|
|
LimsRangeType = pool.get('lims.range.type')
|
|
|
|
if (self.report_result_type_forced == 'result_range'
|
|
and not self.resultrange_origin):
|
|
ranges = LimsRangeType.search([
|
|
('use', '=', 'result_range'),
|
|
('by_default', '=', True),
|
|
])
|
|
if ranges:
|
|
self.resultrange_origin = ranges[0].id
|
|
|
|
@fields.depends('laboratory')
|
|
def on_change_with_signer_domain(self, name=None):
|
|
pool = Pool()
|
|
LimsUserLaboratory = pool.get('lims.user-laboratory')
|
|
LimsLaboratoryProfessional = pool.get('lims.laboratory.professional')
|
|
|
|
if not self.laboratory:
|
|
return []
|
|
users = LimsUserLaboratory.search([
|
|
('laboratory', '=', self.laboratory.id),
|
|
])
|
|
if not users:
|
|
return []
|
|
professionals = LimsLaboratoryProfessional.search([
|
|
('party.lims_user', 'in', [u.user.id for u in users]),
|
|
('role', '!=', ''),
|
|
])
|
|
if not professionals:
|
|
return []
|
|
return [p.id for p in professionals]
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
def revise(cls, details):
|
|
LimsResultsReportVersionDetailLine = Pool().get(
|
|
'lims.results_report.version.detail.line')
|
|
|
|
cls.revise_notebook_lines(details)
|
|
for detail in details:
|
|
defaults = {
|
|
'state': 'revised',
|
|
'valid': True,
|
|
}
|
|
valid_details = cls.search([
|
|
('report_version', '=', detail.report_version.id),
|
|
('valid', '=', True),
|
|
])
|
|
if valid_details:
|
|
vd_ids = []
|
|
notebook_lines = []
|
|
for vd in valid_details:
|
|
vd_ids.append(vd.id)
|
|
for nline in vd.notebook_lines:
|
|
notebook_lines.append({
|
|
'notebook_line': nline.notebook_line.id,
|
|
})
|
|
if notebook_lines:
|
|
defaults['notebook_lines'] = [('create', notebook_lines)]
|
|
|
|
cls.write(valid_details, {
|
|
'valid': False,
|
|
})
|
|
old_lines = LimsResultsReportVersionDetailLine.search([
|
|
('report_version_detail.id', 'in', vd_ids),
|
|
])
|
|
LimsResultsReportVersionDetailLine.delete(old_lines)
|
|
|
|
cls.write([detail], defaults)
|
|
detail.generate_report()
|
|
|
|
@classmethod
|
|
def revise_notebook_lines(cls, details):
|
|
pool = Pool()
|
|
LimsNotebookLine = pool.get('lims.notebook.line')
|
|
LimsEntryDetailAnalysis = pool.get('lims.entry.detail.analysis')
|
|
|
|
for detail in details:
|
|
revised_lines = []
|
|
revised_entry_details = []
|
|
for nline in detail.notebook_lines:
|
|
revised_lines.append(nline.notebook_line.id)
|
|
revised_entry_details.append(
|
|
nline.notebook_line.analysis_detail.id)
|
|
|
|
notebook_lines = LimsNotebookLine.search([
|
|
('id', 'in', revised_lines),
|
|
])
|
|
if notebook_lines:
|
|
LimsNotebookLine.write(notebook_lines, {
|
|
'results_report': detail.report_version.results_report.id,
|
|
})
|
|
|
|
entry_details = LimsEntryDetailAnalysis.search([
|
|
('id', 'in', revised_entry_details),
|
|
])
|
|
if entry_details:
|
|
LimsEntryDetailAnalysis.write(entry_details, {
|
|
'state': 'reported',
|
|
})
|
|
|
|
@classmethod
|
|
@ModelView.button_action('lims.wiz_lims_results_report_annulation')
|
|
def annul(cls, details):
|
|
pass
|
|
|
|
@classmethod
|
|
def annul_notebook_lines(cls, details):
|
|
pool = Pool()
|
|
LimsNotebookLine = pool.get('lims.notebook.line')
|
|
LimsEntryDetailAnalysis = pool.get('lims.entry.detail.analysis')
|
|
|
|
for detail in details:
|
|
annulled_lines = []
|
|
annulled_entry_details = []
|
|
for nline in detail.notebook_lines:
|
|
annulled_lines.append(nline.notebook_line.id)
|
|
annulled_entry_details.append(
|
|
nline.notebook_line.analysis_detail.id)
|
|
|
|
notebook_lines = LimsNotebookLine.search([
|
|
('id', 'in', annulled_lines),
|
|
('results_report', '=',
|
|
detail.report_version.results_report.id),
|
|
])
|
|
if notebook_lines:
|
|
LimsNotebookLine.write(notebook_lines, {
|
|
'results_report': None,
|
|
})
|
|
|
|
entry_details = LimsEntryDetailAnalysis.search([
|
|
('id', 'in', annulled_entry_details),
|
|
])
|
|
if entry_details:
|
|
LimsEntryDetailAnalysis.write(entry_details, {
|
|
'state': 'done',
|
|
})
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
def revise_all_lang(cls, details):
|
|
for detail in details:
|
|
detail.generate_report()
|
|
|
|
def generate_report(self):
|
|
pool = Pool()
|
|
LimsResultReport = pool.get('lims.result_report', type='report')
|
|
LimsResultReportTranscription = pool.get(
|
|
'lims.result_report.transcription', type='report')
|
|
|
|
LimsResultReport.execute([self.id], {
|
|
'english_report': self.english_report,
|
|
})
|
|
LimsResultReportTranscription.execute([self.id], {
|
|
'english_report': self.english_report,
|
|
})
|
|
|
|
def get_date(self, name):
|
|
pool = Pool()
|
|
Company = pool.get('company.company')
|
|
|
|
date = self.write_date if self.write_date else self.create_date
|
|
company_id = Transaction().context.get('company')
|
|
if company_id:
|
|
date = Company(company_id).convert_timezone_datetime(date)
|
|
return date.date()
|
|
|
|
@classmethod
|
|
def search_date(cls, name, clause):
|
|
pool = Pool()
|
|
Company = pool.get('company.company')
|
|
cursor = Transaction().connection.cursor()
|
|
|
|
timezone = None
|
|
company_id = Transaction().context.get('company')
|
|
if company_id:
|
|
timezone = Company(company_id).timezone
|
|
timezone_datetime = ('COALESCE(write_date, create_date)::timestamp'
|
|
' AT TIME ZONE \'UTC\'')
|
|
if timezone:
|
|
timezone_datetime += ' AT TIME ZONE \'' + timezone + '\''
|
|
|
|
operator_ = clause[1:2][0]
|
|
cursor.execute('SELECT id '
|
|
'FROM "' + cls._table + '" '
|
|
'WHERE (' + timezone_datetime + ')::date '
|
|
+ operator_ + ' %s::date', clause[2:3])
|
|
return [('id', 'in', [x[0] for x in cursor.fetchall()])]
|
|
|
|
@classmethod
|
|
def get_version_field(cls, details, names):
|
|
result = {}
|
|
for name in names:
|
|
result[name] = {}
|
|
for d in details:
|
|
field = getattr(d.report_version, name, None)
|
|
result[name][d.id] = field.id if field else None
|
|
return result
|
|
|
|
@classmethod
|
|
def search_version_field(cls, name, clause):
|
|
return [('report_version.' + name,) + tuple(clause[1:])]
|
|
|
|
@classmethod
|
|
def get_report_field(cls, details, names):
|
|
result = {}
|
|
for name in names:
|
|
result[name] = {}
|
|
if name in ('cie_fraction_type', 'english_report'):
|
|
for d in details:
|
|
field = getattr(d.report_version.results_report, name,
|
|
False)
|
|
result[name][d.id] = field
|
|
else:
|
|
for d in details:
|
|
field = getattr(d.report_version.results_report, name,
|
|
None)
|
|
result[name][d.id] = field.id if field else None
|
|
return result
|
|
|
|
@classmethod
|
|
def search_report_field(cls, name, clause):
|
|
return [('report_version.results_report.' + name,) + tuple(clause[1:])]
|
|
|
|
@classmethod
|
|
def get_create_date2(cls, details, name):
|
|
result = {}
|
|
for d in details:
|
|
create_date = getattr(d, 'create_date', None)
|
|
result[d.id] = (create_date.replace(microsecond=0)
|
|
if create_date else None)
|
|
return result
|
|
|
|
@classmethod
|
|
def search_create_date2(cls, name, clause):
|
|
cursor = Transaction().connection.cursor()
|
|
operator_ = clause[1:2][0]
|
|
cursor.execute('SELECT id '
|
|
'FROM "' + cls._table + '" '
|
|
'WHERE create_date' + operator_ + ' %s',
|
|
clause[2:3])
|
|
return [('id', 'in', [x[0] for x in cursor.fetchall()])]
|
|
|
|
@classmethod
|
|
def order_create_date2(cls, tables):
|
|
return cls.create_date.convert_order('create_date', tables, cls)
|
|
|
|
@classmethod
|
|
def get_write_date2(cls, details, name):
|
|
result = {}
|
|
for d in details:
|
|
write_date = getattr(d, 'write_date', None)
|
|
result[d.id] = (write_date.replace(microsecond=0)
|
|
if write_date else None)
|
|
return result
|
|
|
|
@classmethod
|
|
def search_write_date2(cls, name, clause):
|
|
cursor = Transaction().connection.cursor()
|
|
operator_ = clause[1:2][0]
|
|
cursor.execute('SELECT id '
|
|
'FROM "' + cls._table + '" '
|
|
'WHERE write_date' + operator_ + ' %s',
|
|
clause[2:3])
|
|
return [('id', 'in', [x[0] for x in cursor.fetchall()])]
|
|
|
|
@classmethod
|
|
def order_write_date2(cls, tables):
|
|
return cls.write_date.convert_order('write_date', tables, cls)
|
|
|
|
@classmethod
|
|
def delete(cls, details):
|
|
cls.check_delete(details)
|
|
super(LimsResultsReportVersionDetail, cls).delete(details)
|
|
|
|
@classmethod
|
|
def check_delete(cls, details):
|
|
for detail in details:
|
|
if detail.state != 'draft':
|
|
cls.raise_user_error('delete_detail')
|
|
|
|
@classmethod
|
|
def get_fraction_comments(cls, details, name):
|
|
result = {}
|
|
for d in details:
|
|
result[d.id] = None
|
|
notebook = getattr(d.report_version.results_report,
|
|
'notebook', None)
|
|
if notebook:
|
|
result[d.id] = getattr(notebook, 'fraction_comments')
|
|
return result
|
|
|
|
@classmethod
|
|
def view_attributes(cls):
|
|
return [
|
|
('/tree', 'colors',
|
|
If(Len(Eval('fraction_comments')) > 0, 'blue', 'black')),
|
|
]
|
|
|
|
|
|
class LimsResultsReportVersionDetailLine(ModelSQL, ModelView):
|
|
'Results Report Version Detail Line'
|
|
__name__ = 'lims.results_report.version.detail.line'
|
|
_table = 'lims_results_report_version_detail_l'
|
|
|
|
report_version_detail = fields.Many2One(
|
|
'lims.results_report.version.detail', 'Report Detail',
|
|
required=True, ondelete='CASCADE', select=True)
|
|
notebook_line = fields.Many2One('lims.notebook.line', 'Notebook Line',
|
|
required=True)
|
|
notebook = fields.Function(fields.Many2One('lims.notebook',
|
|
'Laboratory notebook'), 'get_nline_field')
|
|
party = fields.Function(fields.Many2One('party.party', 'Party'),
|
|
'get_nline_field')
|
|
analysis = fields.Function(fields.Many2One('lims.analysis', 'Analysis'),
|
|
'get_nline_field')
|
|
repetition = fields.Function(fields.Integer('Repetition'),
|
|
'get_nline_field')
|
|
start_date = fields.Function(fields.Date('Start date'), 'get_nline_field')
|
|
end_date = fields.Function(fields.Date('End date'), 'get_nline_field')
|
|
laboratory = fields.Function(fields.Many2One('lims.laboratory',
|
|
'Laboratory'), 'get_nline_field')
|
|
method = fields.Function(fields.Many2One('lims.lab.method', 'Method'),
|
|
'get_nline_field')
|
|
device = fields.Function(fields.Many2One('lims.lab.device', 'Device'),
|
|
'get_nline_field')
|
|
analysis_origin = fields.Function(fields.Char('Analysis origin'),
|
|
'get_nline_field')
|
|
urgent = fields.Function(fields.Boolean('Urgent'), 'get_nline_field')
|
|
priority = fields.Function(fields.Integer('Priority'), 'get_nline_field')
|
|
report_date = fields.Function(fields.Date('Date agreed for result'),
|
|
'get_nline_field')
|
|
result_modifier = fields.Function(fields.Selection([
|
|
('eq', '='),
|
|
('low', '<'),
|
|
('nd', 'nd'),
|
|
('na', 'na'),
|
|
('pos', 'Positive'),
|
|
('neg', 'Negative'),
|
|
('ni', 'ni'),
|
|
('abs', 'Absence'),
|
|
('pre', 'Presence'),
|
|
], 'Result modifier'), 'get_nline_field')
|
|
converted_result_modifier = fields.Function(fields.Selection([
|
|
('eq', '='),
|
|
('low', '<'),
|
|
('nd', 'nd'),
|
|
('na', 'na'),
|
|
('pos', 'Positive'),
|
|
('neg', 'Negative'),
|
|
('ni', 'ni'),
|
|
], 'Converted result modifier'), 'get_nline_field')
|
|
result = fields.Function(fields.Char('Result'), 'get_nline_field')
|
|
converted_result = fields.Function(fields.Char('Converted result'),
|
|
'get_nline_field')
|
|
initial_unit = fields.Function(fields.Many2One('product.uom',
|
|
'Initial unit'), 'get_nline_field')
|
|
final_unit = fields.Function(fields.Many2One('product.uom',
|
|
'Final unit'), 'get_nline_field')
|
|
comments = fields.Function(fields.Text('Entry comments'),
|
|
'get_nline_field')
|
|
literal_result = fields.Function(fields.Char('Literal result'),
|
|
'get_nline_field')
|
|
|
|
@classmethod
|
|
def get_nline_field(cls, details, names):
|
|
result = {}
|
|
for name in names:
|
|
result[name] = {}
|
|
if name in ('notebook', 'party', 'analysis', 'laboratory',
|
|
'method', 'device', 'initial_unit', 'final_unit'):
|
|
for d in details:
|
|
field = getattr(d.notebook_line, name, None)
|
|
result[name][d.id] = field.id if field else None
|
|
else:
|
|
for d in details:
|
|
result[name][d.id] = getattr(d.notebook_line, name, None)
|
|
return result
|
|
|
|
|
|
class LimsSampleProducer(ModelSQL, ModelView):
|
|
'Sample Producer'
|
|
__name__ = 'lims.sample.producer'
|
|
|
|
party = fields.Many2One('party.party', 'Party', required=True)
|
|
name = fields.Char('Name', required=True)
|
|
|
|
|
|
class LimsPlanification(ModelSQL):
|
|
'Planification'
|
|
__name__ = 'lims.planification'
|