kalenislims/lims/sample.py

6905 lines
262 KiB
Python
Raw Normal View History

# -*- coding: utf-8 -*-
# This file is part of lims module for Tryton.
# The COPYRIGHT file at the top level of this repository contains
# the full copyright notices and license terms.
import logging
import operator
from datetime import datetime
from dateutil.relativedelta import relativedelta
from decimal import Decimal
2020-07-17 22:40:45 +02:00
from sql.conditionals import Case
2020-05-05 04:58:54 +02:00
from trytond.model import ModelView, ModelSQL, fields, Unique, DictSchemaMixin
from trytond.wizard import Wizard, StateTransition, StateView, StateAction, \
Button
from trytond.pool import Pool
from trytond.pyson import PYSONEncoder, Eval, Equal, Bool, Not, Or, If
from trytond.transaction import Transaction
from trytond.report import Report
2019-07-23 23:27:33 +02:00
from trytond.exceptions import UserError
from trytond.i18n import gettext
from trytond.rpc import RPC
class Zone(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):
2020-08-06 19:52:36 +02:00
super().__setup__()
t = cls.__table__()
cls._sql_constraints += [
('code_uniq', Unique(t, t.code),
'lims.msg_zone_code_unique_id'),
]
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 Variety(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):
2020-08-06 19:52:36 +02:00
super().__setup__()
t = cls.__table__()
cls._sql_constraints += [
('code_uniq', Unique(t, t.code),
'lims.msg_variety_code_unique_id'),
]
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 MatrixVariety(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 PackagingIntegrity(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):
2020-08-06 19:52:36 +02:00
super().__setup__()
t = cls.__table__()
cls._sql_constraints += [
('code_uniq', Unique(t, t.code),
'lims.msg_packaging_integrity_code_unique_id'),
]
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 PackagingType(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)
2020-06-10 16:38:54 +02:00
capacity = fields.Float('Capacity')
capacity_uom = fields.Many2One('product.uom', 'Capacity UoM',
domain=[('category.lims_only_available', '=', True)])
@classmethod
def __setup__(cls):
2020-08-06 19:52:36 +02:00
super().__setup__()
t = cls.__table__()
cls._sql_constraints += [
('code_uniq', Unique(t, t.code),
'lims.msg_packaging_type_code_unique_id'),
]
def get_rec_name(self, name):
2020-06-10 16:38:54 +02:00
rec_name = '%s - %s' % (self.code, self.description)
if self.capacity and self.capacity_uom:
rec_name += ' (%s %s)' % (
str(self.capacity), self.capacity_uom.symbol)
return rec_name
@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 FractionType(ModelSQL, ModelView):
'Fraction Type'
__name__ = 'lims.fraction.type'
_rec_name = 'description'
2018-06-19 03:42:32 +02:00
code = fields.Char('Code', required=True, translate=True)
description = fields.Char('Description', required=True, translate=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')
plannable = fields.Boolean('Plannable', select=True)
2020-06-25 17:29:27 +02:00
cie_fraction_type = fields.Boolean('Available for Blind Samples')
without_services = fields.Boolean('Allows entries without services')
default_package_type = fields.Many2One('lims.packaging.type',
'Default Package type')
default_fraction_state = fields.Many2One('lims.packaging.integrity',
'Default Fraction state')
default_storage_location = fields.Many2One('stock.location',
'Default Storage location', domain=[('type', '=', 'storage')])
@classmethod
def __setup__(cls):
2020-08-06 19:52:36 +02:00
super().__setup__()
t = cls.__table__()
cls._order.insert(0, ('code', 'ASC'))
cls._sql_constraints += [
('code_uniq', Unique(t, t.code),
'lims.msg_fraction_type_code_unique_id'),
]
@staticmethod
def default_requalify():
return False
@staticmethod
def default_control_charts():
return False
@staticmethod
def default_report():
return True
@staticmethod
def default_plannable():
return True
2020-06-25 17:29:27 +02:00
@staticmethod
def default_cie_fraction_type():
return False
@staticmethod
def default_without_services():
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 copy(cls, records, default=None):
if default is None:
default = {}
current_default = default.copy()
new_records = []
for record in records:
current_default['code'] = '%s (copy)' % record.code
new_record, = super().copy([record], default=current_default)
new_records.append(new_record)
return new_records
class SampleProducer(ModelSQL, ModelView):
'Sample Producer'
__name__ = 'lims.sample.producer'
party = fields.Many2One('party.party', 'Party', required=True)
name = fields.Char('Name', required=True)
2020-05-05 04:58:54 +02:00
class SampleAttribute(DictSchemaMixin, ModelSQL, ModelView):
'Sample Attribute'
__name__ = 'lims.sample.attribute'
class Service(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')
2021-01-28 22:41:27 +01:00
sample = fields.Function(fields.Many2One('lims.sample', 'Sample',
2021-06-01 00:45:06 +02:00
states={'readonly': True}),
'get_fraction_field', setter='set_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, select=True, depends=['analysis_domain'],
2018-11-25 18:41:58 +01:00
domain=['OR', ('id', '=', Eval('analysis')),
('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')
2019-03-19 14:44:08 +01:00
urgent = fields.Boolean('Urgent')
priority = fields.Integer('Priority')
estimated_waiting_laboratory = fields.Integer(
'Number of days for Laboratory',
states={'readonly': Or(
Bool(Eval('context', {}).get('readonly', True)),
~Eval('report_date_readonly'))},
depends=['report_date_readonly'])
estimated_waiting_report = fields.Integer(
'Number of days for Reporting',
states={'readonly': Or(
Bool(Eval('context', {}).get('readonly', True)),
~Eval('report_date_readonly'))},
depends=['report_date_readonly'])
laboratory_date = fields.Date('Laboratory deadline',
states={'readonly': Or(
Bool(Eval('context', {}).get('readonly', True)),
Bool(Eval('report_date_readonly')))},
depends=['report_date_readonly'])
report_date = fields.Date('Date agreed for result',
states={'readonly': Or(
Bool(Eval('context', {}).get('readonly', True)),
Bool(Eval('report_date_readonly')))},
depends=['report_date_readonly'])
report_date_readonly = fields.Function(fields.Boolean(
'Report deadline Readonly'), 'get_report_date_readonly')
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',
2018-11-25 18:41:58 +01:00
domain=['OR', ('id', '=', Eval('method')),
('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',
2020-02-26 23:04:15 +01:00
domain=['OR', ('id', '=', Eval('device')),
('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 Report')
not_divided_message = fields.Char('Message', readonly=True,
states={'invisible': Not(Bool(Eval('not_divided_message')))})
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')
icon = fields.Function(fields.Char("Icon"), 'get_icon')
planned = fields.Function(fields.Boolean('Planned'), 'get_planned',
searcher='search_planned')
annulled = fields.Boolean('Annulled', states={'readonly': True})
@classmethod
def __setup__(cls):
2020-08-06 19:52:36 +02:00
super().__setup__()
cls._order.insert(0, ('number', 'DESC'))
@staticmethod
def default_urgent():
return False
@staticmethod
def default_priority():
return 0
@staticmethod
def default_divide():
return False
@staticmethod
def default_annulled():
return False
@classmethod
def check_duplicated_analysis(cls, new_services):
"""
Checks that the new service is not already loaded for the sample
"""
cursor = Transaction().connection.cursor()
pool = Pool()
Fraction = pool.get('lims.fraction')
EntryDetailAnalysis = pool.get('lims.entry.detail.analysis')
Analysis = pool.get('lims.analysis')
Typification = pool.get('lims.typification')
existing_analysis = []
for new_service in new_services:
fraction = Fraction(new_service['fraction'])
details = EntryDetailAnalysis.search([
('fraction', '=', fraction.id),
('state', '!=', 'annulled'),
])
for d in details:
existing_analysis.append([d.analysis.id, d.method.id])
new_analysis = [(new_service['analysis'], new_service['method'])]
new_analysis.extend(Analysis.get_included_analysis_method(
new_service['analysis']))
new_analysis = [list(a) for a in new_analysis]
for a in new_analysis:
if a[1]:
continue
cursor.execute('SELECT method '
'FROM "' + Typification._table + '" '
'WHERE product_type = %s '
'AND matrix = %s '
'AND analysis = %s '
'AND valid IS TRUE '
'AND by_default IS TRUE',
(fraction.product_type.id, fraction.matrix.id, a[0]))
res = cursor.fetchone()
if res:
a[1] = res[0]
for a in new_analysis:
if a in existing_analysis:
raise UserError(gettext(
'lims.msg_duplicated_analysis_service',
analysis=Analysis(a[0]).rec_name,
fraction=fraction.rec_name,
))
existing_analysis.extend(new_analysis)
@classmethod
def create(cls, vlist):
pool = Pool()
LabWorkYear = pool.get('lims.lab.workyear')
EntryDetailAnalysis = pool.get('lims.entry.detail.analysis')
2020-07-17 22:40:45 +02:00
Sample = pool.get('lims.sample')
workyear_id = LabWorkYear.find()
workyear = LabWorkYear(workyear_id)
sequence = workyear.get_sequence('service')
if not sequence:
2019-07-23 23:27:33 +02:00
raise UserError(gettext('lims.msg_no_service_sequence',
work_year=workyear.rec_name))
vlist = [x.copy() for x in vlist]
cls.check_duplicated_analysis(vlist)
for values in vlist:
values['number'] = sequence.get()
2020-08-06 19:52:36 +02:00
services = super().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)
EntryDetailAnalysis.write(analysis_detail, {
'state': 'unplanned',
})
# from lims_account_invoice
if hasattr(aditional_services[0].fraction.type,
'invoiceable'):
for aditional_service in aditional_services:
2021-08-18 21:09:12 +02:00
aditional_service.create_invoice_line()
fractions_ids = list(set(s.fraction.id for s in services))
cls.set_shared_fraction(fractions_ids)
2020-07-17 22:40:45 +02:00
sample_ids = list(set(s.sample.id for s in services))
Sample.update_samples_state(sample_ids)
return services
@classmethod
def write(cls, *args):
2020-07-17 22:40:45 +02:00
Sample = Pool().get('lims.sample')
2020-08-06 19:52:36 +02:00
super().write(*args)
actions = iter(args)
for services, vals in zip(actions, actions):
if vals.get('not_divided_message'):
cls.write(services, {'not_divided_message': None})
check_duplicated = False
for field in ('analysis', 'method'):
if vals.get(field):
check_duplicated = True
break
if check_duplicated:
cls.check_duplicated_analysis([{
'fraction': s.fraction.id,
'analysis': s.analysis.id,
'method': s.method.id,
} for s in services])
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)
fractions_ids = list(set(s.fraction.id for s in services))
cls.set_shared_fraction(fractions_ids)
update_samples_state = False
2020-07-17 22:40:45 +02:00
for field in ('laboratory_date', 'report_date',
'confirmation_date'):
if field in vals:
update_samples_state = True
2020-07-17 22:40:45 +02:00
break
if update_samples_state:
2020-07-17 22:40:45 +02:00
sample_ids = list(set(s.sample.id for s in services))
Sample.update_samples_state(sample_ids)
@classmethod
def delete(cls, services):
2020-07-17 22:40:45 +02:00
Sample = Pool().get('lims.sample')
if Transaction().user != 0:
cls.check_delete(services)
fractions_ids = list(set(s.fraction.id for s in services))
2020-07-17 22:40:45 +02:00
sample_ids = list(set(s.sample.id for s in services))
2020-08-06 19:52:36 +02:00
super().delete(services)
cls.set_shared_fraction(fractions_ids)
2020-07-17 22:40:45 +02:00
Sample.update_samples_state(sample_ids)
@classmethod
def check_delete(cls, services):
for service in services:
if service.fraction and service.fraction.confirmed:
2019-07-23 23:27:33 +02:00
raise UserError(gettext(
'lims.msg_delete_service', service=service.rec_name))
@staticmethod
def update_analysis_detail(services):
pool = Pool()
Service = pool.get('lims.service')
EntryDetailAnalysis = pool.get('lims.entry.detail.analysis')
for service in services:
if service.annulled:
continue
to_delete = EntryDetailAnalysis.search([
('service', '=', service.id),
])
if to_delete:
with Transaction().set_user(0, set_context=True):
EntryDetailAnalysis.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(Service._get_included_analysis(
service.analysis, service.analysis.code,
service_context))
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):
EntryDetailAnalysis.create(to_create)
@staticmethod
def _get_included_analysis(analysis, analysis_origin='',
service_context=None):
cursor = Transaction().connection.cursor()
pool = Pool()
Typification = 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 = None
cursor.execute('SELECT laboratory '
'FROM "' + Typification._table + '" '
'WHERE product_type = %s '
'AND matrix = %s '
'AND analysis = %s '
'AND valid IS TRUE '
'AND by_default IS TRUE '
'AND laboratory IS NOT NULL',
(service_context['product_type'],
service_context['matrix'],
included.included_analysis.id))
res = cursor.fetchone()
if res:
laboratory_id = res[0]
if not laboratory_id:
for l in included.included_analysis.laboratories:
if l.by_default is True:
laboratory_id = l.laboratory.id
2020-05-07 05:34:45 +02:00
method_id = (included.method.id
if included.method else None)
if not method_id:
cursor.execute('SELECT method '
'FROM "' + Typification._table + '" '
'WHERE product_type = %s '
'AND matrix = %s '
'AND analysis = %s '
'AND valid IS TRUE '
'AND by_default IS TRUE',
(service_context['product_type'],
service_context['matrix'],
included.included_analysis.id))
res = cursor.fetchone()
if res:
method_id = res[0]
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(Service._get_included_analysis(
included.included_analysis, origin, service_context))
return childs
@staticmethod
def create_aditional_services(services):
cursor = Transaction().connection.cursor()
pool = Pool()
EntryDetailAnalysis = pool.get('lims.entry.detail.analysis')
Typification = pool.get('lims.typification')
AnalysisLaboratory = pool.get('lims.analysis-laboratory')
AnalysisDevice = pool.get('lims.analysis.device')
Service = pool.get('lims.service')
aditional_services = {}
for service in services:
entry_details = EntryDetailAnalysis.search([
('service', '=', service.id),
])
for detail in entry_details:
typifications = Typification.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 "' + AnalysisLaboratory._table + '" '
'WHERE analysis = %s', (additional.id,))
res = cursor.fetchone()
laboratory_id = res and res[0] or None
cursor.execute('SELECT method '
'FROM "' + Typification._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
if not method_id:
raise UserError(gettext(
'lims.msg_additional_no_method',
additional=additional.rec_name,
analysis=typification.analysis.rec_name))
cursor.execute('SELECT device '
'FROM "' + AnalysisDevice._table + '" '
2020-02-26 23:04:15 +01:00
'WHERE active IS TRUE '
'AND 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 = []
2019-03-04 15:41:58 +01:00
for fraction_id, analysis in aditional_services.items():
for analysis_id, service_data in analysis.items():
if EntryDetailAnalysis.search([
('fraction', '=', fraction_id),
('analysis', '=', analysis_id),
]):
continue
if Service.search([
('fraction', '=', fraction_id),
('analysis', '=', analysis_id),
]):
continue
services_default.append({
'fraction': fraction_id,
'analysis': analysis_id,
'laboratory': service_data['laboratory'],
'method': service_data['method'],
'device': service_data['device'],
})
return Service.create(services_default)
@classmethod
def set_shared_fraction(cls, fractions_ids):
pool = Pool()
Fraction = pool.get('lims.fraction')
fractions = Fraction.search([
('id', 'in', fractions_ids),
])
for fraction in fractions:
shared = False
labs = []
for s in fraction.services:
if not s.analysis:
continue
if s.analysis.type == 'analysis':
if s.analysis.behavior == 'additional':
continue
labs.append(s.laboratory.id)
else:
labs.extend(cls._get_analysis_included_labs(s.analysis))
if len(set(labs)) > 1:
shared = True
if fraction.shared != shared:
Fraction.write([fraction], {'shared': shared})
@classmethod
def _get_analysis_included_labs(cls, analysis):
childs = []
for included in analysis.included_analysis:
if included.included_analysis.type == 'analysis':
for l in included.included_analysis.laboratories:
if l.by_default is True:
childs.append(l.laboratory.id)
childs.extend(cls._get_analysis_included_labs(
included.included_analysis))
return childs
@classmethod
def view_attributes(cls):
return [
('//group[@id="invisible_fields"]', 'states', {
'invisible': True,
}),
]
@classmethod
def copy(cls, services, default=None):
EntryDetailAnalysis = Pool().get('lims.entry.detail.analysis')
if default is None:
default = {}
current_default = default.copy()
current_default['confirmation_date'] = None
current_default['report_date'] = None
current_default['analysis_detail'] = None
detail_default = {}
if current_default.get('method', None):
detail_default['method'] = current_default['method']
if current_default.get('device', None):
detail_default['device'] = current_default['device']
new_services = []
for service in sorted(services, key=lambda x: x.number):
with Transaction().set_context(copying=True):
2020-08-06 19:52:36 +02:00
new_service, = super().copy([service],
default=current_default)
detail_default['service'] = new_service.id
detail_default['state'] = ('annulled' if service.annulled
else 'draft')
EntryDetailAnalysis.copy(service.analysis_detail,
default=detail_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 = []
2019-03-04 15:41:58 +01:00
for fraction_id, comment in comments.items():
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')
Service = pool.get('lims.service')
EntryDetailAnalysis = pool.get('lims.entry.detail.analysis')
if not confirmation_date:
confirmation_date = Date.today()
Service.write(services, {
'confirmation_date': confirmation_date,
})
analysis_details = EntryDetailAnalysis.search([
('service', 'in', [s.id for s in services]),
])
if analysis_details:
EntryDetailAnalysis.write(analysis_details, {
'confirmation_date': confirmation_date,
})
@fields.depends('analysis', 'fraction', 'typification_domain',
2020-03-05 05:24:51 +01:00
'laboratory', '_parent_fraction.id',
methods=['_get_default_laboratory', 'on_change_with_method_domain',
'_on_change_with_device_domain'])
def on_change_analysis(self):
Laboratory = Pool().get('lims.laboratory')
laboratory = None
method = None
device = None
if self.analysis:
default_laboratory = self._get_default_laboratory()
if default_laboratory:
laboratory = default_laboratory
methods = self.on_change_with_method_domain()
if len(methods) == 1:
method = methods[0]
devices = self._on_change_with_device_domain(self.analysis,
Laboratory(laboratory), True)
if len(devices) == 1:
device = devices[0]
self.estimated_waiting_laboratory = (
self.analysis.estimated_waiting_laboratory)
self.estimated_waiting_report = (
self.analysis.estimated_waiting_report)
self.laboratory = laboratory
self.method = method
self.device = device
@staticmethod
def default_analysis_domain():
return Transaction().context.get('analysis_domain', [])
2020-03-05 05:24:51 +01:00
@fields.depends('fraction', '_parent_fraction.id')
def on_change_with_analysis_domain(self, name=None):
if Transaction().context.get('analysis_domain'):
return Transaction().context.get('analysis_domain')
return []
@staticmethod
def default_typification_domain():
return Transaction().context.get('typification_domain', [])
2020-03-05 05:24:51 +01:00
@fields.depends('fraction', '_parent_fraction.id')
def on_change_with_typification_domain(self, name=None):
if Transaction().context.get('typification_domain'):
return Transaction().context.get('typification_domain')
return []
2020-03-05 05:24:51 +01:00
@fields.depends('analysis', '_parent_analysis.type')
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) > 0):
return Transaction().context.get('fraction')
return None
2020-03-05 05:24:51 +01:00
@fields.depends('fraction', '_parent_fraction.id')
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) > 0):
return Transaction().context.get('sample')
return None
2020-03-05 05:24:51 +01:00
@fields.depends('fraction', '_parent_fraction.sample')
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) > 0):
return Transaction().context.get('entry')
return None
2020-03-05 05:24:51 +01:00
@fields.depends('fraction', '_parent_fraction.entry')
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) > 0):
return Transaction().context.get('party')
return None
2020-03-05 05:24:51 +01:00
@fields.depends('fraction', '_parent_fraction.party')
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
@staticmethod
def default_report_date_readonly():
return True
@classmethod
def get_report_date_readonly(cls, services, name):
readonly = cls.default_report_date_readonly()
result = {}
for s in services:
result[s.id] = readonly
return result
@fields.depends('estimated_waiting_laboratory')
def on_change_with_laboratory_date(self, name=None):
pool = Pool()
LabWorkYear = pool.get('lims.lab.workyear')
Date = pool.get('ir.date')
if self.estimated_waiting_laboratory:
date_ = Date.today()
workyear = LabWorkYear(LabWorkYear.find(date_))
date_ = workyear.get_target_date(date_,
self.estimated_waiting_laboratory)
return date_
return None
@fields.depends('estimated_waiting_laboratory', 'estimated_waiting_report')
def on_change_with_report_date(self, name=None):
pool = Pool()
LabWorkYear = pool.get('lims.lab.workyear')
Date = pool.get('ir.date')
if self.estimated_waiting_laboratory or self.estimated_waiting_report:
date_ = Date.today()
workyear = LabWorkYear(LabWorkYear.find(date_))
if self.estimated_waiting_laboratory:
date_ = workyear.get_target_date(date_,
self.estimated_waiting_laboratory)
if self.estimated_waiting_report:
date_ = workyear.get_target_date(date_,
self.estimated_waiting_report)
return date_
return None
2020-03-05 05:24:51 +01:00
@fields.depends('analysis', 'laboratory',
methods=['_on_change_with_device_domain'])
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', '_parent_fraction.product_type',
'_parent_fraction.matrix')
def _get_default_laboratory(self):
cursor = Transaction().connection.cursor()
pool = Pool()
AnalysisLaboratory = pool.get('lims.analysis-laboratory')
Typification = pool.get('lims.typification')
if not self.analysis or self.analysis.type != 'analysis':
return None
cursor.execute('SELECT laboratory '
'FROM "' + Typification._table + '" '
'WHERE product_type = %s '
'AND matrix = %s '
'AND analysis = %s '
'AND valid IS TRUE '
'AND by_default IS TRUE '
'AND laboratory IS NOT NULL',
(self.fraction.product_type.id, self.fraction.matrix.id,
self.analysis.id))
res = cursor.fetchone()
if res:
return res[0]
cursor.execute('SELECT laboratory '
2018-11-25 18:27:38 +01:00
'FROM "' + AnalysisLaboratory._table + '" '
'WHERE analysis = %s '
'AND by_default = TRUE '
'ORDER BY id', (self.analysis.id,))
res = cursor.fetchone()
if res:
return res[0]
return None
@fields.depends('analysis', '_parent_fraction.product_type',
'_parent_fraction.matrix')
def on_change_with_laboratory_domain(self, name=None):
cursor = Transaction().connection.cursor()
pool = Pool()
AnalysisLaboratory = pool.get('lims.analysis-laboratory')
if not self.analysis or self.analysis.type != 'analysis':
return []
cursor.execute('SELECT DISTINCT(laboratory) '
'FROM "' + AnalysisLaboratory._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()
Typification = 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 "' + Typification._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]
2020-03-05 05:24:51 +01:00
@fields.depends('analysis', 'laboratory',
methods=['_on_change_with_device_domain'])
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()
AnalysisDevice = Pool().get('lims.analysis.device')
if not analysis or not laboratory:
return []
if by_default:
2020-02-26 23:04:15 +01:00
by_default_clause = 'AND by_default IS TRUE'
else:
by_default_clause = ''
cursor.execute('SELECT DISTINCT(device) '
'FROM "' + AnalysisDevice._table + '" '
2020-02-26 23:04:15 +01:00
'WHERE active IS TRUE '
'AND 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]
@fields.depends('divide')
def on_change_divide(self):
if self.divide:
self.not_divided_message = gettext('lims.msg_divide_report')
else:
self.not_divided_message = ''
@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
@classmethod
def set_fraction_field(cls, records, name, value):
return
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()
pool = Pool()
NotebookLine = pool.get('lims.notebook.line')
result = {}
for name in names:
result[name] = {}
for s in services:
cursor.execute('SELECT service '
'FROM "' + NotebookLine._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):
pool = Pool()
NotebookLine = pool.get('lims.notebook.line')
planned_notebook_lines = NotebookLine.search([
('service', '=', self.id),
('planification', '!=', None),
])
if planned_notebook_lines:
return False
return True
def get_icon(self, name):
if self.has_results_report:
return 'lims-green'
if not self.confirmed:
return 'lims-red'
2019-02-22 18:08:32 +01:00
return 'lims-white'
def get_planned(self, name):
if not self.analysis_detail:
return False
for ad in self.analysis_detail:
if (ad.state in ('draft', 'unplanned') and
ad.analysis.behavior != 'internal_relation'):
return False
return True
@classmethod
def search_planned(cls, name, clause):
cursor = Transaction().connection.cursor()
pool = Pool()
EntryDetailAnalysis = pool.get('lims.entry.detail.analysis')
Analysis = pool.get('lims.analysis')
cursor.execute('SELECT DISTINCT(d.service) '
'FROM "' + EntryDetailAnalysis._table + '" d '
'INNER JOIN "' + Analysis._table + '" a '
'ON a.id = d.analysis '
'WHERE d.state IN (\'draft\', \'unplanned\') '
'AND a.behavior != \'internal_relation\'')
not_planned_ids = [s[0] for s in cursor.fetchall()]
field, op, operand = clause
if (op, operand) in (('=', True), ('!=', False)):
return [('id', 'not in', not_planned_ids)]
elif (op, operand) in (('=', False), ('!=', True)):
return [('id', 'in', not_planned_ids)]
else:
return []
@classmethod
def is_service_urgent(cls, fraction_id, analysis_id):
service = cls.search([
('fraction', '=', fraction_id),
('analysis', '=', analysis_id),
])
if service:
return service[0].urgent
return False
class Fraction(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')
date = fields.Function(fields.DateTime('Date'), 'get_sample_field',
searcher='search_sample_field')
type = fields.Many2One('lims.fraction.type', 'Fraction type',
required=True, select=True)
storage_location = fields.Many2One('stock.location', 'Storage location',
required=True, domain=[('type', '=', 'storage')])
storage_time = fields.Integer('Storage time (in months)', required=True)
packages_quantity = fields.Integer('Packages quantity', required=True)
package_type = fields.Many2One('lims.packaging.type', 'Package type',
required=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'),
2021-01-28 22:41:27 +01:00
'readonly': Bool(Eval('button_manage_services_available')),
},
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', searcher='search_sample_field')
matrix = fields.Function(fields.Many2One('lims.matrix', 'Matrix'),
'on_change_with_matrix', searcher='search_sample_field')
button_manage_services_available = fields.Function(fields.Boolean(
'Button manage services available'),
'on_change_with_button_manage_services_available')
confirmed = fields.Boolean('Confirmed', select=True)
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')
icon = fields.Function(fields.Char("Icon"), 'get_icon')
special_type = fields.Function(fields.Char('Fraction type'),
'on_change_with_special_type',
searcher='search_special_type')
con_type = fields.Selection([
('', ''),
('con', 'CON'),
('coi', 'COI'),
('mrc', 'MRC'),
('sla', 'SLA'),
('itc', 'ITC'),
('itl', 'ITL'),
], 'Type', sort=False)
con_original_fraction = fields.Many2One('lims.fraction',
'Original fraction')
bmz_type = fields.Selection([
('', ''),
('sla', 'SLA'),
('noinitialbmz', 'No initial BMZ'),
], 'Type', sort=False)
bmz_product_type = fields.Many2One('lims.product.type', 'Product type')
bmz_matrix = fields.Many2One('lims.matrix', 'Matrix')
bmz_original_fraction = fields.Many2One('lims.fraction',
'Original fraction')
rm_type = fields.Selection([
('', ''),
('sla', 'SLA'),
('noinitialrm', 'No initial RM'),
], 'Type', sort=False)
rm_product_type = fields.Many2One('lims.product.type', 'Product type')
rm_matrix = fields.Many2One('lims.matrix', 'Matrix')
rm_original_fraction = fields.Many2One('lims.fraction',
'Original fraction')
bre_product_type = fields.Many2One('lims.product.type', 'Product type')
bre_matrix = fields.Many2One('lims.matrix', 'Matrix')
bre_reagents = fields.One2Many('lims.fraction.reagent', 'fraction',
'Reagents')
mrt_product_type = fields.Many2One('lims.product.type', 'Product type')
mrt_matrix = fields.Many2One('lims.matrix', 'Matrix')
cie_fraction_type_available = fields.Function(fields.Boolean(
'Available for Blind Sample'),
'on_change_with_cie_fraction_type_available')
cie_fraction_type = fields.Boolean('Blind Sample',
states={'readonly': Not(Bool(Eval('cie_fraction_type_available')))},
depends=['cie_fraction_type_available'])
cie_original_fraction = fields.Many2One('lims.fraction',
'Original fraction', states={'readonly': Bool(Eval('confirmed'))},
depends=['confirmed'])
@classmethod
def __setup__(cls):
2020-08-06 19:52:36 +02:00
super().__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'),
},
'load_services': {
'invisible': Or(Bool(Eval('button_manage_services_available')),
Bool(Eval('services'))),
},
})
@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 view_attributes(cls):
return [
('//group[@id="button_confirm"]', 'states', {
'invisible': ~Eval('button_confirm_available'),
}),
('//page[@id="cie"]', 'states', {
'invisible': Not(Bool(Eval('cie_fraction_type'))),
}),
('//page[@id="mrt"]', 'states', {
'invisible': Not(Bool(Equal(Eval('special_type'), 'mrt'))),
}),
('//page[@id="bre"]', 'states', {
'invisible': Not(Bool(Equal(Eval('special_type'), 'bre'))),
}),
('//page[@id="rm"]', 'states', {
'invisible': Not(Bool(Equal(Eval('special_type'), 'rm'))),
}),
('//page[@id="bmz"]', 'states', {
'invisible': Not(Bool(Equal(Eval('special_type'), 'bmz'))),
}),
('//page[@id="con"]', 'states', {
'invisible': Not(Bool(Equal(Eval('special_type'), 'con'))),
}),
]
@classmethod
def get_next_number(cls, sample_id, f_count):
Sample = Pool().get('lims.sample')
samples = Sample.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().create(vlist)
@classmethod
def write(cls, *args):
super().write(*args)
actions = iter(args)
for fractions, vals in zip(actions, actions):
if vals.get('type'):
cls.update_details_plannable(fractions, vals.get('type'))
@classmethod
def update_details_plannable(cls, fractions, fraction_type_id):
cursor = Transaction().connection.cursor()
pool = Pool()
FractionType = pool.get('lims.fraction.type')
EntryDetailAnalysis = pool.get('lims.entry.detail.analysis')
Service = pool.get('lims.service')
plannable = ('TRUE' if FractionType(fraction_type_id).plannable
else 'FALSE')
fractions_ids = ', '.join(str(f.id) for f in fractions)
cursor.execute('UPDATE "' + EntryDetailAnalysis._table + '" d '
'SET plannable = ' + plannable + ' FROM '
'"' + Service._table + '" srv '
'WHERE d.service = srv.id '
'AND d.state IN (\'draft\', \'unplanned\') '
'AND d.referable = FALSE '
'AND srv.fraction IN (' + fractions_ids + ')')
@classmethod
def copy(cls, fractions, default=None):
if default is None:
default = {}
with Transaction().set_context(_check_access=False):
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['discharge_date'] = None
current_default['expiry_date'] = None
current_default['countersample_date'] = None
current_default['countersample_location'] = None
2020-08-06 19:52:36 +02:00
new_fraction, = super().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:
2019-07-23 23:27:33 +02:00
raise UserError(gettext(
'lims.msg_delete_fraction', fraction=fraction.rec_name))
@classmethod
def delete(cls, fractions):
cls.check_delete(fractions)
2020-08-06 19:52:36 +02:00
super().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', [])
2020-03-05 05:24:51 +01:00
@fields.depends('sample', '_parent_sample.id')
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', [])
2020-03-05 05:24:51 +01:00
@fields.depends('sample', '_parent_sample.id')
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)
2020-03-05 05:24:51 +01:00
@fields.depends('sample', '_parent_sample.product_type')
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)
2020-03-05 05:24:51 +01:00
@fields.depends('sample', '_parent_sample.matrix')
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) > 0):
return Transaction().context.get('sample')
return None
@staticmethod
def default_cie_fraction_type():
return False
@fields.depends('type')
def on_change_with_special_type(self, name=None):
Config = Pool().get('lims.configuration')
if self.type:
config = Config(1)
if self.type == config.mcl_fraction_type:
return 'mcl'
elif self.type == config.con_fraction_type:
return 'con'
elif self.type == config.bmz_fraction_type:
return 'bmz'
elif self.type == config.rm_fraction_type:
return 'rm'
elif self.type == config.bre_fraction_type:
return 'bre'
elif self.type == config.mrt_fraction_type:
return 'mrt'
elif self.type == config.coi_fraction_type:
return 'coi'
elif self.type == config.mrc_fraction_type:
return 'mrc'
elif self.type == config.sla_fraction_type:
return 'sla'
elif self.type == config.itc_fraction_type:
return 'itc'
elif self.type == config.itl_fraction_type:
return 'itl'
return ''
@classmethod
def search_special_type(cls, name, clause):
if clause[1] in ('=', '!='):
types = [clause[2]]
elif clause[1] in ('in', 'not in'):
types = clause[2]
else:
return []
2021-10-14 00:34:21 +02:00
if not types:
return []
res_type = cls._get_special_type(types)
if clause[1] in ('=', '!='):
return [('type', clause[1], res_type[0])]
elif clause[1] in ('in', 'not in'):
return [('type', clause[1], res_type)]
return []
2021-10-14 00:34:21 +02:00
@classmethod
def _get_special_type(cls, types):
Config = Pool().get('lims.configuration')
config = Config(1)
res_type = []
for type_ in types:
if type_ == 'mcl':
res_type.append(config.mcl_fraction_type)
elif type_ == 'con':
res_type.append(config.con_fraction_type)
elif type_ == 'bmz':
res_type.append(config.bmz_fraction_type)
elif type_ == 'rm':
res_type.append(config.rm_fraction_type)
elif type_ == 'bre':
res_type.append(config.bre_fraction_type)
elif type_ == 'mrt':
res_type.append(config.mrt_fraction_type)
elif type_ == 'coi':
res_type.append(config.coi_fraction_type)
elif type_ == 'mrc':
res_type.append(config.mrc_fraction_type)
elif type_ == 'sla':
res_type.append(config.sla_fraction_type)
elif type_ == 'itc':
res_type.append(config.itc_fraction_type)
elif type_ == 'itl':
res_type.append(config.itl_fraction_type)
return res_type
2020-03-05 05:24:51 +01:00
@fields.depends('sample', '_parent_sample.id')
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) > 0):
return Transaction().context.get('entry')
return None
2020-03-05 05:24:51 +01:00
@fields.depends('sample', '_parent_sample.entry')
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) > 0):
return Transaction().context.get('party')
return None
2020-03-05 05:24:51 +01:00
@fields.depends('sample', '_parent_sample.party')
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', 'special_type', 'con_original_fraction',
2020-03-05 05:24:51 +01:00
'services', 'create_date', '_parent_sample.label')
def on_change_with_label(self, name=None):
type = self.special_type
if type == 'con':
label = ''
if self.con_original_fraction:
label += self.con_original_fraction.label
if self.services:
if self.services[0].analysis:
label += ' ' + self.services[0].analysis.code
if self.create_date:
label += ' ' + str(datetime.date(self.create_date))
return label
elif type == 'bmz':
label = 'BMZ'
if self.services:
if self.services[0].analysis:
label += ' ' + self.services[0].analysis.code
if self.create_date:
label += ' ' + str(datetime.date(self.create_date))
return label
elif type == 'rm':
label = 'RM'
if self.services:
if self.services[0].analysis:
label += ' ' + self.services[0].analysis.code
if self.create_date:
label += ' ' + str(datetime.date(self.create_date))
return label
elif type == 'bre':
label = 'BRE'
if self.services:
if self.services[0].analysis:
label += ' ' + self.services[0].analysis.code
if self.create_date:
label += ' ' + str(datetime.date(self.create_date))
return label
elif type == 'mrt':
label = 'MRT'
if self.services:
if self.services[0].analysis:
label += ' ' + self.services[0].analysis.code
if self.create_date:
label += ' ' + str(datetime.date(self.create_date))
return label
else:
if self.sample:
result = self.get_sample_field((self,), ('label',))
return result['label'][self.id]
return ''
@staticmethod
def default_package_type():
2018-12-06 03:47:26 +01:00
if Transaction().context.get('package_type'):
return Transaction().context.get('package_type')
return None
2020-03-05 05:24:51 +01:00
@fields.depends('sample', '_parent_sample.package_type')
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_fraction_state():
2018-12-06 03:47:26 +01:00
if Transaction().context.get('fraction_state'):
return Transaction().context.get('fraction_state')
return None
2020-03-05 05:24:51 +01:00
@fields.depends('sample', '_parent_sample.package_state')
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 == 'fraction_state':
for f in fractions:
field = getattr(f.sample, 'package_state', None)
result[name][f.id] = field.id if field else None
elif cls._fields[name]._type == 'many2one':
for f in fractions:
field = getattr(f.sample, name, None)
result[name][f.id] = field.id if field else None
else:
for f in fractions:
result[name][f.id] = getattr(f.sample, name, None)
return result
@classmethod
def search_sample_field(cls, name, clause):
return [('sample.' + name,) + tuple(clause[1:])]
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')
order_date = _order_sample_field('date')
@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
2020-03-05 05:24:51 +01:00
@fields.depends('confirmed', 'type', '_parent_type.cie_fraction_type')
def on_change_with_cie_fraction_type_available(self, name=None):
if not self.confirmed and self.type and self.type.cie_fraction_type:
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
@classmethod
@ModelView.button_action('lims.wiz_lims_load_services')
def load_services(cls, fractions):
pass
2020-03-05 05:24:51 +01:00
@fields.depends('confirmed', 'sample', '_parent_sample.entry')
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()
2018-11-25 18:27:38 +01:00
Service = pool.get('lims.service')
EntryDetailAnalysis = pool.get('lims.entry.detail.analysis')
2018-11-25 18:27:38 +01:00
services = Service.search([
('fraction', 'in', [f.id for f in fractions]),
('divide', '=', True),
('annulled', '=', False),
])
for service in services:
2018-11-25 18:27:38 +01:00
if (EntryDetailAnalysis.search_count([
('service', '=', service.id),
('report_grouper', '!=', 0),
]) == 0):
2019-07-23 23:27:33 +02:00
raise UserError(gettext('lims.msg_not_divided'))
@classmethod
@ModelView.button
def confirm(cls, fractions):
pool = Pool()
Config = pool.get('lims.configuration')
Service = pool.get('lims.service')
Company = pool.get('company.company')
EntryDetailAnalysis = pool.get('lims.entry.detail.analysis')
Move = pool.get('stock.move')
confirm_background = Config(1).entry_confirm_background
cls.check_divided_report(fractions)
fractions_to_save = []
stock_moves_to_create = []
for fraction in fractions:
services = Service.search([
('fraction', '=', fraction.id),
('annulled', '=', False),
])
2020-06-25 17:29:27 +02:00
if not services and not fraction.type.without_services:
companies = Company.search([])
if fraction.party.id not in [c.party.id for c in companies]:
raise UserError(gettext(
'lims.msg_not_services', fraction=fraction.rec_name))
Service.copy_analysis_comments(services)
Service.set_confirmation_date(services)
fraction.create_laboratory_notebook()
analysis_detail = EntryDetailAnalysis.search([
('fraction', '=', fraction.id),
('state', '!=', 'annulled'),
])
if analysis_detail:
EntryDetailAnalysis.create_notebook_lines(analysis_detail,
fraction)
fraction.confirmed = True
if confirm_background:
fraction.waiting_confirmation = True
else:
stock_moves_to_create.append(fraction.create_stock_move())
fractions_to_save.append(fraction)
if stock_moves_to_create:
with Transaction().set_context(check_current_location=False):
Move.save(stock_moves_to_create)
Move.assign(stock_moves_to_create)
Move.do(stock_moves_to_create)
cls.save(fractions_to_save)
with Transaction().set_context(_check_access=False):
fracts = cls.search([
('id', 'in', [f.id for f in fractions]),
])
for fraction in fracts:
fraction.update_detail_analysis()
if fraction.cie_fraction_type:
fraction.create_blind_samples()
def create_laboratory_notebook(self):
pool = Pool()
2018-11-25 18:27:38 +01:00
Notebook = pool.get('lims.notebook')
with Transaction().set_user(0):
2018-11-25 18:27:38 +01:00
notebook = Notebook(
fraction=self.id,
)
notebook.save()
def create_stock_move(self):
return self._get_stock_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:
2019-07-23 23:27:33 +02:00
raise UserError(gettext('lims.msg_missing_fraction_product'))
today = Date.today()
company = User(Transaction().user).company
if self.sample.party.customer_location:
from_location = self.sample.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
2021-09-23 01:08:52 +02:00
if move.on_change_with_unit_price_required():
move.unit_price = 0
move.currency = company.currency
move.state = 'draft'
return move
@classmethod
def confirm_waiting_fractions(cls):
'''
Cron - Confirm Waiting Fractions
'''
Move = Pool().get('stock.move')
logger = logging.getLogger('lims')
fractions = cls.search([
('waiting_confirmation', '=', True),
], order=[('id', 'ASC')])
if fractions:
logger.info('Cron - Confirming fractions:INIT')
fractions_to_save = []
stock_moves_to_create = []
for fraction in fractions:
fraction.waiting_confirmation = False
stock_moves_to_create.append(fraction.create_stock_move())
fractions_to_save.append(fraction)
if stock_moves_to_create:
with Transaction().set_context(check_current_location=False):
Move.save(stock_moves_to_create)
Move.assign(stock_moves_to_create)
Move.do(stock_moves_to_create)
cls.save(fractions_to_save)
logger.info('Cron - Confirming fractions:END')
@fields.depends('services')
def on_change_services(self, name=None):
Analysis = Pool().get('lims.analysis')
self.duplicated_analysis_message = ''
if not self.services:
return
analysis = []
for service in self.services:
if not service.analysis:
continue
if service.annulled:
continue
new_analysis = [(service.analysis.id,
service.method and service.method.id or None)]
new_analysis.extend(Analysis.get_included_analysis_method(
service.analysis.id))
for a in new_analysis:
if a in analysis:
self.duplicated_analysis_message = gettext(
'lims.msg_duplicated_analysis',
analysis=Analysis(a[0]).rec_name)
return
analysis.extend(new_analysis)
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 []
cursor = Transaction().connection.cursor()
pool = Pool()
Fraction = pool.get('lims.fraction')
Move = pool.get('stock.move')
Location = pool.get('stock.location')
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':
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])
cursor.execute('SELECT f.id, last_move.to_location '
'FROM "' + Fraction._table + '" f '
'INNER JOIN ( '
'SELECT DISTINCT ON (fraction) fraction, to_location '
'FROM "' + Move._table + '" '
'WHERE state IN (\'assigned\', \'done\') '
'ORDER BY fraction, effective_date DESC, id DESC '
') last_move '
'ON f.id = last_move.fraction')
processed_lines = [{
'fraction': x[0],
'current_location': x[1],
} for x in cursor.fetchall()]
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)
@classmethod
def get_has_results_report(cls, fractions, names):
cursor = Transaction().connection.cursor()
pool = Pool()
Service = pool.get('lims.service')
NotebookLine = pool.get('lims.notebook.line')
result = {}
for name in names:
result[name] = {}
for f in fractions:
cursor.execute('SELECT s.fraction '
'FROM "' + Service._table + '" s '
'INNER JOIN "' + NotebookLine._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()
Service = pool.get('lims.service')
NotebookLine = pool.get('lims.notebook.line')
cursor.execute('SELECT DISTINCT(s.fraction) '
'FROM "' + Service._table + '" s '
'INNER JOIN "' + NotebookLine._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):
NotebookLine = Pool().get('lims.notebook.line')
notebook_lines = NotebookLine.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
def get_icon(self, name):
if self.has_results_report:
return 'lims-green'
if not self.confirmed:
return 'lims-red'
2019-02-22 18:08:32 +01:00
return 'lims-white'
def update_detail_analysis(self):
EntryDetailAnalysis = Pool().get('lims.entry.detail.analysis')
analysis_details = EntryDetailAnalysis.search([
('fraction', '=', self.id),
('state', '!=', 'annulled'),
])
if analysis_details:
EntryDetailAnalysis.write(analysis_details, {
'state': 'unplanned',
})
def create_blind_samples(self):
pool = Pool()
NotebookLine = pool.get('lims.notebook.line')
BlindSample = pool.get('lims.blind_sample')
Date = pool.get('ir.date')
confirmation_date = Date.today()
to_create = []
nlines = NotebookLine.search([
('notebook.fraction', '=', self.id),
])
for nline in nlines:
detail = nline.analysis_detail
record = {
'line': nline.id,
'entry': nline.fraction.entry.id,
'sample': nline.fraction.sample.id,
'fraction': nline.fraction.id,
'service': nline.service.id,
'analysis': nline.analysis.id,
'repetition': nline.repetition,
'date': confirmation_date,
'min_value': detail.cie_min_value,
'max_value': detail.cie_max_value,
}
original_fraction = self.cie_original_fraction
if original_fraction:
record['original_sample'] = original_fraction.sample.id
record['original_fraction'] = original_fraction.id
original_line = NotebookLine.search([
('notebook.fraction', '=', original_fraction.id),
('analysis', '=', nline.analysis.id),
('repetition', '=', nline.repetition),
])
if original_line:
record['original_line'] = original_line[0].id
record['original_repetition'] = original_line[0].repetition
to_create.append(record)
if to_create:
BlindSample.create(to_create)
@staticmethod
def get_stored_fractions():
cursor = Transaction().connection.cursor()
pool = Pool()
Move = pool.get('stock.move')
Location = pool.get('stock.location')
cursor.execute('SELECT DISTINCT ON (m.fraction) m.fraction, l.type '
'FROM "' + Move._table + '" m '
'INNER JOIN "' + Location._table + '" l '
'ON m.to_location = l.id '
'WHERE m.fraction IS NOT NULL '
'AND m.state IN (\'assigned\', \'done\') '
'ORDER BY m.fraction ASC, m.effective_date DESC, m.id DESC')
return [x[0] for x in cursor.fetchall() if x[1] == 'storage']
class Sample(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.Many2One('party.party', 'Party', required=True,
states={'readonly': True})
multi_party = fields.Function(fields.Boolean('Multi Party'),
'get_entry_field', searcher='search_entry_field')
invoice_party = fields.Function(fields.Many2One('party.party',
'Invoice Party'), 'get_entry_field', searcher='search_entry_field')
producer = fields.Many2One('lims.sample.producer', 'Producer company',
domain=['OR', ('id', '=', Eval('producer')),
('party', '=', Eval('party'))],
depends=['party'])
2021-02-03 00:01:33 +01:00
label = fields.Char('Label')
sample_client_description = fields.Char('Product described by the client',
translate=True)
product_type = fields.Many2One('lims.product.type', 'Product type',
required=True,
domain=['OR', ('id', '=', Eval('product_type')),
('id', 'in', Eval('product_type_domain'))],
states={'readonly': Bool(Eval('product_type_matrix_readonly'))},
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,
domain=['OR', ('id', '=', Eval('matrix')),
('id', 'in', Eval('matrix_domain'))],
states={'readonly': Bool(Eval('product_type_matrix_readonly'))},
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')
2019-06-14 00:38:02 +02:00
obj_description = fields.Many2One('lims.objective_description',
'Objective description', depends=['product_type', 'matrix'],
domain=[
('product_type', '=', Eval('product_type')),
('matrix', '=', Eval('matrix')),
])
obj_description_manual = fields.Char('Manual Objective description',
translate=True, states={'readonly': Bool(Eval('obj_description'))},
depends=['obj_description'])
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)
restricted_entry = fields.Boolean('Restricted entry',
states={'readonly': True})
zone = fields.Many2One('lims.zone', 'Zone',
states={'required': Bool(Eval('zone_required'))},
depends=['zone_required'])
zone_required = fields.Function(fields.Boolean('Zone required'),
'get_zone_required')
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'),
'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')
icon = fields.Function(fields.Char("Icon"), 'get_icon')
urgent = fields.Function(fields.Boolean('Urgent'), 'get_urgent',
searcher='search_urgent')
completion_percentage = fields.Function(fields.Numeric('Complete',
digits=(1, 4), domain=[
('completion_percentage', '>=', 0),
('completion_percentage', '<=', 1),
]),
'get_completion_percentage')
department = fields.Function(fields.Many2One('company.department',
'Department'), 'get_department', searcher='search_department')
2020-05-05 04:58:54 +02:00
attributes = fields.Dict('lims.sample.attribute', 'Attributes')
attributes_keys_string = attributes.translated('attributes', 'keys')
attributes_values_string = attributes.translated('attributes')
2020-07-17 22:40:45 +02:00
confirmation_date = fields.Date('Confirmation date', readonly=True)
laboratory_date = fields.Date('Laboratory deadline', readonly=True)
report_date = fields.Date('Date agreed for result', readonly=True)
laboratory_start_date = fields.Date('Laboratory start date', readonly=True)
laboratory_end_date = fields.Date('Laboratory end date', readonly=True)
laboratory_acceptance_date = fields.Date('Laboratory acceptance date',
readonly=True)
results_report_create_date = fields.Date('Report start date',
readonly=True)
results_report_release_date = fields.Date('Report release date',
readonly=True)
results_reports_list = fields.Function(fields.Char('Results Reports'),
'get_results_reports_list', searcher='search_results_reports_list')
2020-07-17 22:40:45 +02:00
state = fields.Selection([
('draft', 'Draft'),
('annulled', 'Annulled'),
2020-07-17 22:40:45 +02:00
('pending_planning', 'Pending Planification'),
('planned', 'Planned'),
('in_lab', 'In Laboratory'),
('lab_pending_acceptance', 'Pending Laboratory Acceptance'),
('pending_report', 'Pending Reporting'),
('in_report', 'In Report'),
('report_released', 'Report Released'),
], 'State')
qty_lines_pending = fields.Integer('Pending lines')
qty_lines_pending_acceptance = fields.Integer('Lines pending acceptance')
@classmethod
def __setup__(cls):
2020-08-06 19:52:36 +02:00
super().__setup__()
cls._order.insert(0, ('number', 'DESC'))
cls.__rpc__.update({
'update_samples_state': RPC(readonly=False, instantiate=0),
})
2020-07-17 22:40:45 +02:00
@classmethod
def __register__(cls, module_name):
cursor = Transaction().connection.cursor()
Entry = Pool().get('lims.entry')
table_h = cls.__table_handler__(module_name)
qty_lines_pending_exist = table_h.column_exist('qty_lines_pending')
qty_lines_pending_acceptance_exist = table_h.column_exist(
'qty_lines_pending_acceptance')
party_exist = table_h.column_exist('party')
super().__register__(module_name)
if (not qty_lines_pending_exist or
not qty_lines_pending_acceptance_exist):
logging.getLogger(__name__).info(
'Updating Pending lines in Samples...')
for sample in cls.search([]):
sample.update_qty_lines()
if not party_exist:
logging.getLogger(__name__).info('Updating Party in Samples...')
cursor.execute('UPDATE "' + cls._table + '" s '
'SET party = e.party FROM '
'"' + Entry._table + '" e '
'WHERE e.id = s.entry')
@staticmethod
def default_date():
return datetime.now()
@staticmethod
def default_restricted_entry():
return False
@staticmethod
def default_trace_report():
return False
2020-07-17 22:40:45 +02:00
@staticmethod
def default_state():
return 'draft'
@staticmethod
def default_qty_lines_pending():
return None
@staticmethod
def default_qty_lines_pending_acceptance():
return None
@classmethod
def copy(cls, samples, default=None):
if default is None:
default = {}
current_default = default.copy()
current_default['qty_lines_pending'] = None
current_default['qty_lines_pending_acceptance'] = None
new_samples = []
for sample in sorted(samples, key=lambda x: x.number):
2020-08-06 19:52:36 +02:00
new_sample, = super().copy([sample],
default=current_default)
new_samples.append(new_sample)
return new_samples
def get_date(self, name):
pool = Pool()
Company = pool.get('company.company')
date = self.date
2019-05-29 05:24:47 +02:00
if not date:
return None
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()
LabWorkYear = pool.get('lims.lab.workyear')
2022-02-01 17:29:50 +01:00
EntryPreAssignedSample = pool.get('lims.entry.pre_assigned_sample')
workyear_id = LabWorkYear.find()
workyear = LabWorkYear(workyear_id)
sequence = workyear.get_sequence('sample')
if not sequence:
2019-07-23 23:27:33 +02:00
raise UserError(gettext('lims.msg_no_sample_sequence',
work_year=workyear.rec_name))
vlist = [x.copy() for x in vlist]
for values in vlist:
2022-02-01 17:29:50 +01:00
number = EntryPreAssignedSample.get_next_number(values['entry'])
if not number:
number = sequence.get()
values['number'] = number
2020-08-06 19:52:36 +02:00
samples = super().create(vlist)
for sample in samples:
sample.warn_duplicated_label()
return samples
def warn_duplicated_label(self):
return # deactivated
2019-07-23 23:27:33 +02:00
Warning = Pool().get('res.user.warning')
if self.label:
duplicated = self.search([
('entry', '=', self.entry.id),
('label', '=', self.label),
('id', '!=', self.id),
])
if duplicated:
2019-07-23 23:27:33 +02:00
key = 'lims_sample_label@%s' % self.number
if Warning.check(key):
raise UserWarning(gettext(
'lims.duplicated_label', label=self.label))
@classmethod
def write(cls, *args):
2020-08-06 19:52:36 +02:00
super().write(*args)
actions = iter(args)
for samples, vals in zip(actions, actions):
if vals.get('label'):
for sample in samples:
sample.warn_duplicated_label()
@classmethod
def view_attributes(cls):
return [
('/tree/field[@name="qty_lines_pending_acceptance"]',
'visual', If(Eval('qty_lines_pending_acceptance', 0) > 0,
'danger', '')),
]
2020-03-05 05:24:51 +01:00
@fields.depends('product_type', 'matrix', 'zone',
'_parent_product_type.restricted_entry',
'_parent_matrix.restricted_entry', '_parent_zone.restricted_entry')
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()
2018-11-25 18:27:38 +01:00
Typification = pool.get('lims.typification')
CalculatedTypification = pool.get('lims.typification.calculated')
Analysis = pool.get('lims.analysis')
if not self.product_type or not self.matrix:
return []
cursor.execute('SELECT DISTINCT(analysis) '
2018-11-25 18:27:38 +01:00
'FROM "' + Typification._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 '
2018-11-25 18:27:38 +01:00
'FROM "' + Analysis._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) '
2018-11-25 18:27:38 +01:00
'FROM "' + CalculatedTypification._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 '
2018-11-25 18:27:38 +01:00
'FROM "' + Analysis._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()
2018-11-25 18:27:38 +01:00
Typification = Pool().get('lims.typification')
if not self.product_type or not self.matrix:
return []
cursor.execute('SELECT id '
2018-11-25 18:27:38 +01:00
'FROM "' + Typification._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()
2018-11-25 18:27:38 +01:00
Typification = Pool().get('lims.typification')
cursor.execute('SELECT DISTINCT(product_type) '
2018-11-25 18:27:38 +01:00
'FROM "' + Typification._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()
2018-11-25 18:27:38 +01:00
Typification = Pool().get('lims.typification')
if not self.product_type:
return []
cursor.execute('SELECT DISTINCT(matrix) '
2018-11-25 18:27:38 +01:00
'FROM "' + Typification._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()
2018-11-25 18:27:38 +01:00
Service = pool.get('lims.service')
if Service.search_count([('sample', '=', self.id)]) != 0:
return True
return False
2019-06-14 00:38:02 +02:00
@fields.depends('product_type', 'matrix')
def on_change_with_obj_description(self):
cursor = Transaction().connection.cursor()
ObjectiveDescription = Pool().get('lims.objective_description')
if not self.product_type or not self.matrix:
return None
cursor.execute('SELECT id '
'FROM "' + ObjectiveDescription._table + '" '
'WHERE product_type = %s '
'AND matrix = %s',
(self.product_type.id, self.matrix.id))
res = cursor.fetchone()
return res and res[0] or None
@classmethod
def check_delete(cls, samples):
for sample in samples:
if sample.entry and sample.entry.state != 'draft':
2019-07-23 23:27:33 +02:00
raise UserError(
gettext('lims.msg_delete_sample', sample=sample.rec_name))
@classmethod
def delete(cls, samples):
cls.check_delete(samples)
2020-08-06 19:52:36 +02:00
super().delete(samples)
@staticmethod
def default_entry_view():
if (Transaction().context.get('entry', 0) > 0):
return Transaction().context.get('entry')
return None
2020-03-05 05:24:51 +01:00
@fields.depends('entry', '_parent_entry.id')
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) > 0):
return Transaction().context.get('party')
return None
@fields.depends('entry', '_parent_entry.party')
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
@staticmethod
def default_zone_required():
Config = Pool().get('lims.configuration')
return Config(1).zone_required
def get_zone_required(self, name=None):
return self.default_zone_required()
@staticmethod
def default_zone():
Party = Pool().get('party.party')
if (Transaction().context.get('party', 0) > 0):
party = Party(Transaction().context.get('party'))
if party.entry_zone:
return party.entry_zone.id
@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] = {}
if cls._fields[name]._type == 'many2one':
for s in samples:
field = getattr(s.entry, name, None)
result[name][s.id] = field.id if field else None
else:
for s in samples:
result[name][s.id] = getattr(s.entry, name, None)
return result
@classmethod
def search_entry_field(cls, name, clause):
return [('entry.' + name,) + tuple(clause[1:])]
def _order_entry_field(name):
def order_field(tables):
Entry = Pool().get('lims.entry')
field = Entry._fields[name]
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(name, entry_tables, Entry)
return staticmethod(order_field)
order_invoice_party = _order_entry_field('invoice_party')
@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)
2020-07-17 22:40:45 +02:00
@staticmethod
def order_state(tables):
table, _ = tables[None]
order = [Case((table.state == 'draft', 1),
else_=Case((table.state == 'annulled', 2),
else_=Case((table.state == 'pending_planning', 3),
else_=Case((table.state == 'planned', 4),
else_=Case((table.state == 'in_lab', 5),
else_=Case((table.state == 'lab_pending_acceptance', 6),
else_=Case((table.state == 'pending_report', 7),
else_=Case((table.state == 'in_report', 8),
else_=Case((table.state == 'report_released', 9),
else_=0)))))))))]
2020-07-17 22:40:45 +02:00
return order
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
2021-05-04 03:59:16 +02:00
@classmethod
def get_icon(cls, samples, name):
result = {}
for s in samples:
if s.state == 'report_released':
result[s.id] = 'lims-green'
elif s.state == 'draft':
result[s.id] = 'lims-red'
else:
result[s.id] = 'lims-white'
return result
@classmethod
def order_create_date2(cls, tables):
return cls.create_date.convert_order('create_date', tables, cls)
@classmethod
def get_has_results_report(cls, samples, names):
cursor = Transaction().connection.cursor()
pool = Pool()
2018-11-25 18:27:38 +01:00
Fraction = pool.get('lims.fraction')
Service = pool.get('lims.service')
NotebookLine = pool.get('lims.notebook.line')
result = {}
for name in names:
result[name] = {}
for s in samples:
cursor.execute('SELECT f.sample '
2018-11-25 18:27:38 +01:00
'FROM "' + Fraction._table + '" f '
'INNER JOIN "' + Service._table + '" s '
'ON f.id = s.fraction '
2018-11-25 18:27:38 +01:00
'INNER JOIN "' + NotebookLine._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
@classmethod
def get_urgent(cls, samples, name):
pool = Pool()
Service = pool.get('lims.service')
result = {}
for s in samples:
services = Service.search_count([
('sample', '=', s.id),
('urgent', '=', True),
])
result[s.id] = True if services > 0 else False
return result
@classmethod
def search_urgent(cls, name, clause):
field, op, operand = clause
if (op, operand) in (('=', True), ('!=', False)):
return [('fractions.services.urgent', '=', True)]
elif (op, operand) in (('=', False), ('!=', True)):
urgents = cls.search([('fractions.services.urgent', '=', True)])
return [('id', 'not in', [u.id for u in urgents])]
return []
def get_completion_percentage(self, name=None):
cursor = Transaction().connection.cursor()
pool = Pool()
Config = pool.get('lims.configuration')
NotebookLine = pool.get('lims.notebook.line')
EntryDetailAnalysis = pool.get('lims.entry.detail.analysis')
Notebook = pool.get('lims.notebook')
Fraction = pool.get('lims.fraction')
FractionType = pool.get('lims.fraction.type')
_ZERO = Decimal(0)
samples_in_progress = Config(1).samples_in_progress
digits = Sample.completion_percentage.digits[1]
cursor.execute('SELECT nl.notebook, nl.analysis, nl.method, '
'nl.accepted, nl.result, nl.literal_result, '
'nl.result_modifier '
'FROM "' + NotebookLine._table + '" nl '
'INNER JOIN "' + EntryDetailAnalysis._table + '" d '
'ON d.id = nl.analysis_detail '
'INNER JOIN "' + Notebook._table + '" n '
'ON n.id = nl.notebook '
'INNER JOIN "' + Fraction._table + '" f '
'ON f.id = n.fraction '
'INNER JOIN "' + FractionType._table + '" ft '
'ON ft.id = f.type '
'WHERE ft.report = TRUE '
'AND f.sample = %s '
'AND nl.report = TRUE '
'AND nl.annulled = FALSE',
(self.id,))
notebook_lines = cursor.fetchall()
total = len(notebook_lines)
if not total:
return _ZERO
# Check repetitions
oks, to_check = [], []
if samples_in_progress == 'accepted':
for line in notebook_lines:
key = (line[0], line[1], line[2])
if line[3]:
oks.append(key)
else:
to_check.append(key)
elif samples_in_progress == 'result':
for line in notebook_lines:
key = (line[0], line[1], line[2])
if (line[4] not in [None, ''] or
line[5] not in [None, ''] or
line[6] in ['d', 'nd', 'pos',
'neg', 'ni', 'abs', 'pre', 'na']):
oks.append(key)
else:
to_check.append(key)
accepted = len(oks)
if not accepted:
return _ZERO
for key in to_check:
if key in oks:
total -= 1
return Decimal(
Decimal(accepted) / Decimal(total)
).quantize(Decimal(str(10 ** -digits)))
@classmethod
def get_department(cls, samples, name):
result = {}
for s in samples:
field = getattr(s.product_type, name, None)
result[s.id] = field.id if field else None
return result
@classmethod
def search_department(cls, name, clause):
return [('product_type.' + name,) + tuple(clause[1:])]
@staticmethod
def order_department(tables):
ProductType = Pool().get('lims.product.type')
field = ProductType._fields['department']
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('department', product_type_tables,
ProductType)
@classmethod
def get_results_reports_list(cls, samples, name):
cursor = Transaction().connection.cursor()
pool = Pool()
ResultsReport = pool.get('lims.results_report')
ResultsVersion = pool.get('lims.results_report.version')
ResultsDetail = pool.get('lims.results_report.version.detail')
ResultsSample = pool.get('lims.results_report.version.detail.sample')
Notebook = pool.get('lims.notebook')
Fraction = pool.get('lims.fraction')
result = {}
for s in samples:
result[s.id] = ''
cursor.execute('SELECT r.number '
'FROM "' + ResultsReport._table + '" r '
'INNER JOIN "' + ResultsVersion._table + '" rv '
'ON r.id = rv.results_report '
'INNER JOIN "' + ResultsDetail._table + '" rd '
'ON rv.id = rd.report_version '
'INNER JOIN "' + ResultsSample._table + '" rs '
'ON rd.id = rs.version_detail '
'INNER JOIN "' + Notebook._table + '" n '
'ON n.id = rs.notebook '
'INNER JOIN "' + Fraction._table + '" f '
'ON f.id = n.fraction '
'WHERE f.sample = %s',
(s.id,))
details = [x[0] for x in cursor.fetchall()]
if details:
result[s.id] = ', '.join(details)
return result
@classmethod
def search_results_reports_list(cls, name, clause):
cursor = Transaction().connection.cursor()
pool = Pool()
ResultsReport = pool.get('lims.results_report')
ResultsVersion = pool.get('lims.results_report.version')
ResultsDetail = pool.get('lims.results_report.version.detail')
ResultsSample = pool.get('lims.results_report.version.detail.sample')
Notebook = pool.get('lims.notebook')
Fraction = pool.get('lims.fraction')
value = clause[2]
cursor.execute('SELECT f.sample '
'FROM "' + ResultsReport._table + '" r '
'INNER JOIN "' + ResultsVersion._table + '" rv '
'ON r.id = rv.results_report '
'INNER JOIN "' + ResultsDetail._table + '" rd '
'ON rv.id = rd.report_version '
'INNER JOIN "' + ResultsSample._table + '" rs '
'ON rd.id = rs.version_detail '
'INNER JOIN "' + Notebook._table + '" n '
'ON n.id = rs.notebook '
'INNER JOIN "' + Fraction._table + '" f '
'ON f.id = n.fraction '
'WHERE r.number ILIKE %s',
(value,))
samples_ids = [x[0] for x in cursor.fetchall()]
if not samples_ids:
return [('id', '=', -1)]
return [('id', 'in', samples_ids)]
2020-07-17 22:40:45 +02:00
@classmethod
def update_samples_state(cls, sample_ids):
samples = cls.browse(sample_ids)
for sample in samples:
sample.update_sample_dates()
sample.update_sample_state()
sample.update_qty_lines()
2020-07-17 22:40:45 +02:00
def update_sample_dates(self):
dates = self._get_sample_dates()
for field, value in dates.items():
setattr(self, field, value)
self.save()
def _get_sample_dates(self):
cursor = Transaction().connection.cursor()
pool = Pool()
Fraction = pool.get('lims.fraction')
Service = pool.get('lims.service')
Notebook = pool.get('lims.notebook')
NotebookLine = pool.get('lims.notebook.line')
ResultsReport = pool.get('lims.results_report')
ResultsVersion = pool.get('lims.results_report.version')
ResultsDetail = pool.get('lims.results_report.version.detail')
ResultsSample = pool.get('lims.results_report.version.detail.sample')
res = {}
# Confirmation date
cursor.execute('SELECT MIN(s.confirmation_date) '
'FROM "' + Service._table + '" s '
'INNER JOIN "' + Fraction._table + '" f '
'ON f.id = s.fraction '
'WHERE f.sample = %s',
(self.id,))
res['confirmation_date'] = cursor.fetchone()[0] or None
# Laboratory deadline
cursor.execute('SELECT MAX(s.laboratory_date) '
'FROM "' + Service._table + '" s '
'INNER JOIN "' + Fraction._table + '" f '
'ON f.id = s.fraction '
'WHERE f.sample = %s',
(self.id,))
res['laboratory_date'] = cursor.fetchone()[0] or None
# Date agreed for result
cursor.execute('SELECT MAX(s.report_date) '
'FROM "' + Service._table + '" s '
'INNER JOIN "' + Fraction._table + '" f '
'ON f.id = s.fraction '
'WHERE f.sample = %s',
(self.id,))
res['report_date'] = cursor.fetchone()[0] or None
# Laboratory start date
cursor.execute('SELECT MIN(nl.start_date) '
'FROM "' + NotebookLine._table + '" nl '
'INNER JOIN "' + Service._table + '" s '
'ON s.id = nl.service '
'INNER JOIN "' + Fraction._table + '" f '
'ON f.id = s.fraction '
'WHERE f.sample = %s',
(self.id,))
res['laboratory_start_date'] = cursor.fetchone()[0] or None
# Laboratory end date
laboratory_end_date = None
cursor.execute('SELECT COUNT(*) '
'FROM "' + NotebookLine._table + '" nl '
'INNER JOIN "' + Service._table + '" s '
'ON s.id = nl.service '
'INNER JOIN "' + Fraction._table + '" f '
'ON f.id = s.fraction '
'WHERE f.sample = %s '
'AND nl.report = TRUE '
'AND nl.annulled = FALSE '
'AND nl.end_date IS NULL',
(self.id,))
if cursor.fetchone()[0] == 0:
cursor.execute('SELECT MAX(nl.end_date) '
'FROM "' + NotebookLine._table + '" nl '
'INNER JOIN "' + Service._table + '" s '
'ON s.id = nl.service '
'INNER JOIN "' + Fraction._table + '" f '
'ON f.id = s.fraction '
'WHERE f.sample = %s',
(self.id,))
laboratory_end_date = cursor.fetchone()[0] or None
res['laboratory_end_date'] = laboratory_end_date
# Laboratory acceptance date
laboratory_acceptance_date = None
cursor.execute('SELECT COUNT(*) '
'FROM "' + NotebookLine._table + '" nl '
'INNER JOIN "' + Service._table + '" s '
'ON s.id = nl.service '
'INNER JOIN "' + Fraction._table + '" f '
'ON f.id = s.fraction '
'WHERE f.sample = %s '
'AND nl.report = TRUE '
'AND nl.annulled = FALSE '
'AND nl.acceptance_date IS NULL',
(self.id,))
if cursor.fetchone()[0] == 0:
cursor.execute('SELECT MAX(nl.acceptance_date::date) '
'FROM "' + NotebookLine._table + '" nl '
'INNER JOIN "' + Service._table + '" s '
'ON s.id = nl.service '
'INNER JOIN "' + Fraction._table + '" f '
'ON f.id = s.fraction '
'WHERE f.sample = %s',
(self.id,))
laboratory_acceptance_date = cursor.fetchone()[0] or None
res['laboratory_acceptance_date'] = laboratory_acceptance_date
# Report start date
cursor.execute('SELECT MIN(r.create_date::date) '
'FROM "' + ResultsReport._table + '" r '
'INNER JOIN "' + ResultsVersion._table + '" rv '
'ON rv.results_report = r.id '
'INNER JOIN "' + ResultsDetail._table + '" rd '
'ON rd.report_version = rv.id '
'INNER JOIN "' + ResultsSample._table + '" rs '
'ON rs.version_detail = rd.id '
'INNER JOIN "' + Notebook._table + '" n '
'ON n.id = rs.notebook '
'INNER JOIN "' + Fraction._table + '" f '
'ON f.id = n.fraction '
'WHERE f.sample = %s '
'AND rd.type != \'preliminary\'',
2020-07-17 22:40:45 +02:00
(self.id,))
res['results_report_create_date'] = cursor.fetchone()[0] or None
# Report release date
cursor.execute('SELECT MAX(rd.release_date::date) '
'FROM "' + ResultsDetail._table + '" rd '
'INNER JOIN "' + ResultsSample._table + '" rs '
'ON rs.version_detail = rd.id '
'INNER JOIN "' + Notebook._table + '" n '
'ON n.id = rs.notebook '
'INNER JOIN "' + Fraction._table + '" f '
'ON f.id = n.fraction '
'WHERE f.sample = %s '
'AND rd.valid '
'AND rd.type != \'preliminary\'',
(self.id,))
res['results_report_release_date'] = cursor.fetchone()[0] or None
return res
def update_sample_state(self):
state = self._get_sample_state()
if self.state != state:
self.state = state
self.save()
def _get_sample_state(self):
cursor = Transaction().connection.cursor()
pool = Pool()
Fraction = pool.get('lims.fraction')
Service = pool.get('lims.service')
NotebookLine = pool.get('lims.notebook.line')
if self.results_report_release_date:
return 'report_released'
if self.results_report_create_date:
return 'in_report'
if self.laboratory_acceptance_date:
return 'pending_report'
cursor.execute('SELECT COUNT(*) '
'FROM "' + NotebookLine._table + '" nl '
'INNER JOIN "' + Service._table + '" s '
'ON s.id = nl.service '
'INNER JOIN "' + Fraction._table + '" f '
'ON f.id = s.fraction '
'WHERE f.sample = %s '
'AND nl.annulled = TRUE',
(self.id,))
annulled_lines = cursor.fetchone()[0]
if annulled_lines > 0:
cursor.execute('SELECT COUNT(*) '
'FROM "' + NotebookLine._table + '" nl '
'INNER JOIN "' + Service._table + '" s '
'ON s.id = nl.service '
'INNER JOIN "' + Fraction._table + '" f '
'ON f.id = s.fraction '
'WHERE f.sample = %s',
(self.id,))
if cursor.fetchone()[0] == annulled_lines:
return 'annulled'
2020-07-17 22:40:45 +02:00
if self.laboratory_end_date:
return 'lab_pending_acceptance'
if self.laboratory_start_date:
cursor.execute('SELECT COUNT(*) '
'FROM "' + NotebookLine._table + '" nl '
'INNER JOIN "' + Service._table + '" s '
'ON s.id = nl.service '
'INNER JOIN "' + Fraction._table + '" f '
'ON f.id = s.fraction '
'WHERE f.sample = %s '
'AND nl.report = TRUE '
'AND nl.annulled = FALSE '
'AND nl.end_date IS NOT NULL',
(self.id,))
if cursor.fetchone()[0] > 0:
return 'in_lab'
return 'planned'
if self.confirmation_date:
return 'pending_planning'
return 'draft'
def update_qty_lines(self):
save = False
qty = self._get_qty_lines_pending()
if self.qty_lines_pending != qty:
self.qty_lines_pending = qty
save = True
qty = self._get_qty_lines_pending_acceptance()
if self.qty_lines_pending_acceptance != qty:
self.qty_lines_pending_acceptance = qty
save = True
if save:
self.save()
def _get_qty_lines_pending(self):
cursor = Transaction().connection.cursor()
pool = Pool()
Fraction = pool.get('lims.fraction')
Service = pool.get('lims.service')
NotebookLine = pool.get('lims.notebook.line')
cursor.execute('SELECT COUNT(*) '
'FROM "' + NotebookLine._table + '" nl '
'INNER JOIN "' + Service._table + '" s '
'ON s.id = nl.service '
'INNER JOIN "' + Fraction._table + '" f '
'ON f.id = s.fraction '
'WHERE f.sample = %s '
'AND nl.report = TRUE '
'AND nl.annulled = FALSE '
'AND nl.end_date IS NULL',
(self.id,))
return cursor.fetchone()[0]
def _get_qty_lines_pending_acceptance(self):
cursor = Transaction().connection.cursor()
pool = Pool()
Fraction = pool.get('lims.fraction')
Service = pool.get('lims.service')
NotebookLine = pool.get('lims.notebook.line')
cursor.execute('SELECT COUNT(*) '
'FROM "' + NotebookLine._table + '" nl '
'INNER JOIN "' + Service._table + '" s '
'ON s.id = nl.service '
'INNER JOIN "' + Fraction._table + '" f '
'ON f.id = s.fraction '
'WHERE f.sample = %s '
'AND nl.report = TRUE '
'AND nl.annulled = FALSE '
'AND nl.end_date IS NOT NULL '
'AND nl.acceptance_date IS NULL',
(self.id,))
return cursor.fetchone()[0]
class DuplicateSampleStart(ModelView):
'Copy Sample'
__name__ = 'lims.sample.duplicate.start'
sample = fields.Many2One('lims.sample', 'Sample', readonly=True)
date = fields.DateTime('Date', required=True)
labels = fields.Text('Labels')
class DuplicateSample(Wizard):
'Copy Sample'
__name__ = 'lims.sample.duplicate'
start = StateView('lims.sample.duplicate.start',
'lims.lims_duplicate_sample_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Copy', 'duplicate', 'tryton-ok', default=True),
])
duplicate = StateTransition()
def default_start(self, fields):
2018-11-25 18:27:38 +01:00
Sample = Pool().get('lims.sample')
sample = Sample(Transaction().context['active_id'])
return {
'sample': sample.id,
'date': sample.date,
}
def transition_duplicate(self):
2018-11-25 18:27:38 +01:00
Sample = Pool().get('lims.sample')
sample = self.start.sample
date = self.start.date
labels_list = self._get_labels_list(self.start.labels)
for label in labels_list:
2018-11-25 18:27:38 +01:00
Sample.copy([sample], default={
'label': label,
'date': date,
})
return 'end'
def _get_labels_list(self, labels=None):
if not labels:
return [None]
return labels.split('\n')
def end(self):
return 'reload'
class DuplicateSampleFromEntryStart(ModelView):
'Copy Sample'
__name__ = 'lims.entry.duplicate_sample.start'
entry = fields.Many2One('lims.entry', 'Entry')
sample = fields.Many2One('lims.sample', 'Sample', required=True,
domain=[('entry', '=', Eval('entry'))], depends=['entry'])
date = fields.DateTime('Date', required=True)
labels = fields.Text('Labels')
2020-03-05 05:24:51 +01:00
@fields.depends('sample', '_parent_sample.date')
def on_change_with_date(self, name=None):
if self.sample:
return self.sample.date
return None
class DuplicateSampleFromEntry(Wizard):
'Copy Sample'
__name__ = 'lims.entry.duplicate_sample'
start = StateView('lims.entry.duplicate_sample.start',
'lims.lims_duplicate_sample_from_entry_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Copy', 'duplicate', 'tryton-ok', default=True),
])
duplicate = StateTransition()
def default_start(self, fields):
return {
'entry': Transaction().context['active_id'],
}
def transition_duplicate(self):
2018-11-25 18:27:38 +01:00
Sample = Pool().get('lims.sample')
sample = self.start.sample
date = self.start.date
labels_list = self._get_labels_list(self.start.labels)
for label in labels_list:
2018-11-25 18:27:38 +01:00
Sample.copy([sample], default={
'label': label,
'date': date,
})
return 'end'
def _get_labels_list(self, labels=None):
if not labels:
return [None]
return labels.split('\n')
class ManageServicesAckOfReceipt(ModelView):
'Manage Services'
__name__ = 'lims.manage_services.ack_of_receipt'
class ManageServices(Wizard):
'Manage Services'
__name__ = 'lims.manage_services'
start_state = 'check'
check = StateTransition()
start = StateView('lims.fraction',
'lims.lims_manage_services_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Ok', 'ok', 'tryton-ok', default=True),
])
ok = StateTransition()
send_ack_of_receipt = StateView('lims.manage_services.ack_of_receipt',
'lims.lims_manage_services_ack_of_receipt_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Send', 'send_ack', 'tryton-ok', default=True),
])
send_ack = StateTransition()
def default_start(self, fields):
2018-11-25 18:27:38 +01:00
Fraction = Pool().get('lims.fraction')
2018-11-25 18:27:38 +01:00
fraction = Fraction(Transaction().context['active_id'])
if not fraction:
return {}
not_planned_services_ids = [s.id for s in fraction.services if
s.manage_service_available]
analysis_domain_ids = fraction.on_change_with_analysis_domain()
typification_domain_ids = fraction.on_change_with_typification_domain()
default = {
'id': fraction.id,
'sample': fraction.sample.id,
'entry': fraction.entry.id,
'party': fraction.party.id,
'services': not_planned_services_ids,
'analysis_domain': analysis_domain_ids,
'typification_domain': typification_domain_ids,
'product_type': fraction.product_type.id,
'matrix': fraction.matrix.id,
}
return default
def transition_check(self):
2018-11-25 18:27:38 +01:00
Fraction = Pool().get('lims.fraction')
2018-11-25 18:27:38 +01:00
fraction = Fraction(Transaction().context['active_id'])
if fraction.countersample_date is None:
return 'start'
else:
2019-07-23 23:27:33 +02:00
raise UserError(gettext('lims.msg_counter_sample_date'))
return 'end'
def transition_ok(self):
pool = Pool()
Entry = pool.get('lims.entry')
Fraction = pool.get('lims.fraction')
delete_ack_report_cache = False
fraction = Fraction(Transaction().context['active_id'])
actual_services = self.start.services
original_services = [s for s in fraction.services if
s.manage_service_available]
services_to_delete = []
for service in original_services:
if service not in actual_services:
services_to_delete.append(service)
delete_ack_report_cache = True
if services_to_delete:
self.delete_services(services_to_delete)
for service in actual_services:
if service not in original_services:
self.create_service(service, fraction)
delete_ack_report_cache = True
for original_service in original_services:
for actual_service in actual_services:
if original_service == actual_service:
for field in self._get_comparison_fields():
if (getattr(original_service, field) !=
getattr(actual_service, field)):
self.update_service(original_service,
actual_service, fraction, field)
delete_ack_report_cache = True
break
if delete_ack_report_cache:
entry = Entry(fraction.entry.id)
entry.ack_report_format = None
entry.ack_report_cache = None
entry.save()
original_services = [s for s in fraction.services if not s.planned]
actual_services = self.start.services
for original_service in original_services:
for actual_service in actual_services:
if original_service == actual_service:
update_cie_data = True
for field in ('analysis', 'laboratory', 'method',
'device'):
if (getattr(original_service, field) !=
getattr(actual_service, field)):
update_cie_data = False
break
if update_cie_data:
for detail in actual_service.analysis_detail:
detail.save()
if self._send_ack_of_receipt():
return 'send_ack_of_receipt'
return 'end'
def create_service(self, service, fraction):
pool = Pool()
2018-11-25 18:27:38 +01:00
Service = pool.get('lims.service')
EntryDetailAnalysis = pool.get('lims.entry.detail.analysis')
service_create = [{
'fraction': fraction.id,
'sample': service.sample.id,
'analysis': service.analysis.id,
'laboratory': (service.laboratory.id if service.laboratory
else None),
'method': service.method.id if service.method else None,
'device': service.device.id if service.device else None,
'urgent': service.urgent,
'priority': service.priority,
'estimated_waiting_laboratory': (
service.estimated_waiting_laboratory),
'estimated_waiting_report': (
service.estimated_waiting_report),
'laboratory_date': service.laboratory_date,
'report_date': service.report_date,
'comments': service.comments,
'divide': service.divide,
}]
with Transaction().set_context(manage_service=True):
2018-11-25 18:27:38 +01:00
new_service, = Service.create(service_create)
2018-11-25 18:27:38 +01:00
Service.copy_analysis_comments([new_service])
Service.set_confirmation_date([new_service])
analysis_detail = EntryDetailAnalysis.search([
('service', '=', new_service.id)])
if analysis_detail:
EntryDetailAnalysis.create_notebook_lines(analysis_detail,
fraction)
EntryDetailAnalysis.write(analysis_detail, {
'state': 'unplanned',
})
if fraction.cie_fraction_type:
self._create_blind_samples(analysis_detail, fraction)
return new_service
def delete_services(self, services):
2018-11-25 18:27:38 +01:00
Service = Pool().get('lims.service')
with Transaction().set_user(0, set_context=True):
Service.delete(services)
def update_service(self, original_service, actual_service, fraction,
field_changed):
pool = Pool()
2018-11-25 18:27:38 +01:00
Service = pool.get('lims.service')
NotebookLine = pool.get('lims.notebook.line')
EntryDetailAnalysis = pool.get('lims.entry.detail.analysis')
service_write = {}
service_write[field_changed] = getattr(actual_service, field_changed)
2018-11-25 18:27:38 +01:00
Service.write([original_service], service_write)
update_details = True if field_changed in ('analysis', 'laboratory',
'method', 'device') else False
if update_details:
2018-11-25 18:27:38 +01:00
notebook_lines = NotebookLine.search([
('service', '=', original_service.id),
])
if notebook_lines:
2018-11-25 18:27:38 +01:00
NotebookLine.delete(notebook_lines)
analysis_detail = EntryDetailAnalysis.search([
('service', '=', original_service.id)])
if analysis_detail:
EntryDetailAnalysis.create_notebook_lines(analysis_detail,
fraction)
EntryDetailAnalysis.write(analysis_detail, {
'state': 'unplanned',
'confirmation_date': original_service.confirmation_date,
})
if fraction.cie_fraction_type:
self._create_blind_samples(analysis_detail, fraction)
def _create_blind_samples(self, analysis_detail, fraction):
pool = Pool()
NotebookLine = pool.get('lims.notebook.line')
BlindSample = pool.get('lims.blind_sample')
Date = pool.get('ir.date')
confirmation_date = Date.today()
to_create = []
for detail in analysis_detail:
nlines = NotebookLine.search([
('analysis_detail', '=', detail.id),
])
for nline in nlines:
record = {
'line': nline.id,
'entry': nline.fraction.entry.id,
'sample': nline.fraction.sample.id,
'fraction': nline.fraction.id,
'service': nline.service.id,
'analysis': nline.analysis.id,
'repetition': nline.repetition,
'date': confirmation_date,
'min_value': detail.cie_min_value,
'max_value': detail.cie_max_value,
}
original_fraction = fraction.cie_original_fraction
if original_fraction:
record['original_sample'] = original_fraction.sample.id
record['original_fraction'] = original_fraction.id
original_line = NotebookLine.search([
('notebook.fraction', '=', original_fraction.id),
('analysis', '=', nline.analysis.id),
('repetition', '=', nline.repetition),
])
if original_line:
record['original_line'] = original_line[0].id
record['original_repetition'] = (
original_line[0].repetition)
to_create.append(record)
if to_create:
BlindSample.create(to_create)
def _get_comparison_fields(self):
return ('analysis', 'laboratory', 'method', 'device', 'urgent',
'priority', 'estimated_waiting_laboratory',
'estimated_waiting_report', 'report_date', 'comments', 'divide')
def _send_ack_of_receipt(self):
Cron = Pool().get('ir.cron')
if Cron.search([
('method', '=', 'lims.entry|cron_acknowledgment_of_receipt'),
('active', '=', True),
]):
return True
return False
def transition_send_ack(self):
pool = Pool()
Fraction = pool.get('lims.fraction')
ForwardAcknowledgmentOfReceipt = pool.get(
'lims.entry.acknowledgment.forward', type='wizard')
fraction = Fraction(Transaction().context['active_id'])
entry_ids = [fraction.sample.entry.id]
session_id, _, _ = ForwardAcknowledgmentOfReceipt.create()
acknowledgment_forward = ForwardAcknowledgmentOfReceipt(session_id)
with Transaction().set_context(active_ids=entry_ids):
acknowledgment_forward.transition_start()
return 'end'
class CompleteServices(Wizard):
'Complete Services'
__name__ = 'lims.complete_services'
start = StateTransition()
def transition_start(self):
2018-11-25 18:27:38 +01:00
Fraction = Pool().get('lims.fraction')
fraction = Fraction(Transaction().context['active_id'])
analysis_domain_ids = fraction.on_change_with_analysis_domain()
for service in fraction.services:
if service.analysis.id not in analysis_domain_ids:
raise UserError(gettext('lims.msg_not_typified',
analysis=service.analysis.rec_name,
product_type=fraction.product_type.rec_name,
matrix=fraction.matrix.rec_name,
))
self.complete_analysis_detail(service)
return 'end'
def complete_analysis_detail(self, service):
2018-11-25 18:27:38 +01:00
'Similar to Service.update_analysis_detail(services)'
pool = Pool()
2018-11-25 18:27:38 +01:00
Service = pool.get('lims.service')
EntryDetailAnalysis = pool.get('lims.entry.detail.analysis')
if service.annulled:
return
to_delete = EntryDetailAnalysis.search([
('service', '=', service.id),
('state', 'in', ('draft', 'unplanned')),
])
if to_delete:
with Transaction().set_user(0, set_context=True):
EntryDetailAnalysis.delete(to_delete)
if service.analysis.behavior == 'additional':
return
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:
service_context = {
'product_type': service.fraction.product_type.id,
'matrix': service.fraction.matrix.id,
}
2018-11-25 18:27:38 +01:00
analysis_data.extend(Service._get_included_analysis(
service.analysis, service.analysis.code,
service_context))
to_create = []
for analysis in analysis_data:
2018-11-25 18:27:38 +01:00
if EntryDetailAnalysis.search_count([
('fraction', '=', service.fraction.id),
('analysis', '=', analysis['id']),
('method', '=', analysis['method']),
]) != 0:
continue
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']
values['confirmation_date'] = service.confirmation_date
values['state'] = 'unplanned'
to_create.append(values)
if to_create:
with Transaction().set_user(0, set_context=True):
2018-11-25 18:27:38 +01:00
analysis_detail = EntryDetailAnalysis.create(to_create)
if analysis_detail:
2018-11-25 18:27:38 +01:00
EntryDetailAnalysis.create_notebook_lines(analysis_detail,
service.fraction)
2021-02-18 23:49:22 +01:00
class LoadServices(Wizard):
'Load Services'
__name__ = 'lims.load_services'
start_state = 'check'
check = StateTransition()
load = StateTransition()
def transition_check(self):
Fraction = Pool().get('lims.fraction')
fraction = Fraction(Transaction().context['active_id'])
if (not fraction or not fraction.cie_fraction_type or
not fraction.cie_original_fraction):
return 'end'
return 'load'
def transition_load(self):
pool = Pool()
Fraction = pool.get('lims.fraction')
Service = pool.get('lims.service')
Analysis = pool.get('lims.analysis')
new_fraction = Fraction(Transaction().context['active_id'])
original_fraction = new_fraction.cie_original_fraction
# new services
services = Service.search([
('fraction', '=', original_fraction),
('annulled', '=', False),
])
for service in services:
if not Analysis.is_typified(service.analysis,
new_fraction.product_type, new_fraction.matrix):
continue
new_service, = Service.copy([service], default={
'fraction': new_fraction.id,
})
return 'end'
class AddSampleServiceStart(ModelView):
'Add Sample Services'
__name__ = 'lims.sample.add_service.start'
product_type = fields.Many2One('lims.product.type', 'Product type')
matrix = fields.Many2One('lims.matrix', 'Matrix')
analysis_domain = fields.Many2Many('lims.analysis', None, None,
'Analysis domain')
services = fields.One2Many('lims.create_sample.service', None, 'Services',
required=True, depends=['analysis_domain', 'product_type', 'matrix'],
context={
'analysis_domain': Eval('analysis_domain'),
'product_type': Eval('product_type'), 'matrix': Eval('matrix'),
})
class AddSampleServiceAckOfReceipt(ModelView):
'Add Sample Services'
__name__ = 'lims.sample.add_service.ack_of_receipt'
class AddSampleService(Wizard):
'Add Sample Services'
__name__ = 'lims.sample.add_service'
start = StateView('lims.sample.add_service.start',
'lims.add_sample_service_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Confirm', 'confirm', 'tryton-ok', default=True),
])
confirm = StateTransition()
send_ack_of_receipt = StateView('lims.sample.add_service.ack_of_receipt',
'lims.add_sample_service_ack_of_receipt_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Send', 'send_ack', 'tryton-ok', default=True),
])
send_ack = StateTransition()
def default_start(self, fields):
Sample = Pool().get('lims.sample')
sample = Sample(Transaction().context['active_id'])
if not sample:
return {}
analysis_domain_ids = sample.on_change_with_analysis_domain()
default = {
'product_type': sample.product_type.id,
'matrix': sample.matrix.id,
'analysis_domain': analysis_domain_ids,
'services': [],
}
return default
def transition_confirm(self):
pool = Pool()
Sample = pool.get('lims.sample')
Entry = pool.get('lims.entry')
for sample in Sample.browse(Transaction().context['active_ids']):
delete_ack_report_cache = False
for fraction in sample.fractions:
original_analysis = []
for service in fraction.services:
if service.annulled:
continue
key = (service.analysis.id,
service.method and service.method.id or None)
original_analysis.append(key)
for service in self.start.services:
key = (service.analysis.id,
service.method and service.method.id or None)
if key not in original_analysis:
self.create_service(service, fraction)
delete_ack_report_cache = True
if delete_ack_report_cache:
entry = Entry(sample.entry.id)
entry.ack_report_format = None
entry.ack_report_cache = None
entry.save()
if self._send_ack_of_receipt():
return 'send_ack_of_receipt'
return 'end'
def create_service(self, service, fraction):
pool = Pool()
Service = pool.get('lims.service')
EntryDetailAnalysis = pool.get('lims.entry.detail.analysis')
service_create = [{
'fraction': fraction.id,
'sample': fraction.sample.id,
'analysis': service.analysis.id,
'laboratory': (service.laboratory.id if service.laboratory
else None),
'method': service.method.id if service.method else None,
'device': service.device.id if service.device else None,
'urgent': service.urgent,
'priority': service.priority,
'estimated_waiting_laboratory': (
service.estimated_waiting_laboratory),
'estimated_waiting_report': (
service.estimated_waiting_report),
'laboratory_date': service.laboratory_date,
'report_date': service.report_date,
'divide': service.divide,
}]
with Transaction().set_context(manage_service=True):
new_service, = Service.create(service_create)
Service.copy_analysis_comments([new_service])
Service.set_confirmation_date([new_service])
analysis_detail = EntryDetailAnalysis.search([
('service', '=', new_service.id)])
if analysis_detail:
EntryDetailAnalysis.create_notebook_lines(analysis_detail,
fraction)
EntryDetailAnalysis.write(analysis_detail, {
'state': 'unplanned',
})
return new_service
def _send_ack_of_receipt(self):
Cron = Pool().get('ir.cron')
if Cron.search([
('method', '=', 'lims.entry|cron_acknowledgment_of_receipt'),
('active', '=', True),
]):
return True
return False
def transition_send_ack(self):
pool = Pool()
Sample = pool.get('lims.sample')
ForwardAcknowledgmentOfReceipt = pool.get(
'lims.entry.acknowledgment.forward', type='wizard')
entry_ids = set()
for sample in Sample.browse(Transaction().context['active_ids']):
entry_ids.add(sample.entry.id)
session_id, _, _ = ForwardAcknowledgmentOfReceipt.create()
acknowledgment_forward = ForwardAcknowledgmentOfReceipt(session_id)
with Transaction().set_context(active_ids=list(entry_ids)):
acknowledgment_forward.transition_start()
return 'end'
class EditSampleServiceStart(ModelView):
'Edit Sample Services'
__name__ = 'lims.sample.edit_service.start'
product_type = fields.Many2One('lims.product.type', 'Product type')
matrix = fields.Many2One('lims.matrix', 'Matrix')
analysis_domain = fields.Many2Many('lims.analysis', None, None,
'Analysis domain')
services = fields.One2Many('lims.create_sample.service', None, 'Services',
required=True, depends=['analysis_domain', 'product_type', 'matrix'],
context={
'analysis_domain': Eval('analysis_domain'),
'product_type': Eval('product_type'), 'matrix': Eval('matrix'),
})
class EditSampleServiceAckOfReceipt(ModelView):
'Edit Sample Services'
__name__ = 'lims.sample.edit_service.ack_of_receipt'
class EditSampleService(Wizard):
'Edit Sample Services'
__name__ = 'lims.sample.edit_service'
start = StateView('lims.sample.edit_service.start',
'lims.edit_sample_service_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Confirm', 'confirm', 'tryton-ok', default=True),
])
confirm = StateTransition()
send_ack_of_receipt = StateView('lims.sample.edit_service.ack_of_receipt',
'lims.edit_sample_service_ack_of_receipt_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Send', 'send_ack', 'tryton-ok', default=True),
])
send_ack = StateTransition()
def default_start(self, fields):
Sample = Pool().get('lims.sample')
sample = Sample(Transaction().context['active_id'])
if not sample:
return {}
analysis_domain_ids = sample.on_change_with_analysis_domain()
services = []
for f in sample.fractions:
for s in f.services:
if s.annulled:
continue
services.append({
'analysis_locked': True,
'laboratory_locked': True,
'analysis': s.analysis.id,
'laboratory': s.laboratory and s.laboratory.id or None,
'method': s.method and s.method.id or None,
'device': s.device and s.device.id or None,
'urgent': s.urgent,
'priority': s.priority,
'estimated_waiting_laboratory': (
s.estimated_waiting_laboratory),
'estimated_waiting_report': (
s.estimated_waiting_report),
'laboratory_date': s.laboratory_date,
'report_date': s.report_date,
'divide': s.divide,
})
default = {
'product_type': sample.product_type.id,
'matrix': sample.matrix.id,
'analysis_domain': analysis_domain_ids,
'services': services,
}
return default
def transition_confirm(self):
pool = Pool()
Sample = pool.get('lims.sample')
Entry = pool.get('lims.entry')
actual_analysis = [(s.analysis.id, s.method and s.method.id or None)
for s in self.start.services]
for sample in Sample.browse(Transaction().context['active_ids']):
delete_ack_report_cache = False
for fraction in sample.fractions:
original_analysis = []
for service in fraction.services:
if service.annulled:
continue
key = (service.analysis.id,
service.method and service.method.id or None)
original_analysis.append(key)
if key not in actual_analysis:
self.annul_service(service)
delete_ack_report_cache = True
for service in self.start.services:
key = (service.analysis.id,
service.method and service.method.id or None)
if key not in original_analysis:
self.create_service(service, fraction)
delete_ack_report_cache = True
self.update_fraction_services(fraction)
if delete_ack_report_cache:
entry = Entry(sample.entry.id)
entry.ack_report_format = None
entry.ack_report_cache = None
entry.save()
if self._send_ack_of_receipt():
return 'send_ack_of_receipt'
return 'end'
def annul_service(self, service):
service.annulled = True
service.save()
def create_service(self, service, fraction):
pool = Pool()
Service = pool.get('lims.service')
EntryDetailAnalysis = pool.get('lims.entry.detail.analysis')
service_create = [{
'fraction': fraction.id,
'sample': fraction.sample.id,
'analysis': service.analysis.id,
'laboratory': (service.laboratory.id if service.laboratory
else None),
'method': service.method.id if service.method else None,
'device': service.device.id if service.device else None,
'urgent': service.urgent,
'priority': service.priority,
'estimated_waiting_laboratory': (
service.estimated_waiting_laboratory),
'estimated_waiting_report': (
service.estimated_waiting_report),
'laboratory_date': service.laboratory_date,
'report_date': service.report_date,
'divide': service.divide,
}]
with Transaction().set_context(manage_service=True):
new_service, = Service.create(service_create)
Service.copy_analysis_comments([new_service])
Service.set_confirmation_date([new_service])
analysis_detail = EntryDetailAnalysis.search([
('service', '=', new_service.id)])
if analysis_detail:
EntryDetailAnalysis.create_notebook_lines(analysis_detail,
fraction)
EntryDetailAnalysis.write(analysis_detail, {
'state': 'unplanned',
})
return new_service
def update_fraction_services(self, fraction):
pool = Pool()
Service = pool.get('lims.service')
EntryDetailAnalysis = pool.get('lims.entry.detail.analysis')
NotebookLine = pool.get('lims.notebook.line')
annulled_services = Service.search([
('fraction', '=', fraction),
('annulled', '=', True),
])
for annulled_service in annulled_services:
original_details = EntryDetailAnalysis.search([
('service', '=', annulled_service),
])
for original_detail in original_details:
duplicated_details = self._get_duplicated_details(
original_detail)
if duplicated_details:
self._migrate_detail(original_detail,
duplicated_details[0])
with Transaction().set_user(0, set_context=True):
duplicated_nlines = NotebookLine.search([
('analysis_detail', 'in', duplicated_details),
])
NotebookLine.delete(duplicated_nlines)
EntryDetailAnalysis.delete(duplicated_details)
else:
self._annul_detail(original_detail)
def _get_duplicated_details(self, original):
EntryDetailAnalysis = Pool().get('lims.entry.detail.analysis')
duplicated_details = EntryDetailAnalysis.search([
('fraction', '=', original.fraction),
('analysis', '=', original.analysis),
('method', '=', original.method),
('service', '!=', original.service),
])
return duplicated_details
def _migrate_detail(self, original, duplicated):
NotebookLine = Pool().get('lims.notebook.line')
original.service = duplicated.service
original.analysis_origin = duplicated.analysis_origin
#original.device = duplicated.device
original.save()
with Transaction().set_user(0, set_context=True):
notebook_lines = NotebookLine.search([
('analysis_detail', '=', original),
])
NotebookLine.write(notebook_lines, {
'service': original.service.id,
'analysis_origin': original.analysis_origin,
#'device': original.device and original.device.id or None,
})
def _annul_detail(self, original):
NotebookLine = Pool().get('lims.notebook.line')
if NotebookLine.search_count([
('analysis_detail', '=', original),
('results_report', '!=', None),
]) > 0:
raise UserError(gettext('lims.msg_annul_analysis',
analysis=original.analysis.rec_name))
with Transaction().set_user(0, set_context=True):
notebook_lines = NotebookLine.search([
('analysis_detail', '=', original),
])
NotebookLine.write(notebook_lines, {
'annulled': True,
'annulment_date': datetime.now(),
'accepted': False,
'acceptance_date': None,
'report': False,
})
original.state = 'annulled'
original.save()
def _send_ack_of_receipt(self):
Cron = Pool().get('ir.cron')
if Cron.search([
('method', '=', 'lims.entry|cron_acknowledgment_of_receipt'),
('active', '=', True),
]):
return True
return False
def transition_send_ack(self):
pool = Pool()
Sample = pool.get('lims.sample')
ForwardAcknowledgmentOfReceipt = pool.get(
'lims.entry.acknowledgment.forward', type='wizard')
entry_ids = set()
for sample in Sample.browse(Transaction().context['active_ids']):
entry_ids.add(sample.entry.id)
session_id, _, _ = ForwardAcknowledgmentOfReceipt.create()
acknowledgment_forward = ForwardAcknowledgmentOfReceipt(session_id)
with Transaction().set_context(active_ids=list(entry_ids)):
acknowledgment_forward.transition_start()
return 'end'
class FractionsByLocationsStart(ModelView):
'Fractions by Locations'
__name__ = 'lims.fractions_by_locations.start'
location = fields.Many2One('stock.location', 'Location',
required=True)
@classmethod
def default_location(cls):
Location = Pool().get('stock.location')
locations = Location.search([('type', '=', 'warehouse')])
if len(locations) == 1:
return locations[0].id
class FractionsByLocations(Wizard):
'Fractions by Locations'
__name__ = 'lims.fractions_by_locations'
start = StateView('lims.fractions_by_locations.start',
'lims.fractions_by_locations_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Open', 'open', 'tryton-ok', True),
])
open = StateAction('lims.act_lims_fractions_by_locations')
def do_open(self, action):
Location = Pool().get('stock.location')
childs = Location.search([
('parent', 'child_of', [self.start.location.id]),
])
locations = [l.id for l in childs]
action['pyson_domain'] = PYSONEncoder().encode([
('current_location', 'in', locations),
])
action['name'] += ' (%s)' % self.start.location.rec_name
return action, {}
class CountersampleStorageStart(ModelView):
'Countersamples Storage'
__name__ = 'lims.countersample.storage.start'
report_date_from = fields.Date('Report date from', required=True)
report_date_to = fields.Date('to', required=True)
date_from = fields.Date('Date from', required=True)
date_to = fields.Date('to', required=True)
location_origin = fields.Many2One('stock.location', 'Origin Location',
required=True, domain=[('type', '=', 'storage')])
storage_force = fields.Boolean('Storage force')
class CountersampleStorageEmpty(ModelView):
'Countersamples Storage'
__name__ = 'lims.countersample.storage.empty'
class CountersampleStorageResult(ModelView):
'Countersamples Storage'
__name__ = 'lims.countersample.storage.result'
location_destination = fields.Many2One('stock.location',
'Destination Location', required=True,
domain=[('type', '=', 'storage')])
countersample_date = fields.Date('Storage date', required=True)
fractions = fields.Many2Many('lims.fraction', None, None,
'Fractions', required=True,
domain=[('id', 'in', Eval('fraction_domain'))],
depends=['fraction_domain'])
fraction_domain = fields.One2Many('lims.fraction', None,
'Fractions domain')
shipment = fields.Many2One('stock.shipment.internal', 'Internal Shipment')
class CountersampleStorage(Wizard):
'Countersamples Storage'
__name__ = 'lims.countersample.storage'
start = StateView('lims.countersample.storage.start',
'lims.lims_countersample_storage_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
2019-01-03 16:43:16 +01:00
Button('Search', 'search', 'tryton-forward', default=True),
])
search = StateTransition()
empty = StateView('lims.countersample.storage.empty',
'lims.lims_countersample_storage_empty_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
2019-01-03 16:43:16 +01:00
Button('Search again', 'start', 'tryton-forward', default=True),
])
result = StateView('lims.countersample.storage.result',
'lims.lims_countersample_storage_result_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Storage', 'storage', 'tryton-ok', default=True),
])
storage = StateTransition()
open = StateAction('stock.act_shipment_internal_form')
def default_start(self, fields):
res = {}
for field in ('report_date_from', 'report_date_to',
'date_from', 'date_to'):
if (hasattr(self.start, field) and getattr(self.start, field)):
res[field] = getattr(self.start, field)
for field in ('location_origin',):
if (hasattr(self.start, field) and getattr(self.start, field)):
res[field] = getattr(self.start, field).id
return res
def transition_search(self):
pool = Pool()
Fraction = pool.get('lims.fraction')
NotebookLine = pool.get('lims.notebook.line')
f_list = []
if self.start.storage_force is True:
fractions = Fraction.search([
('countersample_date', '=', None),
('sample.date2', '>=', self.start.date_from),
('sample.date2', '<=', self.start.date_to),
('current_location', '=', self.start.location_origin.id),
])
if fractions:
for f in fractions:
notebook_lines_ids = (
self._get_fraction_notebook_lines_storage_force(f.id))
if not notebook_lines_ids:
continue
notebook_lines = NotebookLine.search([
('id', 'in', notebook_lines_ids),
])
if not notebook_lines:
continue
f_list.append(f)
else:
fractions = Fraction.search([
('countersample_date', '=', None),
('sample.date2', '>=', self.start.date_from),
('sample.date2', '<=', self.start.date_to),
('has_results_report', '=', True),
('current_location', '=', self.start.location_origin.id),
])
if fractions:
for f in fractions:
notebook_lines_ids = self._get_fraction_notebook_lines(
f.id)
if not notebook_lines_ids:
continue
notebook_lines = NotebookLine.search([
('id', 'in', notebook_lines_ids),
])
if not notebook_lines:
continue
# Check repetitions
oks, to_check = [], []
for line in notebook_lines:
key = (line.analysis.id, line.method.id)
if not line.accepted:
to_check.append(key)
else:
oks.append(key)
to_check = list(set(to_check) - set(oks))
if len(to_check) > 0:
continue
all_results_reported = True
for nl in notebook_lines:
if not nl.accepted:
continue
if not nl.results_report:
all_results_reported = False
break
if not self._get_line_reported(nl,
self.start.report_date_from,
self.start.report_date_to):
all_results_reported = False
break
if all_results_reported:
f_list.append(f)
if f_list:
self.result.fractions = f_list
return 'result'
return 'empty'
def _get_fraction_notebook_lines(self, fraction_id):
cursor = Transaction().connection.cursor()
pool = Pool()
NotebookLine = pool.get('lims.notebook.line')
EntryDetailAnalysis = pool.get('lims.entry.detail.analysis')
Service = pool.get('lims.service')
cursor.execute('SELECT nl.id '
'FROM "' + NotebookLine._table + '" nl '
'INNER JOIN "' + EntryDetailAnalysis._table + '" ad '
'ON ad.id = nl.analysis_detail '
'INNER JOIN "' + Service._table + '" srv '
'ON srv.id = nl.service '
'WHERE srv.fraction = %s '
'AND nl.report = TRUE '
'AND nl.annulled = FALSE',
(fraction_id,))
return [x[0] for x in cursor.fetchall()]
def _get_line_reported(self, nl, date_from, date_to):
cursor = Transaction().connection.cursor()
pool = Pool()
ResultsLine = pool.get('lims.results_report.version.detail.line')
ResultsSample = pool.get('lims.results_report.version.detail.sample')
ResultsDetail = pool.get('lims.results_report.version.detail')
ResultsVersion = pool.get('lims.results_report.version')
cursor.execute('SELECT rvdl.id '
'FROM "' + ResultsLine._table + '" rvdl '
'INNER JOIN "' + ResultsSample._table + '" rvds '
'ON rvds.id = rvdl.detail_sample '
'INNER JOIN "' + ResultsDetail._table + '" rvd '
'ON rvd.id = rvds.version_detail '
'INNER JOIN "' + ResultsVersion._table + '" rv '
'ON rv.id = rvd.report_version '
'WHERE rvdl.notebook_line = %s '
'AND rv.results_report = %s '
'AND DATE(COALESCE(rvd.write_date, rvd.create_date)) '
'BETWEEN %s::date AND %s::date',
(nl.id, nl.results_report.id, date_from, date_to))
return cursor.fetchone()
def default_result(self, fields):
fractions = [f.id for f in self.result.fractions]
self.result.fractions = None
return {
'fractions': [],
'fraction_domain': fractions,
}
def transition_storage(self):
Fraction = Pool().get('lims.fraction')
countersample_location = self.result.location_destination
countersample_date = self.result.countersample_date
fractions_to_save = []
for fraction in self.result.fractions:
fraction.countersample_location = countersample_location
fraction.countersample_date = countersample_date
fraction.expiry_date = countersample_date + relativedelta(
months=fraction.storage_time)
fractions_to_save.append(fraction)
Fraction.save(fractions_to_save)
moves = self._get_stock_moves(self.result.fractions)
shipment = self.create_internal_shipment(moves)
if shipment:
self.result.shipment = shipment
return 'open'
return 'end'
def create_internal_shipment(self, moves):
ShipmentInternal = Pool().get('stock.shipment.internal')
shipment = self._get_internal_shipment()
if not shipment:
return
shipment.moves = moves
with Transaction().set_context(check_current_location=False):
shipment.save()
ShipmentInternal.wait([shipment])
ShipmentInternal.assign_force([shipment])
ShipmentInternal.done([shipment])
return shipment
def _get_internal_shipment(self):
pool = Pool()
User = pool.get('res.user')
ShipmentInternal = pool.get('stock.shipment.internal')
company = User(Transaction().user).company
from_location = self.start.location_origin
to_location = self.result.location_destination
planned_date = self.result.countersample_date
with Transaction().set_user(0, set_context=True):
shipment = ShipmentInternal()
2019-07-23 23:27:33 +02:00
shipment.reference = gettext('lims.msg_reference')
shipment.planned_date = planned_date
shipment.planned_start_date = planned_date
shipment.company = company
shipment.from_location = from_location
shipment.to_location = to_location
shipment.state = 'draft'
return shipment
def _get_stock_moves(self, fractions):
pool = Pool()
Config = pool.get('lims.configuration')
User = pool.get('res.user')
Move = pool.get('stock.move')
config_ = Config(1)
if config_.fraction_product:
product = config_.fraction_product
else:
2019-07-23 23:27:33 +02:00
raise UserError(gettext('lims.msg_missing_fraction_product'))
company = User(Transaction().user).company
from_location = self.start.location_origin
to_location = self.result.location_destination
planned_date = self.result.countersample_date
moves = []
for fraction in fractions:
with Transaction().set_user(0, set_context=True):
move = Move()
move.product = product.id
move.fraction = fraction.id
move.quantity = fraction.packages_quantity
move.uom = product.default_uom
move.from_location = from_location
move.to_location = to_location
move.company = company
move.planned_date = planned_date
move.origin = fraction
2021-09-23 01:08:52 +02:00
if move.on_change_with_unit_price_required():
move.unit_price = 0
move.currency = company.currency
move.state = 'draft'
moves.append(move)
return moves
def do_open(self, action):
action['pyson_domain'] = PYSONEncoder().encode([
('id', '=', self.result.shipment.id),
])
return action, {}
def transition_open(self):
return 'end'
def _get_fraction_notebook_lines_storage_force(self, fraction_id):
cursor = Transaction().connection.cursor()
pool = Pool()
NotebookLine = pool.get('lims.notebook.line')
EntryDetailAnalysis = pool.get('lims.entry.detail.analysis')
Service = pool.get('lims.service')
cursor.execute('SELECT nl.id '
'FROM "' + NotebookLine._table + '" nl '
'INNER JOIN "' + EntryDetailAnalysis._table + '" ad '
'ON ad.id = nl.analysis_detail '
'INNER JOIN "' + Service._table + '" srv '
'ON srv.id = nl.service '
'WHERE srv.fraction = %s ',
(fraction_id,))
return [x[0] for x in cursor.fetchall()]
class CountersampleStorageRevertStart(ModelView):
'Revert Countersamples Storage'
__name__ = 'lims.countersample.storage_revert.start'
date_from = fields.Date('Date from', required=True)
date_to = fields.Date('Date to', required=True)
location_origin = fields.Many2One('stock.location', 'Origin Location',
required=True, domain=[('type', '=', 'storage')])
class CountersampleStorageRevertEmpty(ModelView):
'Revert Countersamples Storage'
__name__ = 'lims.countersample.storage_revert.empty'
class CountersampleStorageRevertResult(ModelView):
'Revert Countersamples Storage'
__name__ = 'lims.countersample.storage_revert.result'
location_destination = fields.Many2One('stock.location',
'Destination Location', required=True,
domain=[('type', '=', 'storage')])
fractions = fields.Many2Many('lims.fraction', None, None,
'Fractions', required=True,
domain=[('id', 'in', Eval('fraction_domain'))],
depends=['fraction_domain'])
fraction_domain = fields.One2Many('lims.fraction', None,
'Fractions domain')
shipment = fields.Many2One('stock.shipment.internal', 'Internal Shipment')
class CountersampleStorageRevert(Wizard):
'Revert Countersamples Storage'
__name__ = 'lims.countersample.storage_revert'
start = StateView('lims.countersample.storage_revert.start',
'lims.lims_countersample_storage_revert_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
2019-01-03 16:43:16 +01:00
Button('Search', 'search', 'tryton-forward', default=True),
])
search = StateTransition()
empty = StateView('lims.countersample.storage_revert.empty',
'lims.lims_countersample_storage_revert_empty_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
2019-01-03 16:43:16 +01:00
Button('Search again', 'start', 'tryton-forward', default=True),
])
result = StateView('lims.countersample.storage_revert.result',
'lims.lims_countersample_storage_revert_result_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Revert', 'revert', 'tryton-ok', default=True),
])
revert = StateTransition()
open = StateAction('stock.act_shipment_internal_form')
def default_start(self, fields):
res = {}
for field in ('date_from', 'date_to'):
if (hasattr(self.start, field) and getattr(self.start, field)):
res[field] = getattr(self.start, field)
for field in ('location_origin',):
if (hasattr(self.start, field) and getattr(self.start, field)):
res[field] = getattr(self.start, field).id
return res
def transition_search(self):
Fraction = Pool().get('lims.fraction')
fractions = Fraction.search([
('countersample_date', '>=', self.start.date_from),
('countersample_date', '<=', self.start.date_to),
('current_location', '=', self.start.location_origin.id),
])
if fractions:
self.result.fractions = fractions
return 'result'
return 'empty'
def default_result(self, fields):
fractions = [f.id for f in self.result.fractions]
self.result.fractions = None
return {
'fractions': [],
'fraction_domain': fractions,
}
def transition_revert(self):
Fraction = Pool().get('lims.fraction')
fractions_to_save = []
for fraction in self.result.fractions:
fraction.countersample_location = None
fraction.countersample_date = None
fraction.expiry_date = None
fractions_to_save.append(fraction)
Fraction.save(fractions_to_save)
moves = self._get_stock_moves(self.result.fractions)
shipment = self.create_internal_shipment(moves)
if shipment:
self.result.shipment = shipment
return 'open'
return 'end'
def create_internal_shipment(self, moves):
ShipmentInternal = Pool().get('stock.shipment.internal')
shipment = self._get_internal_shipment()
if not shipment:
return
shipment.moves = moves
with Transaction().set_context(check_current_location=False):
shipment.save()
ShipmentInternal.wait([shipment])
ShipmentInternal.assign_force([shipment])
ShipmentInternal.done([shipment])
return shipment
def _get_internal_shipment(self):
pool = Pool()
User = pool.get('res.user')
Date = pool.get('ir.date')
ShipmentInternal = pool.get('stock.shipment.internal')
company = User(Transaction().user).company
from_location = self.start.location_origin
to_location = self.result.location_destination
today = Date.today()
with Transaction().set_user(0, set_context=True):
shipment = ShipmentInternal()
2019-07-23 23:27:33 +02:00
shipment.reference = gettext('lims.msg_reference_reversion')
shipment.planned_date = today
shipment.planned_start_date = today
shipment.company = company
shipment.from_location = from_location
shipment.to_location = to_location
shipment.state = 'draft'
return shipment
def _get_stock_moves(self, fractions):
pool = Pool()
Config = pool.get('lims.configuration')
User = pool.get('res.user')
Date = pool.get('ir.date')
Move = pool.get('stock.move')
config_ = Config(1)
if config_.fraction_product:
product = config_.fraction_product
else:
2019-07-23 23:27:33 +02:00
raise UserError(gettext('lims.msg_missing_fraction_product'))
company = User(Transaction().user).company
from_location = self.start.location_origin
to_location = self.result.location_destination
today = Date.today()
moves = []
for fraction in fractions:
with Transaction().set_user(0, set_context=True):
move = Move()
move.product = product.id
move.fraction = fraction.id
move.quantity = fraction.packages_quantity
move.uom = product.default_uom
move.from_location = from_location
move.to_location = to_location
move.company = company
move.planned_date = today
move.origin = fraction
2021-09-23 01:08:52 +02:00
if move.on_change_with_unit_price_required():
move.unit_price = 0
move.currency = company.currency
move.state = 'draft'
moves.append(move)
return moves
def do_open(self, action):
action['pyson_domain'] = PYSONEncoder().encode([
('id', '=', self.result.shipment.id),
])
return action, {}
def transition_open(self):
return 'end'
class CountersampleDischargeStart(ModelView):
'Countersamples Discharge'
__name__ = 'lims.countersample.discharge.start'
expiry_date_from = fields.Date('Expiry date from', required=True)
expiry_date_to = fields.Date('to', required=True)
date_from = fields.Date('Date from', required=True)
date_to = fields.Date('to', required=True)
location_origin = fields.Many2One('stock.location', 'Origin Location',
required=True, domain=[('type', '=', 'storage')])
class CountersampleDischargeEmpty(ModelView):
'Countersamples Discharge'
__name__ = 'lims.countersample.discharge.empty'
class CountersampleDischargeResult(ModelView):
'Countersamples Discharge'
__name__ = 'lims.countersample.discharge.result'
location_destination = fields.Many2One('stock.location',
'Destination Location', required=True,
domain=[('type', '=', 'lost_found')])
discharge_date = fields.Date('Discharge date', required=True)
fractions = fields.Many2Many('lims.fraction', None, None,
'Fractions', required=True,
domain=[('id', 'in', Eval('fraction_domain'))],
depends=['fraction_domain'])
fraction_domain = fields.One2Many('lims.fraction', None,
'Fractions domain')
shipment = fields.Many2One('stock.shipment.internal', 'Internal Shipment')
class CountersampleDischarge(Wizard):
'Countersamples Discharge'
__name__ = 'lims.countersample.discharge'
start = StateView('lims.countersample.discharge.start',
'lims.lims_countersample_discharge_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
2019-01-03 16:43:16 +01:00
Button('Search', 'search', 'tryton-forward', default=True),
])
search = StateTransition()
empty = StateView('lims.countersample.discharge.empty',
'lims.lims_countersample_discharge_empty_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
2019-01-03 16:43:16 +01:00
Button('Search again', 'start', 'tryton-forward', default=True),
])
result = StateView('lims.countersample.discharge.result',
'lims.lims_countersample_discharge_result_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Discharge', 'discharge', 'tryton-ok', default=True),
])
discharge = StateTransition()
open = StateAction('stock.act_shipment_internal_form')
def default_start(self, fields):
res = {}
for field in ('expiry_date_from', 'expiry_date_to',
'date_from', 'date_to'):
if (hasattr(self.start, field) and getattr(self.start, field)):
res[field] = getattr(self.start, field)
for field in ('location_origin',):
if (hasattr(self.start, field) and getattr(self.start, field)):
res[field] = getattr(self.start, field).id
return res
def transition_search(self):
Fraction = Pool().get('lims.fraction')
fractions = Fraction.search([
('discharge_date', '=', None),
('sample.date2', '>=', self.start.date_from),
('sample.date2', '<=', self.start.date_to),
('expiry_date', '>=', self.start.expiry_date_from),
('expiry_date', '<=', self.start.expiry_date_to),
('current_location', '=', self.start.location_origin.id),
])
if fractions:
self.result.fractions = fractions
return 'result'
return 'empty'
def default_result(self, fields):
fractions = [f.id for f in self.result.fractions]
self.result.fractions = None
return {
'fractions': [],
'fraction_domain': fractions,
}
def transition_discharge(self):
Fraction = Pool().get('lims.fraction')
discharge_date = self.result.discharge_date
fractions_to_save = []
for fraction in self.result.fractions:
fraction.discharge_date = discharge_date
fractions_to_save.append(fraction)
Fraction.save(fractions_to_save)
moves = self._get_stock_moves(self.result.fractions)
shipment = self.create_internal_shipment(moves)
if shipment:
self.result.shipment = shipment
return 'open'
return 'end'
def create_internal_shipment(self, moves):
ShipmentInternal = Pool().get('stock.shipment.internal')
shipment = self._get_internal_shipment()
if not shipment:
return
shipment.moves = moves
with Transaction().set_context(check_current_location=False):
shipment.save()
ShipmentInternal.wait([shipment])
ShipmentInternal.assign_force([shipment])
ShipmentInternal.done([shipment])
return shipment
def _get_internal_shipment(self):
pool = Pool()
User = pool.get('res.user')
ShipmentInternal = pool.get('stock.shipment.internal')
company = User(Transaction().user).company
from_location = self.start.location_origin
to_location = self.result.location_destination
planned_date = self.result.discharge_date
with Transaction().set_user(0, set_context=True):
shipment = ShipmentInternal()
2019-07-23 23:27:33 +02:00
shipment.reference = gettext('lims.msg_reference_discharge')
shipment.planned_date = planned_date
shipment.planned_start_date = planned_date
shipment.company = company
shipment.from_location = from_location
shipment.to_location = to_location
shipment.state = 'draft'
return shipment
def _get_stock_moves(self, fractions):
pool = Pool()
Config = pool.get('lims.configuration')
User = pool.get('res.user')
Move = pool.get('stock.move')
config_ = Config(1)
if config_.fraction_product:
product = config_.fraction_product
else:
2019-07-23 23:27:33 +02:00
raise UserError(gettext('lims.msg_missing_fraction_product'))
company = User(Transaction().user).company
from_location = self.start.location_origin
to_location = self.result.location_destination
planned_date = self.result.discharge_date
moves = []
for fraction in fractions:
with Transaction().set_user(0, set_context=True):
move = Move()
move.product = product.id
move.fraction = fraction.id
move.quantity = fraction.packages_quantity
move.uom = product.default_uom
move.from_location = from_location
move.to_location = to_location
move.company = company
move.planned_date = planned_date
move.origin = fraction
2021-09-23 01:08:52 +02:00
if move.on_change_with_unit_price_required():
move.unit_price = 0
move.currency = company.currency
move.state = 'draft'
moves.append(move)
return moves
def do_open(self, action):
action['pyson_domain'] = PYSONEncoder().encode([
('id', '=', self.result.shipment.id),
])
return action, {}
def transition_open(self):
return 'end'
class FractionDischargeStart(ModelView):
'Fractions Discharge'
__name__ = 'lims.fraction.discharge.start'
date_from = fields.Date('Date from', required=True)
date_to = fields.Date('Date to', required=True)
location_origin = fields.Many2One('stock.location', 'Origin Location',
required=True, domain=[('type', '=', 'storage')])
discharge_force = fields.Boolean('Discharge force')
class FractionDischargeEmpty(ModelView):
'Fractions Discharge'
__name__ = 'lims.fraction.discharge.empty'
class FractionDischargeResult(ModelView):
'Fractions Discharge'
__name__ = 'lims.fraction.discharge.result'
location_destination = fields.Many2One('stock.location',
'Destination Location', required=True,
domain=[('type', '=', 'lost_found')])
discharge_date = fields.Date('Discharge date', required=True)
fractions = fields.Many2Many('lims.fraction', None, None,
'Fractions', required=True,
domain=[('id', 'in', Eval('fraction_domain'))],
depends=['fraction_domain'])
fraction_domain = fields.One2Many('lims.fraction', None,
'Fractions domain')
shipment = fields.Many2One('stock.shipment.internal', 'Internal Shipment')
class FractionDischarge(Wizard):
'Fractions Discharge'
__name__ = 'lims.fraction.discharge'
start = StateView('lims.fraction.discharge.start',
'lims.lims_fraction_discharge_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
2019-01-03 16:43:16 +01:00
Button('Search', 'search', 'tryton-forward', default=True),
])
search = StateTransition()
empty = StateView('lims.fraction.discharge.empty',
'lims.lims_fraction_discharge_empty_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
2019-01-03 16:43:16 +01:00
Button('Search again', 'start', 'tryton-forward', default=True),
])
result = StateView('lims.fraction.discharge.result',
'lims.lims_fraction_discharge_result_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Discharge', 'discharge', 'tryton-ok', default=True),
])
discharge = StateTransition()
open = StateAction('stock.act_shipment_internal_form')
def default_start(self, fields):
res = {}
for field in ('date_from', 'date_to'):
if (hasattr(self.start, field) and getattr(self.start, field)):
res[field] = getattr(self.start, field)
for field in ('location_origin',):
if (hasattr(self.start, field) and getattr(self.start, field)):
res[field] = getattr(self.start, field).id
return res
def transition_search(self):
Fraction = Pool().get('lims.fraction')
if self.start.discharge_force is True:
fractions = Fraction.search([
('discharge_date', '=', None),
('sample.date2', '>=', self.start.date_from),
('sample.date2', '<=', self.start.date_to),
('current_location', '=', self.start.location_origin.id),
])
else:
fractions = Fraction.search([
('discharge_date', '=', None),
('sample.date2', '>=', self.start.date_from),
('sample.date2', '<=', self.start.date_to),
('has_results_report', '=', False),
('current_location', '=', self.start.location_origin.id),
])
if fractions:
self.result.fractions = fractions
return 'result'
return 'empty'
def default_result(self, fields):
fractions = [f.id for f in self.result.fractions]
self.result.fractions = None
return {
'fractions': [],
'fraction_domain': fractions,
}
def transition_discharge(self):
Fraction = Pool().get('lims.fraction')
discharge_date = self.result.discharge_date
fractions_to_save = []
for fraction in self.result.fractions:
fraction.discharge_date = discharge_date
fractions_to_save.append(fraction)
Fraction.save(fractions_to_save)
moves = self._get_stock_moves(self.result.fractions)
shipment = self.create_internal_shipment(moves)
if shipment:
self.result.shipment = shipment
return 'open'
return 'end'
def create_internal_shipment(self, moves):
ShipmentInternal = Pool().get('stock.shipment.internal')
shipment = self._get_internal_shipment()
if not shipment:
return
shipment.moves = moves
with Transaction().set_context(check_current_location=False):
shipment.save()
ShipmentInternal.wait([shipment])
ShipmentInternal.assign_force([shipment])
ShipmentInternal.done([shipment])
return shipment
def _get_internal_shipment(self):
pool = Pool()
User = pool.get('res.user')
ShipmentInternal = pool.get('stock.shipment.internal')
company = User(Transaction().user).company
from_location = self.start.location_origin
to_location = self.result.location_destination
planned_date = self.result.discharge_date
with Transaction().set_user(0, set_context=True):
shipment = ShipmentInternal()
2019-07-23 23:27:33 +02:00
shipment.reference = gettext('lims.msg_reference_fractions_discharge')
shipment.planned_date = planned_date
shipment.planned_start_date = planned_date
shipment.company = company
shipment.from_location = from_location
shipment.to_location = to_location
shipment.state = 'draft'
return shipment
def _get_stock_moves(self, fractions):
pool = Pool()
Config = pool.get('lims.configuration')
User = pool.get('res.user')
Move = pool.get('stock.move')
config_ = Config(1)
if config_.fraction_product:
product = config_.fraction_product
else:
2019-07-23 23:27:33 +02:00
raise UserError(gettext('lims.msg_missing_fraction_product'))
company = User(Transaction().user).company
from_location = self.start.location_origin
to_location = self.result.location_destination
planned_date = self.result.discharge_date
moves = []
for fraction in fractions:
with Transaction().set_user(0, set_context=True):
move = Move()
move.product = product.id
move.fraction = fraction.id
move.quantity = fraction.packages_quantity
move.uom = product.default_uom
move.from_location = from_location
move.to_location = to_location
move.company = company
move.planned_date = planned_date
move.origin = fraction
2021-09-23 01:08:52 +02:00
if move.on_change_with_unit_price_required():
move.unit_price = 0
move.currency = company.currency
move.state = 'draft'
moves.append(move)
return moves
def do_open(self, action):
action['pyson_domain'] = PYSONEncoder().encode([
('id', '=', self.result.shipment.id),
])
return action, {}
def transition_open(self):
return 'end'
class FractionDischargeRevertStart(ModelView):
'Revert Fractions Discharge'
__name__ = 'lims.fraction.discharge_revert.start'
date_from = fields.Date('Date from', required=True)
date_to = fields.Date('Date to', required=True)
location_origin = fields.Many2One('stock.location', 'Origin Location',
required=True, domain=[('type', '=', 'lost_found')])
class FractionDischargeRevertEmpty(ModelView):
'Revert Fractions Discharge'
__name__ = 'lims.fraction.discharge_revert.empty'
class FractionDischargeRevertResult(ModelView):
'Revert Fractions Discharge'
__name__ = 'lims.fraction.discharge_revert.result'
location_destination = fields.Many2One('stock.location',
'Destination Location', required=True,
domain=[('type', '=', 'storage')])
fractions = fields.Many2Many('lims.fraction', None, None,
'Fractions', required=True,
domain=[('id', 'in', Eval('fraction_domain'))],
depends=['fraction_domain'])
fraction_domain = fields.One2Many('lims.fraction', None,
'Fractions domain')
shipment = fields.Many2One('stock.shipment.internal', 'Internal Shipment')
class FractionDischargeRevert(Wizard):
'Revert Fractions Discharge'
__name__ = 'lims.fraction.discharge_revert'
start = StateView('lims.fraction.discharge_revert.start',
'lims.lims_fraction_discharge_revert_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
2019-01-03 16:43:16 +01:00
Button('Search', 'search', 'tryton-forward', default=True),
])
search = StateTransition()
empty = StateView('lims.fraction.discharge_revert.empty',
'lims.lims_fraction_discharge_revert_empty_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
2019-01-03 16:43:16 +01:00
Button('Search again', 'start', 'tryton-forward', default=True),
])
result = StateView('lims.fraction.discharge_revert.result',
'lims.lims_fraction_discharge_revert_result_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Revert', 'revert', 'tryton-ok', default=True),
])
revert = StateTransition()
open = StateAction('stock.act_shipment_internal_form')
def default_start(self, fields):
res = {}
for field in ('date_from', 'date_to'):
if (hasattr(self.start, field) and getattr(self.start, field)):
res[field] = getattr(self.start, field)
for field in ('location_origin',):
if (hasattr(self.start, field) and getattr(self.start, field)):
res[field] = getattr(self.start, field).id
return res
def transition_search(self):
Fraction = Pool().get('lims.fraction')
fractions = Fraction.search([
('discharge_date', '>=', self.start.date_from),
('discharge_date', '<=', self.start.date_to),
('current_location', '=', self.start.location_origin.id),
])
if fractions:
self.result.fractions = fractions
return 'result'
return 'empty'
def default_result(self, fields):
fractions = [f.id for f in self.result.fractions]
self.result.fractions = None
return {
'fractions': [],
'fraction_domain': fractions,
}
def transition_revert(self):
Fraction = Pool().get('lims.fraction')
fractions_to_save = []
for fraction in self.result.fractions:
fraction.discharge_date = None
fractions_to_save.append(fraction)
Fraction.save(fractions_to_save)
moves = self._get_stock_moves(self.result.fractions)
shipment = self.create_internal_shipment(moves)
if shipment:
self.result.shipment = shipment
return 'open'
return 'end'
def create_internal_shipment(self, moves):
ShipmentInternal = Pool().get('stock.shipment.internal')
shipment = self._get_internal_shipment()
if not shipment:
return
shipment.moves = moves
with Transaction().set_context(check_current_location=False):
shipment.save()
ShipmentInternal.wait([shipment])
ShipmentInternal.assign_force([shipment])
ShipmentInternal.done([shipment])
return shipment
def _get_internal_shipment(self):
pool = Pool()
User = pool.get('res.user')
Date = pool.get('ir.date')
ShipmentInternal = pool.get('stock.shipment.internal')
company = User(Transaction().user).company
from_location = self.start.location_origin
to_location = self.result.location_destination
today = Date.today()
with Transaction().set_user(0, set_context=True):
shipment = ShipmentInternal()
2019-07-23 23:27:33 +02:00
shipment.reference = gettext('lims.msg_reference')
shipment.planned_date = today
shipment.planned_start_date = today
shipment.company = company
shipment.from_location = from_location
shipment.to_location = to_location
shipment.state = 'draft'
return shipment
def _get_stock_moves(self, fractions):
pool = Pool()
Config = pool.get('lims.configuration')
User = pool.get('res.user')
Date = pool.get('ir.date')
Move = pool.get('stock.move')
config_ = Config(1)
if config_.fraction_product:
product = config_.fraction_product
else:
2019-07-23 23:27:33 +02:00
raise UserError(gettext('lims.msg_missing_fraction_product'))
company = User(Transaction().user).company
from_location = self.start.location_origin
to_location = self.result.location_destination
today = Date.today()
moves = []
for fraction in fractions:
with Transaction().set_user(0, set_context=True):
move = Move()
move.product = product.id
move.fraction = fraction.id
move.quantity = fraction.packages_quantity
move.uom = product.default_uom
move.from_location = from_location
move.to_location = to_location
move.company = company
move.planned_date = today
move.origin = fraction
2021-09-23 01:08:52 +02:00
if move.on_change_with_unit_price_required():
move.unit_price = 0
move.currency = company.currency
move.state = 'draft'
moves.append(move)
return moves
def do_open(self, action):
action['pyson_domain'] = PYSONEncoder().encode([
('id', '=', self.result.shipment.id),
])
return action, {}
def transition_open(self):
return 'end'
class CreateSampleStart(ModelView):
'Create Sample'
__name__ = 'lims.create_sample.start'
party = fields.Many2One('party.party', 'Party', required=True,
states={'invisible': ~Eval('multi_party')},
domain=[('id', 'in', Eval('party_domain'))],
depends=['party_domain', 'multi_party'])
party_domain = fields.Many2Many('party.party',
None, None, 'Party domain')
multi_party = fields.Boolean('Multi Party')
date = fields.DateTime('Date', required=True)
producer = fields.Many2One('lims.sample.producer', 'Producer company',
domain=[('party', '=', Eval('party'))], depends=['party'])
sample_client_description = fields.Char(
'Product described by the client', required=True)
sample_client_description_lang = fields.Char(
'Product described by the client (foreign language)',
states={'readonly': ~Eval('foreign_language')},
depends=['foreign_language'])
product_type = fields.Many2One('lims.product.type', 'Product type',
required=True, states={'readonly': Bool(Eval('services'))},
domain=[('id', 'in', Eval('product_type_domain'))],
depends=['product_type_domain', 'services'])
product_type_domain = fields.Many2Many('lims.product.type', None, None,
'Product type domain')
matrix = fields.Many2One('lims.matrix', 'Matrix', required=True,
states={'readonly': Bool(Eval('services'))},
domain=[('id', 'in', Eval('matrix_domain'))],
depends=['matrix_domain', 'services'])
matrix_domain = fields.Many2Many('lims.matrix', None, None,
'Matrix domain')
2019-06-14 00:38:02 +02:00
obj_description = fields.Many2One('lims.objective_description',
'Objective description', depends=['product_type', 'matrix'],
domain=[
('product_type', '=', Eval('product_type')),
('matrix', '=', Eval('matrix')),
])
obj_description_manual = fields.Char(
'Manual Objective description', depends=['obj_description'],
states={'readonly': Bool(Eval('obj_description'))})
obj_description_manual_lang = fields.Char(
'Manual Objective description (foreign language)',
states={
'readonly': Or(
Bool(Eval('obj_description')),
~Eval('foreign_language')),
},
depends=['obj_description', 'foreign_language'])
fraction_state = fields.Many2One('lims.packaging.integrity',
'Package state', required=True)
package_type = fields.Many2One('lims.packaging.type', 'Package type',
required=True)
packages_quantity = fields.Integer('Packages quantity', required=True)
restricted_entry = fields.Boolean('Restricted entry',
states={'readonly': True})
zone = fields.Many2One('lims.zone', 'Zone',
states={'required': Bool(Eval('zone_required'))},
depends=['zone_required'])
zone_required = fields.Boolean('Zone required',
states={'readonly': True})
trace_report = fields.Boolean('Trace report')
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'])
labels = fields.Text('Labels')
fraction_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)
shared = fields.Boolean('Shared')
analysis_domain = fields.Function(fields.Many2Many('lims.analysis',
None, None, 'Analysis domain'), 'on_change_with_analysis_domain')
services = fields.One2Many('lims.create_sample.service', None, 'Services',
states={'required': ~Eval('without_services')},
context={
'analysis_domain': Eval('analysis_domain'),
'product_type': Eval('product_type'), 'matrix': Eval('matrix'),
},
depends=['analysis_domain', 'product_type', 'matrix',
'without_services'])
without_services = fields.Boolean('Without services')
2020-05-05 04:58:54 +02:00
attributes = fields.Dict('lims.sample.attribute', 'Attributes')
foreign_language = fields.Boolean('Foreign Language')
@fields.depends('product_type', 'matrix', 'matrix_domain')
def on_change_product_type(self):
matrix_domain = []
matrix = None
if self.product_type:
matrix_domain = self.on_change_with_matrix_domain()
if len(matrix_domain) == 1:
matrix = matrix_domain[0]
self.matrix_domain = matrix_domain
self.matrix = matrix
@fields.depends('product_type')
def on_change_with_matrix_domain(self, name=None):
cursor = Transaction().connection.cursor()
Typification = Pool().get('lims.typification')
if not self.product_type:
return []
cursor.execute('SELECT DISTINCT(matrix) '
'FROM "' + Typification._table + '" '
'WHERE product_type = %s '
'AND valid',
(self.product_type.id,))
res = cursor.fetchall()
if not res:
return []
return [x[0] for x in res]
@fields.depends('product_type', 'matrix')
def on_change_with_analysis_domain(self, name=None):
cursor = Transaction().connection.cursor()
pool = Pool()
Typification = pool.get('lims.typification')
CalculatedTypification = pool.get('lims.typification.calculated')
Analysis = pool.get('lims.analysis')
if not self.product_type or not self.matrix:
return []
cursor.execute('SELECT DISTINCT(analysis) '
'FROM "' + Typification._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 "' + Analysis._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 "' + CalculatedTypification._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 "' + Analysis._table + '" '
'WHERE behavior = \'additional\' '
'AND state = \'active\'')
additional_analysis = [a[0] for a in cursor.fetchall()]
return typified_analysis + typified_sets_groups + additional_analysis
2020-03-05 05:24:51 +01:00
@fields.depends('product_type', 'matrix', 'zone',
'_parent_product_type.restricted_entry',
'_parent_matrix.restricted_entry', '_parent_zone.restricted_entry')
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('fraction_type', 'storage_location', 'package_type',
'fraction_state')
def on_change_fraction_type(self):
if not self.fraction_type:
return
if (not self.storage_location and
self.fraction_type.default_storage_location):
self.storage_location = self.fraction_type.default_storage_location
if (not self.package_type and
self.fraction_type.default_package_type):
self.package_type = self.fraction_type.default_package_type
if (not self.fraction_state and
self.fraction_type.default_fraction_state):
self.fraction_state = self.fraction_type.default_fraction_state
2020-03-05 05:24:51 +01:00
@fields.depends('fraction_type', 'storage_location',
'_parent_fraction_type.max_storage_time',
'_parent_storage_location.storage_time')
def on_change_with_storage_time(self, name=None):
if self.fraction_type and self.fraction_type.max_storage_time:
return self.fraction_type.max_storage_time
if self.storage_location and self.storage_location.storage_time:
return self.storage_location.storage_time
return 3
2019-06-14 00:38:02 +02:00
@fields.depends('product_type', 'matrix')
def on_change_with_obj_description(self):
cursor = Transaction().connection.cursor()
ObjectiveDescription = Pool().get('lims.objective_description')
if not self.product_type or not self.matrix:
return None
cursor.execute('SELECT id '
'FROM "' + ObjectiveDescription._table + '" '
'WHERE product_type = %s '
'AND matrix = %s',
(self.product_type.id, self.matrix.id))
res = cursor.fetchone()
return res and res[0] or None
class CreateSampleService(ModelView):
'Service'
__name__ = 'lims.create_sample.service'
analysis = fields.Many2One('lims.analysis', 'Analysis/Set/Group',
required=True, domain=[
('id', 'in', Eval('context', {}).get('analysis_domain', [])),
])
laboratory = fields.Many2One('lims.laboratory', 'Laboratory',
domain=[('id', 'in', Eval('laboratory_domain'))],
states={'required': Bool(Eval('laboratory_domain'))},
depends=['laboratory_domain'])
laboratory_domain = fields.Many2Many('lims.laboratory',
None, None, 'Laboratory domain')
method = fields.Many2One('lims.lab.method', 'Method',
domain=[('id', 'in', Eval('method_domain'))],
states={'required': Bool(Eval('method_domain'))},
depends=['method_domain'])
method_domain = fields.Many2Many('lims.lab.method',
None, None, 'Method domain')
device = fields.Many2One('lims.lab.device', 'Device',
domain=[('id', 'in', Eval('device_domain'))],
states={'required': Bool(Eval('device_domain'))},
depends=['device_domain'])
device_domain = fields.Many2Many('lims.lab.device',
None, None, 'Device domain')
urgent = fields.Boolean('Urgent')
priority = fields.Integer('Priority')
estimated_waiting_laboratory = fields.Integer(
'Number of days for Laboratory',
states={'readonly': ~Eval('report_date_readonly')},
depends=['report_date_readonly'])
estimated_waiting_report = fields.Integer(
'Number of days for Reporting',
states={'readonly': ~Eval('report_date_readonly')},
depends=['report_date_readonly'])
laboratory_date = fields.Date('Laboratory deadline',
states={'readonly': Bool(Eval('report_date_readonly'))},
depends=['report_date_readonly'])
report_date = fields.Date('Date agreed for result',
states={'readonly': Bool(Eval('report_date_readonly'))},
depends=['report_date_readonly'])
report_date_readonly = fields.Boolean('Report deadline Readonly')
divide = fields.Boolean('Divide Report')
analysis_locked = fields.Boolean('Analysis/Set/Group Locked')
laboratory_locked = fields.Boolean('Laboratory Locked')
explode_analysis = fields.Boolean(
'Load included analyzes individually',
states={'invisible': Bool(Eval('explode_analysis_invisible'))},
depends=['explode_analysis_invisible'])
explode_analysis_invisible = fields.Boolean(
'Load included analyzes individually Invisible')
@staticmethod
def default_explode_analysis():
return False
@staticmethod
def default_explode_analysis_invisible():
ModelData = Pool().get('ir.model.data')
# check if called from wizard Create Sample
action_id = Transaction().context.get('action_id')
if (action_id and action_id == ModelData.get_id(
'lims', 'wiz_lims_create_sample')):
return False
return True
@staticmethod
def default_analysis_locked():
return False
@staticmethod
def default_laboratory_locked():
return False
@staticmethod
def default_urgent():
return False
@staticmethod
def default_priority():
return 0
@staticmethod
def default_divide():
return False
@staticmethod
def default_report_date_readonly():
return True
@fields.depends('analysis', 'analysis_locked')
def on_change_analysis(self):
analysis_id = self.analysis.id if self.analysis else None
product_type_id = Transaction().context.get('product_type', None)
matrix_id = Transaction().context.get('matrix', None)
laboratory_id = None
laboratory_domain = []
method_id = None
method_domain = []
device_id = None
device_domain = []
if analysis_id:
default_laboratory = self._get_default_laboratory(analysis_id,
product_type_id, matrix_id)
if default_laboratory:
laboratory_id = default_laboratory
laboratory_domain = self._get_laboratory_domain(analysis_id)
method_domain = self._get_method_domain(analysis_id,
product_type_id, matrix_id)
if len(method_domain) == 1:
method_id = method_domain[0]
if laboratory_id:
device_domain = self._get_device_domain(analysis_id,
laboratory_id)
if len(device_domain) == 1:
device_id = device_domain[0]
self.estimated_waiting_laboratory = (
self.analysis.estimated_waiting_laboratory)
self.estimated_waiting_report = (
self.analysis.estimated_waiting_report)
self.laboratory_domain = laboratory_domain
self.method_domain = method_domain
self.device_domain = device_domain
if not self.analysis_locked:
self.laboratory = laboratory_id
self.method = method_id
self.device = device_id
self.analysis_locked = False
@fields.depends('analysis', 'laboratory', 'laboratory_locked')
def on_change_laboratory(self):
analysis_id = self.analysis.id if self.analysis else None
laboratory_id = self.laboratory.id if self.laboratory else None
device_id = None
device_domain = []
if analysis_id and laboratory_id:
device_domain = self._get_device_domain(analysis_id,
laboratory_id)
if len(device_domain) == 1:
device_id = device_domain[0]
self.device_domain = device_domain
if self.laboratory_locked:
self.device = device_id
self.laboratory_locked = False
@staticmethod
def _get_default_laboratory(analysis_id, product_type_id, matrix_id):
cursor = Transaction().connection.cursor()
pool = Pool()
Analysis = pool.get('lims.analysis')
AnalysisLaboratory = pool.get('lims.analysis-laboratory')
Typification = pool.get('lims.typification')
if Analysis(analysis_id).type != 'analysis':
return None
cursor.execute('SELECT laboratory '
'FROM "' + Typification._table + '" '
'WHERE product_type = %s '
'AND matrix = %s '
'AND analysis = %s '
'AND valid IS TRUE '
'AND by_default IS TRUE '
'AND laboratory IS NOT NULL',
(product_type_id, matrix_id, analysis_id))
res = cursor.fetchone()
if res:
return res[0]
cursor.execute('SELECT laboratory '
'FROM "' + AnalysisLaboratory._table + '" '
'WHERE analysis = %s '
'AND by_default = TRUE '
'ORDER BY id', (analysis_id,))
res = cursor.fetchone()
if res:
return res[0]
return None
@staticmethod
def _get_laboratory_domain(analysis_id):
cursor = Transaction().connection.cursor()
pool = Pool()
Analysis = pool.get('lims.analysis')
AnalysisLaboratory = pool.get('lims.analysis-laboratory')
if Analysis(analysis_id).type != 'analysis':
return []
cursor.execute('SELECT DISTINCT(laboratory) '
'FROM "' + AnalysisLaboratory._table + '" '
'WHERE analysis = %s',
(analysis_id,))
res = cursor.fetchall()
if not res:
return []
return [x[0] for x in res]
@staticmethod
def _get_method_domain(analysis_id, product_type_id, matrix_id):
cursor = Transaction().connection.cursor()
Typification = Pool().get('lims.typification')
cursor.execute('SELECT DISTINCT(method) '
'FROM "' + Typification._table + '" '
'WHERE product_type = %s '
'AND matrix = %s '
'AND analysis = %s '
'AND valid',
(product_type_id, matrix_id, analysis_id))
res = cursor.fetchall()
if not res:
return []
return [x[0] for x in res]
@staticmethod
def _get_device_domain(analysis_id, laboratory_id):
cursor = Transaction().connection.cursor()
AnalysisDevice = Pool().get('lims.analysis.device')
cursor.execute('SELECT DISTINCT(device) '
'FROM "' + AnalysisDevice._table + '" '
2020-02-26 23:04:15 +01:00
'WHERE active IS TRUE '
'AND analysis = %s '
'AND laboratory = %s '
2020-02-26 23:04:15 +01:00
'AND by_default IS TRUE',
(analysis_id, laboratory_id))
res = cursor.fetchall()
if not res:
return []
return [x[0] for x in res]
@fields.depends('analysis', 'estimated_waiting_laboratory')
def on_change_with_laboratory_date(self, name=None):
pool = Pool()
LabWorkYear = pool.get('lims.lab.workyear')
Date = pool.get('ir.date')
if self.estimated_waiting_laboratory:
date_ = Date.today()
workyear = LabWorkYear(LabWorkYear.find(date_))
date_ = workyear.get_target_date(date_,
self.estimated_waiting_laboratory)
return date_
return None
@fields.depends('analysis', 'estimated_waiting_laboratory',
'estimated_waiting_report')
def on_change_with_report_date(self, name=None):
pool = Pool()
LabWorkYear = pool.get('lims.lab.workyear')
Date = pool.get('ir.date')
if self.estimated_waiting_laboratory or self.estimated_waiting_report:
date_ = Date.today()
workyear = LabWorkYear(LabWorkYear.find(date_))
if self.estimated_waiting_laboratory:
date_ = workyear.get_target_date(date_,
self.estimated_waiting_laboratory)
if self.estimated_waiting_report:
date_ = workyear.get_target_date(date_,
self.estimated_waiting_report)
return date_
return None
class CreateSample(Wizard):
'Create Sample'
__name__ = 'lims.create_sample'
start = StateView('lims.create_sample.start',
'lims.lims_create_sample_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Create', 'create_', 'tryton-ok', default=True),
])
create_ = StateTransition()
def default_start(self, fields):
cursor = Transaction().connection.cursor()
pool = Pool()
Entry = pool.get('lims.entry')
Config = pool.get('lims.configuration')
PartyRelation = pool.get('party.relation')
Typification = pool.get('lims.typification')
config_ = Config(1)
defaults = {
'date': datetime.now(),
'restricted_entry': False,
'zone_required': config_.zone_required,
'trace_report': False,
'storage_time': 3,
'without_services': False,
}
entry = Entry(Transaction().context['active_id'])
if entry.multi_party:
party_domain = [entry.invoice_party.id]
relations = PartyRelation.search([
('to', '=', entry.invoice_party),
('type', '=', config_.invoice_party_relation_type)
])
party_domain.extend([r.from_.id for r in relations])
party_domain = list(set(party_domain))
party_id = party_domain[0] if len(party_domain) == 1 else None
else:
party_domain = [entry.party.id]
party_id = entry.party.id
if entry.party.entry_zone:
defaults['zone'] = entry.party.entry_zone.id
cursor.execute('SELECT DISTINCT(product_type) '
'FROM "' + Typification._table + '" '
'WHERE valid')
res = cursor.fetchall()
if res:
defaults['product_type_domain'] = [x[0] for x in res]
defaults['party'] = party_id
defaults['party_domain'] = party_domain
defaults['multi_party'] = entry.multi_party
defaults['foreign_language'] = (entry.report_language !=
config_.results_report_language)
return defaults
def transition_create_(self):
# TODO: Remove logs
logger = logging.getLogger(__name__)
logger.info('-- CreateSample().transition_create_():INIT --')
pool = Pool()
Sample = pool.get('lims.sample')
Entry = pool.get('lims.entry')
entry_id = Transaction().context['active_id']
entry = Entry(entry_id)
samples_defaults = self._get_samples_defaults(entry_id)
logger.info('.. Sample.create(..)')
sample, = Sample.create(samples_defaults)
foreign_language = entry.report_language.code
if (hasattr(self.start, 'sample_client_description_lang') and
getattr(self.start, 'sample_client_description_lang')):
with Transaction().set_context(language=foreign_language):
sample_lang = Sample(sample.id)
sample_lang.sample_client_description = (
self.start.sample_client_description_lang)
sample_lang.save()
if (hasattr(self.start, 'obj_description_manual_lang') and
getattr(self.start, 'obj_description_manual_lang')):
with Transaction().set_context(language=foreign_language):
sample_lang = Sample(sample.id)
sample_lang.obj_description_manual = (
self.start.obj_description_manual_lang)
sample_lang.save()
labels_list = self._get_labels_list(self.start.labels)
if len(labels_list) > 1:
logger.info('.. Sample.copy(..): %s' % (len(labels_list) - 1))
for label in labels_list[1:]:
if not label:
continue
Sample.copy([sample], default={
'label': label,
})
logger.info('-- CreateSample().transition_create_():END --')
return 'end'
def _get_samples_defaults(self, entry_id):
2019-06-14 00:38:02 +02:00
obj_description_id = None
if (hasattr(self.start, 'obj_description') and
getattr(self.start, 'obj_description')):
obj_description_id = getattr(self.start, 'obj_description').id
producer_id = None
if (hasattr(self.start, 'producer') and
getattr(self.start, 'producer')):
producer_id = getattr(self.start, 'producer').id
zone_id = None
if (hasattr(self.start, 'zone') and
getattr(self.start, 'zone')):
zone_id = getattr(self.start, 'zone').id
restricted_entry = (hasattr(self.start, 'restricted_entry') and
getattr(self.start, 'restricted_entry') or False)
variety_id = None
if (hasattr(self.start, 'variety') and
getattr(self.start, 'variety')):
variety_id = getattr(self.start, 'variety').id
shared = (hasattr(self.start, 'shared') and
getattr(self.start, 'shared') or False)
trace_report = (hasattr(self.start, 'trace_report') and
getattr(self.start, 'trace_report') or False)
report_comments = (hasattr(self.start, 'report_comments') and
getattr(self.start, 'report_comments') or None)
comments = (hasattr(self.start, 'comments') and
getattr(self.start, 'comments') or None)
2020-05-05 04:58:54 +02:00
attributes = (hasattr(self.start, 'attributes') and
getattr(self.start, 'attributes') or None)
# services data
services_defaults = []
if hasattr(self.start, 'services'):
for service in self.start.services:
estimated_waiting_laboratory = (
hasattr(service, 'estimated_waiting_laboratory') and
getattr(service, 'estimated_waiting_laboratory') or None)
estimated_waiting_report = (
hasattr(service, 'estimated_waiting_report') and
getattr(service, 'estimated_waiting_report') or None)
explode_analysis = (hasattr(service, 'explode_analysis') and
getattr(service, 'explode_analysis') or False)
if (explode_analysis and
service.analysis.type in ('set', 'group')):
for included_analysis in self._get_included_analysis(
service.analysis):
services_defaults.append({
'analysis': included_analysis['analysis'],
'laboratory': included_analysis['laboratory'],
'method': included_analysis['method'],
'device': included_analysis['device'],
'urgent': service.urgent,
'priority': service.priority,
'estimated_waiting_laboratory': (
estimated_waiting_laboratory),
'estimated_waiting_report': (
estimated_waiting_report),
'laboratory_date': service.laboratory_date,
'report_date': service.report_date,
'divide': service.divide,
})
else:
services_defaults.append({
'analysis': service.analysis.id,
'laboratory': (service.laboratory.id
if service.laboratory else None),
'method': (service.method.id if service.method
else None),
'device': (service.device.id if service.device
else None),
'urgent': service.urgent,
'priority': service.priority,
'estimated_waiting_laboratory': (
estimated_waiting_laboratory),
'estimated_waiting_report': (
estimated_waiting_report),
'laboratory_date': service.laboratory_date,
'report_date': service.report_date,
'divide': service.divide,
})
# samples data
samples_defaults = []
labels_list = self._get_labels_list(self.start.labels)
for label in labels_list[:1]:
# fraction data
fraction_defaults = {
'type': self.start.fraction_type.id,
'storage_location': self.start.storage_location.id,
'storage_time': self.start.storage_time,
'packages_quantity': self.start.packages_quantity,
'shared': shared,
'package_type': self.start.package_type.id,
'fraction_state': self.start.fraction_state.id,
}
if services_defaults:
fraction_defaults['services'] = [('create', services_defaults)]
sample_defaults = {
'entry': entry_id,
'party': self.start.party.id,
'date': self.start.date,
'producer': producer_id,
'sample_client_description': (
self.start.sample_client_description),
'product_type': self.start.product_type.id,
'matrix': self.start.matrix.id,
2019-06-14 00:38:02 +02:00
'obj_description': obj_description_id,
'obj_description_manual': self.start.obj_description_manual,
'package_state': self.start.fraction_state.id,
'package_type': self.start.package_type.id,
'packages_quantity': self.start.packages_quantity,
'restricted_entry': restricted_entry,
'zone': zone_id,
'trace_report': trace_report,
'report_comments': report_comments,
'comments': comments,
'variety': variety_id,
'label': label,
'fractions': [('create', [fraction_defaults])],
2020-05-05 04:58:54 +02:00
'attributes': attributes,
}
samples_defaults.append(sample_defaults)
return samples_defaults
def _get_included_analysis(self, analysis):
cursor = Transaction().connection.cursor()
pool = Pool()
Typification = pool.get('lims.typification')
childs = []
if analysis.included_analysis:
for included in analysis.included_analysis:
if included.included_analysis.type == 'analysis':
laboratory_id = None
cursor.execute('SELECT laboratory '
'FROM "' + Typification._table + '" '
'WHERE product_type = %s '
'AND matrix = %s '
'AND analysis = %s '
'AND valid IS TRUE '
'AND by_default IS TRUE '
'AND laboratory IS NOT NULL',
(self.start.product_type.id,
self.start.matrix.id,
included.included_analysis.id))
res = cursor.fetchone()
if res:
laboratory_id = res[0]
if not laboratory_id:
for l in included.included_analysis.laboratories:
if l.by_default is True:
laboratory_id = l.laboratory.id
method_id = (included.method.id
if included.method else None)
if not method_id:
cursor.execute('SELECT method '
'FROM "' + Typification._table + '" '
'WHERE product_type = %s '
'AND matrix = %s '
'AND analysis = %s '
'AND valid IS TRUE '
'AND by_default IS TRUE',
(self.start.product_type.id,
self.start.matrix.id,
included.included_analysis.id))
res = cursor.fetchone()
if res:
method_id = res[0]
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({
'analysis': included.included_analysis.id,
'laboratory': laboratory_id,
'method': method_id,
'device': device_id,
})
childs.extend(self._get_included_analysis(
included.included_analysis))
return childs
def _get_labels_list(self, labels=None):
if not labels:
return [None]
return labels.split('\n')
class CountersampleStoragePrintStart(ModelView):
'Countersamples Storage Report'
__name__ = 'lims.countersample.storage.print.start'
report_date_from = fields.Date('Report date from', required=True)
report_date_to = fields.Date('to', required=True)
date_from = fields.Date('Date from', required=True)
date_to = fields.Date('to', required=True)
class CountersampleStoragePrint(Wizard):
'Countersamples Storage Report'
__name__ = 'lims.countersample.storage.print'
start = StateView('lims.countersample.storage.print.start',
'lims.lims_countersample_storage_print_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Print', 'print_', 'tryton-print', default=True),
])
print_ = StateAction('lims.report_countersample_storage')
def do_print_(self, action):
data = {
'report_date_from': self.start.report_date_from,
'report_date_to': self.start.report_date_to,
'date_from': self.start.date_from,
'date_to': self.start.date_to,
}
return action, data
def transition_print_(self):
return 'end'
class CountersampleStorageReport(Report):
'Countersamples Storage Report'
__name__ = 'lims.countersample.storage.report'
@classmethod
2021-09-23 01:07:04 +02:00
def get_context(cls, records, header, data):
pool = Pool()
Fraction = pool.get('lims.fraction')
NotebookLine = pool.get('lims.notebook.line')
2021-09-23 01:07:04 +02:00
report_context = super().get_context(records, header, data)
report_context['company'] = report_context['user'].company
report_context['report_date_from'] = data['report_date_from']
report_context['report_date_to'] = data['report_date_to']
report_context['date_from'] = data['date_from']
report_context['date_to'] = data['date_to']
f_list = []
fractions = Fraction.search([
('countersample_date', '=', None),
('sample.date2', '>=', data['date_from']),
('sample.date2', '<=', data['date_to']),
('has_results_report', '=', True),
], order=[('number', 'ASC')])
for f in fractions:
notebook_lines_ids = cls._get_fraction_notebook_lines(f.id)
if not notebook_lines_ids:
continue
notebook_lines = NotebookLine.search([
('id', 'in', notebook_lines_ids),
])
if not notebook_lines:
continue
# Check repetitions
oks, to_check = [], []
for line in notebook_lines:
key = (line.analysis.id, line.method.id)
if not line.accepted:
to_check.append(key)
else:
oks.append(key)
to_check = list(set(to_check) - set(oks))
if len(to_check) > 0:
continue
all_results_reported = True
for nl in notebook_lines:
if not nl.accepted:
continue
if not nl.results_report:
all_results_reported = False
break
if not cls._get_line_reported(nl, data['report_date_from'],
data['report_date_to']):
all_results_reported = False
break
if all_results_reported:
f_list.append(f)
objects = {}
for fraction in f_list:
if fraction.current_location.id not in objects:
objects[fraction.current_location.id] = {
'location': fraction.current_location.rec_name,
'fractions': [],
}
objects[fraction.current_location.id]['fractions'].append({
'number': fraction.get_formated_number('pt-m-sy-sn-fn'),
'type': fraction.type.code,
'packages': '%s %s' % (fraction.packages_quantity or '',
fraction.package_type.description if fraction.package_type
else ''),
'entry_date': fraction.sample.date2,
'results_reports': cls.get_fraction_results_reports(
fraction.id),
'stp_number': (fraction.sample.entry.project.code
if fraction.sample.entry.project else ''),
'comments': (fraction.comments
if fraction.comments else ''),
})
2019-03-04 15:41:58 +01:00
ordered_objects = sorted(list(objects.values()),
key=lambda x: x['location'])
2021-09-23 01:07:04 +02:00
report_context['records'] = ordered_objects
return report_context
@classmethod
def _get_fraction_notebook_lines(cls, fraction_id):
cursor = Transaction().connection.cursor()
pool = Pool()
NotebookLine = pool.get('lims.notebook.line')
EntryDetailAnalysis = pool.get('lims.entry.detail.analysis')
Service = pool.get('lims.service')
cursor.execute('SELECT nl.id '
'FROM "' + NotebookLine._table + '" nl '
'INNER JOIN "' + EntryDetailAnalysis._table + '" ad '
'ON ad.id = nl.analysis_detail '
'INNER JOIN "' + Service._table + '" srv '
'ON srv.id = nl.service '
'WHERE srv.fraction = %s '
'AND nl.report = TRUE '
'AND nl.annulled = FALSE',
(fraction_id,))
return [x[0] for x in cursor.fetchall()]
@classmethod
def _get_line_reported(cls, nl, date_from, date_to):
cursor = Transaction().connection.cursor()
pool = Pool()
ResultsLine = pool.get('lims.results_report.version.detail.line')
ResultsSample = pool.get('lims.results_report.version.detail.sample')
ResultsDetail = pool.get('lims.results_report.version.detail')
ResultsVersion = pool.get('lims.results_report.version')
cursor.execute('SELECT rvdl.id '
'FROM "' + ResultsLine._table + '" rvdl '
'INNER JOIN "' + ResultsSample._table + '" rvds '
'ON rvds.id = rvdl.detail_sample '
'INNER JOIN "' + ResultsDetail._table + '" rvd '
'ON rvd.id = rvds.version_detail '
'INNER JOIN "' + ResultsVersion._table + '" rv '
'ON rv.id = rvd.report_version '
'WHERE rvdl.notebook_line = %s '
'AND rv.results_report = %s '
'AND DATE(COALESCE(rvd.write_date, rvd.create_date)) '
'BETWEEN %s::date AND %s::date',
(nl.id, nl.results_report.id, date_from, date_to))
return cursor.fetchone()
@classmethod
def get_fraction_results_reports(cls, fraction_id):
cursor = Transaction().connection.cursor()
pool = Pool()
Service = pool.get('lims.service')
NotebookLine = pool.get('lims.notebook.line')
ResultsReport = pool.get('lims.results_report')
cursor.execute('SELECT DISTINCT(nl.results_report) '
'FROM "' + Service._table + '" s '
'INNER JOIN "' + NotebookLine._table + '" nl '
'ON s.id = nl.service '
'WHERE s.fraction = %s '
'AND nl.results_report IS NOT NULL',
(fraction_id,))
res = cursor.fetchall()
if not res:
return ''
result = []
for report_id in res:
results_report = ResultsReport(report_id[0])
result.append('%s (%s)' % (results_report.rec_name,
results_report.create_date2.strftime("%d/%m/%Y")))
return ' '.join(result)
class CountersampleDischargePrintStart(ModelView):
'Countersamples Discharge Report'
__name__ = 'lims.countersample.discharge.print.start'
expiry_date_from = fields.Date('Expiry date from', required=True)
expiry_date_to = fields.Date('to', required=True)
date_from = fields.Date('Date from', required=True)
date_to = fields.Date('to', required=True)
class CountersampleDischargePrint(Wizard):
'Countersamples Discharge Report'
__name__ = 'lims.countersample.discharge.print'
start = StateView('lims.countersample.discharge.print.start',
'lims.lims_countersample_discharge_print_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Print', 'print_', 'tryton-print', default=True),
])
print_ = StateAction('lims.report_countersample_discharge')
def do_print_(self, action):
data = {
'expiry_date_from': self.start.expiry_date_from,
'expiry_date_to': self.start.expiry_date_to,
'date_from': self.start.date_from,
'date_to': self.start.date_to,
}
return action, data
def transition_print_(self):
return 'end'
class CountersampleDischargeReport(Report):
'Countersamples Discharge Report'
__name__ = 'lims.countersample.discharge.report'
@classmethod
2021-09-23 01:07:04 +02:00
def get_context(cls, records, header, data):
pool = Pool()
Fraction = pool.get('lims.fraction')
2021-09-23 01:07:04 +02:00
report_context = super().get_context(records, header, data)
report_context['company'] = report_context['user'].company
report_context['expiry_date_from'] = data['expiry_date_from']
report_context['expiry_date_to'] = data['expiry_date_to']
report_context['date_from'] = data['date_from']
report_context['date_to'] = data['date_to']
fractions = Fraction.search([
('discharge_date', '=', None),
('sample.date2', '>=', data['date_from']),
('sample.date2', '<=', data['date_to']),
('expiry_date', '>=', data['expiry_date_from']),
('expiry_date', '<=', data['expiry_date_to']),
], order=[('number', 'ASC')])
objects = {}
for fraction in fractions:
if fraction.current_location.id not in objects:
objects[fraction.current_location.id] = {
'location': fraction.current_location.rec_name,
'fractions': [],
}
objects[fraction.current_location.id]['fractions'].append({
'number': fraction.get_formated_number('pt-m-sy-sn-fn'),
'type': fraction.type.code,
'packages': '%s %s' % (fraction.packages_quantity or '',
fraction.package_type.description if fraction.package_type
else ''),
'entry_date': fraction.sample.date2,
'results_reports': cls.get_fraction_results_reports(
fraction.id),
'stp_number': (fraction.sample.entry.project.code
if fraction.sample.entry.project else ''),
'countersample_date': fraction.countersample_date or '',
'expiry_date': fraction.expiry_date or '',
'comments': (fraction.comments
if fraction.comments else ''),
})
2019-03-04 15:41:58 +01:00
ordered_objects = sorted(list(objects.values()),
key=lambda x: x['location'])
2021-09-23 01:07:04 +02:00
report_context['records'] = ordered_objects
return report_context
@classmethod
def get_fraction_results_reports(cls, fraction_id):
cursor = Transaction().connection.cursor()
pool = Pool()
Service = pool.get('lims.service')
NotebookLine = pool.get('lims.notebook.line')
ResultsReport = pool.get('lims.results_report')
cursor.execute('SELECT DISTINCT(nl.results_report) '
'FROM "' + Service._table + '" s '
'INNER JOIN "' + NotebookLine._table + '" nl '
'ON s.id = nl.service '
'WHERE s.fraction = %s '
'AND nl.results_report IS NOT NULL',
(fraction_id,))
res = cursor.fetchall()
if not res:
return ''
result = []
for report_id in res:
results_report = ResultsReport(report_id[0])
result.append('%s (%s)' % (results_report.rec_name,
results_report.create_date2.strftime("%d/%m/%Y")))
return ' '.join(result)
2020-07-01 16:00:49 +02:00
class Referral(ModelSQL, ModelView):
'Referral of Services'
__name__ = 'lims.referral'
_rec_name = 'number'
2020-07-01 16:00:49 +02:00
_states = {'readonly': Eval('state') != 'draft'}
_depends = ['state']
number = fields.Char('Number', select=True, readonly=True)
2020-07-01 16:00:49 +02:00
date = fields.Date('Date', required=True,
states=_states, depends=_depends)
2021-07-22 01:08:31 +02:00
sent_date = fields.Date('Sent date', readonly=True)
2020-07-01 16:00:49 +02:00
laboratory = fields.Many2One('party.party', 'Destination Laboratory',
required=True, states=_states, depends=_depends)
carrier = fields.Many2One('carrier', 'Carrier',
states=_states, depends=_depends)
comments = fields.Text('Comments', states=_states, depends=_depends)
2020-07-01 16:00:49 +02:00
state = fields.Selection([
('draft', 'Draft'),
('sent', 'Sent'),
('done', 'Done'),
], 'State', required=True, readonly=True)
services = fields.One2Many('lims.entry.detail.analysis',
'referral', 'Services',
states=_states, depends=_depends,
add_remove=[
('state', '=', 'unplanned'),
('referral', '=', None),
])
del _states, _depends
2020-07-01 16:00:49 +02:00
@classmethod
def __setup__(cls):
2020-08-06 19:52:36 +02:00
super().__setup__()
cls._order.insert(0, ('number', 'DESC'))
2020-07-01 16:00:49 +02:00
cls._buttons.update({
'send': {
'invisible': Eval('state') != 'draft',
'depends': ['state'],
},
})
@staticmethod
def default_date():
Date = Pool().get('ir.date')
return Date.today()
@staticmethod
def default_state():
return 'draft'
def get_rec_name(self, name):
return self.laboratory.name
@classmethod
def search_rec_name(cls, name, clause):
return [('laboratory',) + tuple(clause[1:])]
@fields.depends('laboratory')
def on_change_laboratory(self):
if self.laboratory and self.laboratory.carrier:
self.carrier = self.laboratory.carrier
2020-07-01 16:00:49 +02:00
@classmethod
@ModelView.button
def send(cls, referrals):
pool = Pool()
NotebookLine = pool.get('lims.notebook.line')
EntryDetailAnalysis = pool.get('lims.entry.detail.analysis')
2021-07-22 01:08:31 +02:00
Date = pool.get('ir.date')
2020-07-01 16:00:49 +02:00
for referral in referrals:
details = [s for s in referral.services]
lines = NotebookLine.search([
('analysis_detail', 'in', details),
('end_date', '=', None),
2020-07-01 16:00:49 +02:00
])
NotebookLine.write(lines, {'start_date': referral.date})
EntryDetailAnalysis.write(details, {'state': 'referred'})
2021-07-22 01:08:31 +02:00
cls.write(referrals, {'state': 'sent', 'sent_date': Date.today()})
2020-07-01 16:00:49 +02:00
@classmethod
def create(cls, vlist):
pool = Pool()
Config = pool.get('lims.configuration')
vlist = [x.copy() for x in vlist]
config = Config(1)
for values in vlist:
values['number'] = config.referral_sequence.get()
return super().create(vlist)
2020-07-01 16:00:49 +02:00
class ReferralReport(Report):
'Referral of Services Report'
__name__ = 'lims.referral.report'
class ReferServiceStart(ModelView):
'Refer Service'
__name__ = 'lims.referral.service.start'
date = fields.Date('Date', required=True)
laboratory = fields.Many2One('party.party', 'Destination Laboratory',
required=True)
services = fields.Many2Many('lims.entry.detail.analysis',
None, None, 'Services', readonly=True)
referral = fields.Many2One('lims.referral', 'Referral')
class ReferService(Wizard):
'Refer Service'
__name__ = 'lims.referral.service'
start = StateView('lims.referral.service.start',
'lims.referral_service_start_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Confirm', 'confirm', 'tryton-ok', default=True),
])
confirm = StateTransition()
open_ = StateAction('lims.act_referral_list')
def default_start(self, fields):
Date = Pool().get('ir.date')
default = {}
default['date'] = Date.today()
default['laboratory'] = None
default['services'] = self._get_services()
return default
def _get_services(self):
EntryDetailAnalysis = Pool().get('lims.entry.detail.analysis')
details = EntryDetailAnalysis.search([
('id', 'in', Transaction().context['active_ids']),
('referable', '=', True),
('referral', '=', None),
('state', '=', 'unplanned'),
])
return [d.id for d in details]
def transition_confirm(self):
EntryDetailAnalysis = Pool().get('lims.entry.detail.analysis')
if not self.start.services:
return 'end'
referral = self._get_referral()
EntryDetailAnalysis.write([s for s in self.start.services], {
'referral': referral.id,
})
self.start.referral = referral
return 'open_'
def _get_referral(self):
Referral = Pool().get('lims.referral')
referrals = Referral.search([
('laboratory', '=', self.start.laboratory.id),
('date', '=', self.start.date),
('state', '=', 'draft'),
], limit=1)
if referrals:
return referrals[0]
referrals = Referral.create([{
'laboratory': self.start.laboratory.id,
'carrier': (self.start.laboratory.carrier and
self.start.laboratory.carrier.id or None),
2020-07-01 16:00:49 +02:00
'date': self.start.date,
'state': 'draft',
}])
return referrals[0]
def do_open_(self, action):
action['pyson_domain'] = PYSONEncoder().encode([
('id', '=', self.start.referral.id),
])
return action, {}
def transition_open_(self):
return 'end'