# -*- 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 operator import re from collections import defaultdict from datetime import datetime from dateutil.relativedelta import relativedelta from sql import Literal, Join from trytond.model import ModelView, ModelSQL, fields, Index from trytond.wizard import Wizard, StateTransition, StateView, StateAction, \ StateReport, Button from trytond.pool import Pool from trytond.pyson import PYSONEncoder, Eval, Bool, Not, Or from trytond.transaction import Transaction from trytond.report import Report from trytond.exceptions import UserError from trytond.i18n import gettext from .configuration import get_print_date from .formula_parser import FormulaParser class Notebook(ModelSQL, ModelView): 'Laboratory Notebook' __name__ = 'lims.notebook' fraction = fields.Many2One('lims.fraction', 'Fraction', required=True, readonly=True, ondelete='CASCADE') lines = fields.One2Many('lims.notebook.line', 'notebook', 'Lines') product_type = fields.Function(fields.Many2One('lims.product.type', 'Product type'), 'get_sample_field', searcher='search_sample_field') matrix = fields.Function(fields.Many2One('lims.matrix', 'Matrix'), 'get_sample_field', searcher='search_sample_field') party = fields.Function(fields.Many2One('party.party', 'Party'), 'get_sample_field', searcher='search_sample_field') party_code = fields.Function(fields.Char('Party'), 'get_party_code', searcher='search_party_code') invoice_party = fields.Function(fields.Many2One('party.party', 'Invoice 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') date2 = fields.Function(fields.Date('Date'), 'get_sample_field', searcher='search_sample_field') sample_comments = fields.Function(fields.Text('Sample Comments'), 'get_sample_field') fraction_type = fields.Function(fields.Many2One('lims.fraction.type', 'Fraction type'), 'get_fraction_field', searcher='search_fraction_field') fraction_comments = fields.Function(fields.Text('Fraction Comments'), 'get_fraction_field') shared = fields.Function(fields.Boolean('Shared'), 'get_fraction_field', searcher='search_fraction_field') current_location = fields.Function(fields.Many2One('stock.location', 'Current Location'), 'get_current_location', searcher='search_current_location') divided_report = fields.Function(fields.Boolean('Divided report'), 'get_divided_report') icon = fields.Function(fields.Char("Icon"), 'get_icon') obj_description = fields.Function(fields.Char('Objective description', translate=True), 'get_obj_description') completion_percentage = fields.Function(fields.Numeric('Complete', digits=(1, 4)), 'get_sample_field') department = fields.Function(fields.Many2One('company.department', 'Department'), 'get_department', searcher='search_department') state = fields.Function(fields.Char('State'), 'get_state', searcher='search_state') lines_pending_reporting = fields.Function(fields.One2Many( 'lims.notebook.line', None, 'Lines'), 'get_lines_pending_reporting') acceptance_pending = fields.Function(fields.Boolean('Pending acceptance'), 'get_acceptance_pending', searcher='search_acceptance_pending') urgent = fields.Function(fields.Boolean('Urgent'), 'get_urgent', searcher='search_urgent') entry_summary = fields.Function(fields.Char('Entry / Qty. Samples'), 'get_entry_summary', searcher='search_entry_summary') @classmethod def __setup__(cls): super().__setup__() cls._order.insert(0, ('fraction', 'DESC')) def get_rec_name(self, name): if self.fraction: return self.fraction.rec_name @classmethod def search_rec_name(cls, name, clause): return [('fraction',) + tuple(clause[1:])] @classmethod def get_sample_field(cls, notebooks, names): result = {} for name in names: result[name] = {} if cls._fields[name]._type == 'many2one': for n in notebooks: field = getattr(n.fraction.sample, name, None) result[name][n.id] = field.id if field else None elif name == 'sample_comments': for n in notebooks: result[name][n.id] = getattr( n.fraction.sample, 'comments', None) else: for n in notebooks: result[name][n.id] = getattr(n.fraction.sample, name, None) return result @classmethod def search_sample_field(cls, name, clause): return [('fraction.sample.' + name,) + tuple(clause[1:])] def _order_sample_field(name): def order_field(tables): pool = Pool() Sample = pool.get('lims.sample') Fraction = pool.get('lims.fraction') field = Sample._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) order_party = _order_sample_field('party') order_product_type = _order_sample_field('product_type') order_matrix = _order_sample_field('matrix') order_label = _order_sample_field('label') order_date = _order_sample_field('date') @classmethod def get_obj_description(cls, notebooks, name): result = {} for n in notebooks: field = getattr(n.fraction.sample, 'obj_description', None) if field: result[n.id] = field.description else: result[n.id] = getattr(n.fraction.sample, 'obj_description_manual', None) return result @classmethod def get_party_code(cls, notebooks, name): result = {} for n in notebooks: result[n.id] = n.party.code return result @classmethod def search_party_code(cls, name, clause): return [('fraction.sample.party.code',) + tuple(clause[1:])] @classmethod def get_fraction_field(cls, notebooks, names): result = {} for name in names: result[name] = {} if name == 'fraction_type': for n in notebooks: field = getattr(n.fraction, 'type', None) result[name][n.id] = field.id if field else None elif name == 'fraction_comments': for n in notebooks: result[name][n.id] = getattr(n.fraction, 'comments', None) else: for n in notebooks: result[name][n.id] = getattr(n.fraction, name, None) return result @classmethod def search_fraction_field(cls, name, clause): if name == 'fraction_type': name = 'type' return [('fraction.' + name,) + tuple(clause[1:])] def _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) order_fraction_type = _order_fraction_field('type') order_shared = _order_fraction_field('shared') def get_divided_report(self, name): if not self.fraction or not self.fraction.services: return False for s in self.fraction.services: if s.divide: return True return False @classmethod def get_current_location(cls, notebooks, name=None): cursor = Transaction().connection.cursor() pool = Pool() Move = pool.get('stock.move') result = {} for n in notebooks: cursor.execute('SELECT to_location ' 'FROM "' + Move._table + '" ' 'WHERE fraction = %s ' 'AND state IN (\'assigned\', \'done\') ' 'ORDER BY effective_date DESC, id DESC ' 'LIMIT 1', (n.fraction.id,)) location = cursor.fetchone() result[n.id] = location[0] if location else None return result @classmethod def search_current_location(cls, name, domain=None): def _search_current_location_eval_domain(line, domain): operator_funcs = { '=': operator.eq, '>=': operator.ge, '>': operator.gt, '<=': operator.le, '<': operator.lt, '!=': operator.ne, 'in': lambda v, l: v in l, 'not in': lambda v, l: v not in l, 'ilike': lambda v, l: False, } field, op, operand = domain value = line.get(field) return operator_funcs[op](value, operand) if domain and domain[1] == 'ilike': Location = Pool().get('stock.location') locations = Location.search([ ('code', '=', domain[2]), ], order=[]) if not locations: locations = Location.search([ ('name',) + tuple(domain[1:]), ], order=[]) if not locations: return [] domain = ('current_location', 'in', [l.id for l in locations]) all_notebooks = cls.search([]) current_locations = iter( cls.get_current_location(all_notebooks).items()) processed_lines = [{ 'fraction': fraction, 'current_location': location, } for fraction, location in current_locations] record_ids = [line['fraction'] for line in processed_lines if _search_current_location_eval_domain(line, domain)] return [('id', 'in', record_ids)] def get_icon(self, name): if self.fraction_comments: return 'lims-blue' return 'lims-white' @classmethod def get_department(cls, notebooks, name): result = {} for n in notebooks: field = getattr(n.fraction.sample.product_type, name, None) result[n.id] = field.id if field else None return result @classmethod def search_department(cls, name, clause): return [('fraction.sample.product_type.' + name,) + tuple(clause[1:])] @classmethod def get_state(cls, notebooks, name=None): result = {} laboratory_id = Transaction().context.get( 'samples_pending_reporting_laboratory', None) if not laboratory_id: for n in notebooks: result[n.id] = None return result for n in notebooks: result[n.id] = cls._get_notebook_state(n.id, laboratory_id) return result @classmethod def _get_notebook_state(cls, notebook_id, laboratory_id): cursor = Transaction().connection.cursor() pool = Pool() ResultsLine = pool.get('lims.results_report.version.detail.line') NotebookLine = pool.get('lims.notebook.line') Notebook = pool.get('lims.notebook') Fraction = pool.get('lims.fraction') FractionType = pool.get('lims.fraction.type') EntryDetailAnalysis = pool.get('lims.entry.detail.analysis') ResultModifier = pool.get('lims.result_modifier') draft_lines_ids = ResultsLine.get_draft_lines_ids( laboratory_id, notebook_id) draft_lines_ids = ', '.join(str(l) for l in [0] + draft_lines_ids) sql_query = ('SELECT COUNT(*) ' 'FROM "' + NotebookLine._table + '" nl ' '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 ' 'LEFT JOIN "' + ResultModifier._table + '" rm ' 'ON rm.id = nl.result_modifier ' 'WHERE nl.notebook = %s ' 'AND nl.laboratory = %s ' 'AND ft.report = TRUE ' 'AND nl.report = TRUE ' 'AND nl.annulled = FALSE ' 'AND nl.results_report IS NULL ' 'AND nl.accepted = TRUE ' 'AND nl.id NOT IN (' + draft_lines_ids + ') ') excluded_notebooks = cls._get_excluded_notebooks([notebook_id], laboratory_id) if excluded_notebooks: for n_id, grouper in excluded_notebooks: cursor.execute('SELECT nl.id ' 'FROM "' + NotebookLine._table + '" nl ' 'INNER JOIN "' + EntryDetailAnalysis._table + '" d ' 'ON d.id = nl.analysis_detail ' 'WHERE nl.notebook = %s AND d.report_grouper = %s', (n_id, grouper)) excluded_lines = [x[0] for x in cursor.fetchall()] excluded_lines_ids = ', '.join(str(l) for l in [0] + excluded_lines) sql_query += 'AND nl.id NOT IN (' + excluded_lines_ids + ') ' cursor.execute(sql_query, (notebook_id, laboratory_id)) if cursor.fetchone()[0] > 0: return 'complete' sql_query = ('SELECT COUNT(*) ' 'FROM "' + NotebookLine._table + '" nl ' '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 ' 'LEFT JOIN "' + ResultModifier._table + '" rm ' 'ON rm.id = nl.result_modifier ' 'WHERE nl.notebook = %s ' 'AND nl.laboratory = %s ' 'AND ft.report = TRUE ' 'AND nl.report = TRUE ' 'AND nl.annulled = FALSE ' 'AND nl.results_report IS NULL ' 'AND nl.id NOT IN (' + draft_lines_ids + ') ') sql_query += cls._get_samples_in_progress_sql_clause() cursor.execute(sql_query, (notebook_id, laboratory_id)) if cursor.fetchone()[0] > 0: return 'in_progress' return None @classmethod def search_state(cls, name, domain=None): if domain[2] == 'complete': notebooks_ids = cls._get_notebooks_complete() return [('id', 'in', notebooks_ids)] if domain[2] == 'in_progress': notebooks_ids = cls._get_notebooks_in_progress() return [('id', 'in', notebooks_ids)] return [('id', '=', -1)] @classmethod def _get_notebooks_complete(cls): cursor = Transaction().connection.cursor() pool = Pool() ResultsLine = pool.get('lims.results_report.version.detail.line') NotebookLine = pool.get('lims.notebook.line') Notebook = pool.get('lims.notebook') Fraction = pool.get('lims.fraction') FractionType = pool.get('lims.fraction.type') EntryDetailAnalysis = pool.get('lims.entry.detail.analysis') laboratory_id = Transaction().context.get( 'samples_pending_reporting_laboratory', None) if not laboratory_id: return [] draft_lines_ids = ResultsLine.get_draft_lines_ids(laboratory_id) draft_lines_ids = ', '.join(str(l) for l in [0] + draft_lines_ids) sql_query = ('SELECT nl.notebook ' 'FROM "' + NotebookLine._table + '" nl ' '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 nl.laboratory = %s ' 'AND ft.report = TRUE ' 'AND nl.report = TRUE ' 'AND nl.annulled = FALSE ' 'AND nl.results_report IS NULL ' 'AND nl.accepted = TRUE ' 'AND nl.id NOT IN (' + draft_lines_ids + ') ') cursor.execute(sql_query, (laboratory_id,)) notebooks_ids = [x[0] for x in cursor.fetchall()] excluded_notebooks = cls._get_excluded_notebooks(notebooks_ids, laboratory_id) if excluded_notebooks: for n_id, grouper in excluded_notebooks: cursor.execute('SELECT nl.id ' 'FROM "' + NotebookLine._table + '" nl ' 'INNER JOIN "' + EntryDetailAnalysis._table + '" d ' 'ON d.id = nl.analysis_detail ' 'WHERE nl.notebook = %s AND d.report_grouper = %s', (n_id, grouper)) excluded_lines = [x[0] for x in cursor.fetchall()] excluded_lines_ids = ', '.join(str(l) for l in [0] + excluded_lines) sql_query += 'AND nl.id NOT IN (' + excluded_lines_ids + ') ' cursor.execute(sql_query, (laboratory_id,)) notebooks_ids = [x[0] for x in cursor.fetchall()] return notebooks_ids @classmethod def _get_excluded_notebooks(cls, notebooks_ids, laboratory_id): cursor = Transaction().connection.cursor() pool = Pool() NotebookLine = pool.get('lims.notebook.line') EntryDetailAnalysis = pool.get('lims.entry.detail.analysis') Notebook = pool.get('lims.notebook') Fraction = pool.get('lims.fraction') Sample = pool.get('lims.sample') Entry = pool.get('lims.entry') FractionType = pool.get('lims.fraction.type') notebooks_ids = '\', \''.join(str(n) for n in notebooks_ids + [0]) cursor.execute('SELECT nl.notebook, nl.analysis, nl.method, ' 'd.report_grouper, nl.accepted ' '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 "' + Sample._table + '" s ' 'ON s.id = f.sample ' 'INNER JOIN "' + Entry._table + '" e ' 'ON e.id = s.entry ' 'INNER JOIN "' + FractionType._table + '" ft ' 'ON ft.id = f.type ' 'WHERE nl.laboratory = %s ' 'AND ft.report = TRUE ' 'AND nl.report = TRUE ' 'AND nl.annulled = FALSE ' 'AND n.id IN (\'' + notebooks_ids + '\')', (laboratory_id,)) notebook_lines = cursor.fetchall() # Check repetitions oks, to_check = [], [] accepted_notebooks = [] for line in notebook_lines: key = (line[0], line[1], line[2], line[3]) if not line[4]: to_check.append(key) else: oks.append(key) accepted_notebooks.append(line[0]) to_check = list(set(to_check) - set(oks)) accepted_notebooks = list(set(accepted_notebooks)) excluded_notebooks = set() for n_id, a_id, m_id, grouper in to_check: if n_id not in accepted_notebooks: continue key = (n_id, grouper) excluded_notebooks.add(key) return excluded_notebooks @classmethod def _get_notebooks_in_progress(cls): cursor = Transaction().connection.cursor() pool = Pool() ResultsLine = pool.get('lims.results_report.version.detail.line') NotebookLine = pool.get('lims.notebook.line') Notebook = pool.get('lims.notebook') Fraction = pool.get('lims.fraction') FractionType = pool.get('lims.fraction.type') ResultModifier = pool.get('lims.result_modifier') laboratory_id = Transaction().context.get( 'samples_pending_reporting_laboratory', None) if not laboratory_id: return [] draft_lines_ids = ResultsLine.get_draft_lines_ids(laboratory_id) draft_lines_ids = ', '.join(str(l) for l in [0] + draft_lines_ids) sql_query = ('SELECT nl.notebook ' 'FROM "' + NotebookLine._table + '" nl ' '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 ' 'LEFT JOIN "' + ResultModifier._table + '" rm ' 'ON rm.id = nl.result_modifier ' 'WHERE nl.laboratory = %s ' 'AND ft.report = TRUE ' 'AND nl.report = TRUE ' 'AND nl.annulled = FALSE ' 'AND nl.results_report IS NULL ' 'AND nl.id NOT IN (' + draft_lines_ids + ') ') sql_query += cls._get_samples_in_progress_sql_clause() cursor.execute(sql_query, (laboratory_id,)) notebooks_ids = [x[0] for x in cursor.fetchall()] complete_notebooks_ids = cls._get_notebooks_complete() if complete_notebooks_ids: notebooks_ids = list( set(notebooks_ids) - set(complete_notebooks_ids)) return notebooks_ids @classmethod def _get_samples_in_progress_clause(cls): Config = Pool().get('lims.configuration') samples_in_progress = Config(1).samples_in_progress clause = [] if samples_in_progress == 'accepted': clause = [('accepted', '=', True)] elif samples_in_progress == 'result': clause = [['OR', ('result', 'not in', [None, '']), ('literal_result', 'not in', [None, '']), ('result_modifier.code', 'in', [ 'd', 'nd', 'pos', 'neg', 'ni', 'abs', 'pre', 'na']), ]] return clause @classmethod def _get_samples_in_progress_sql_clause(cls): Config = Pool().get('lims.configuration') samples_in_progress = Config(1).samples_in_progress sql_clause = '' if samples_in_progress == 'accepted': sql_clause = 'AND nl.accepted = TRUE ' elif samples_in_progress == 'result': sql_clause = ('AND ((nl.result IS NOT NULL ' 'AND nl.result != \'\') ' 'OR (nl.literal_result IS NOT NULL ' 'AND nl.literal_result != \'\') ' 'OR rm.code IN ' '(\'d\', \'nd\', \'pos\', \'neg\', ' '\'ni\', \'abs\', \'pre\', \'na\')) ') return sql_clause @classmethod def view_toolbar_get(cls): if not Transaction().context.get('samples_pending_reporting', False): return super().view_toolbar_get() # Samples Pending Reporting uses specific keywords prints = cls.get_samples_pending_reporting_keyword('form_print') actions = cls.get_samples_pending_reporting_keyword('form_action') relates = cls.get_samples_pending_reporting_keyword('form_relate') result = { 'print': prints, 'action': actions, 'relate': relates, 'exports': [], 'emails': [], } return result @classmethod def get_samples_pending_reporting_keyword(cls, keyword): """ Method copied from ActionKeyword. It search for specific keywords for Samples Pending Reporting: lims.notebook,-2 """ pool = Pool() ActionKeyword = pool.get('ir.action.keyword') Action = pool.get('ir.action') key = (keyword, ('lims.notebook', -2)) keywords = ActionKeyword._get_keyword_cache.get(key) if keywords is not None: return keywords clause = [ ('keyword', '=', keyword), ('model', '=', 'lims.notebook,-2'), ('action.active', '=', True), ] action_keywords = ActionKeyword.search(clause, order=[]) types = defaultdict(list) for action_keyword in action_keywords: type_ = action_keyword.action.type types[type_].append(action_keyword.action.id) keywords = [] for type_, action_ids in types.items(): for value in Action.get_action_values(type_, action_ids): value['keyword'] = keyword keywords.append(value) keywords.sort(key=operator.itemgetter('name')) ActionKeyword._get_keyword_cache.set(key, keywords) return keywords def get_lines_pending_reporting(self, name=None): laboratory_id = Transaction().context.get( 'samples_pending_reporting_laboratory', None) if not laboratory_id: return [] lines = self._get_lines_for_reporting(laboratory_id, 'complete') return [l.id for l in lines] def _get_lines_for_reporting(self, laboratory_id, state): cursor = Transaction().connection.cursor() pool = Pool() ResultsLine = pool.get('lims.results_report.version.detail.line') NotebookLine = pool.get('lims.notebook.line') EntryDetailAnalysis = pool.get('lims.entry.detail.analysis') draft_lines_ids = ResultsLine.get_draft_lines_ids( laboratory_id, self.id) clause = [ ('notebook', '=', self.id), ('laboratory', '=', laboratory_id), ('notebook.fraction.type.report', '=', True), ('report', '=', True), ('annulled', '=', False), ('results_report', '=', None), ('id', 'not in', draft_lines_ids), ] if state == 'in_progress': clause.extend(self._get_samples_in_progress_clause()) else: clause.append(('accepted', '=', True)) excluded_notebooks = self._get_excluded_notebooks( [self.id], laboratory_id) if excluded_notebooks: for n_id, grouper in excluded_notebooks: cursor.execute('SELECT nl.id ' 'FROM "' + NotebookLine._table + '" nl ' 'INNER JOIN "' + EntryDetailAnalysis._table + '" d ' 'ON d.id = nl.analysis_detail ' 'WHERE nl.notebook = %s AND d.report_grouper = %s', (n_id, grouper)) excluded_notebook_lines = [x[0] for x in cursor.fetchall()] clause.append(('id', 'not in', excluded_notebook_lines)) return NotebookLine.search(clause) @classmethod def get_acceptance_pending(cls, notebooks, name=None): cursor = Transaction().connection.cursor() pool = Pool() NotebookLine = pool.get('lims.notebook.line') Fraction = pool.get('lims.fraction') FractionType = pool.get('lims.fraction.type') ResultModifier = pool.get('lims.result_modifier') result = {} for n in notebooks: result[n.id] = False cursor.execute('SELECT COUNT(*) ' 'FROM "' + NotebookLine._table + '" nl ' 'INNER JOIN "' + cls._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 ' 'LEFT JOIN "' + ResultModifier._table + '" rm ' 'ON rm.id = nl.result_modifier ' 'WHERE ft.report = TRUE ' 'AND nl.report = TRUE ' 'AND nl.annulled = FALSE ' 'AND nl.accepted = FALSE ' 'AND (nl.result IS NOT NULL ' 'OR nl.literal_result IS NOT NULL ' 'OR rm.code IN ' '(\'d\', \'nd\', \'pos\', \'neg\', ' '\'ni\', \'abs\', \'pre\', \'na\')) ' 'AND nl.notebook = %s', (n.id,)) if cursor.fetchone()[0] > 0: result[n.id] = True return result @classmethod def search_acceptance_pending(cls, name, domain=None): cursor = Transaction().connection.cursor() pool = Pool() NotebookLine = pool.get('lims.notebook.line') Fraction = pool.get('lims.fraction') FractionType = pool.get('lims.fraction.type') ResultModifier = pool.get('lims.result_modifier') cursor.execute('SELECT nl.notebook ' 'FROM "' + NotebookLine._table + '" nl ' 'INNER JOIN "' + cls._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 ' 'LEFT JOIN "' + ResultModifier._table + '" rm ' 'ON rm.id = nl.result_modifier ' 'WHERE ft.report = TRUE ' 'AND nl.report = TRUE ' 'AND nl.annulled = FALSE ' 'AND nl.accepted = FALSE ' 'AND (nl.result IS NOT NULL ' 'OR nl.literal_result IS NOT NULL ' 'OR rm.code IN ' '(\'d\', \'nd\', \'pos\', \'neg\', ' '\'ni\', \'abs\', \'pre\', \'na\'))') notebooks_ids = [x[0] for x in cursor.fetchall()] field, op, operand = domain if (op, operand) in (('=', True), ('!=', False)): return [('id', 'in', notebooks_ids)] elif (op, operand) in (('=', False), ('!=', True)): return [('id', 'not in', notebooks_ids)] return [] @classmethod def get_urgent(cls, notebooks, name): pool = Pool() NotebookLine = pool.get('lims.notebook.line') result = {} for n in notebooks: lines = NotebookLine.search_count([ ('notebook', '=', n.id), ('urgent', '=', True), ]) result[n.id] = True if lines > 0 else False return result @classmethod def search_urgent(cls, name, clause): field, op, operand = clause if (op, operand) in (('=', True), ('!=', False)): return [('lines.urgent', '=', True)] elif (op, operand) in (('=', False), ('!=', True)): urgents = cls.search([('lines.urgent', '=', True)]) return [('id', 'not in', [u.id for u in urgents])] return [] @classmethod def get_entry_summary(cls, notebooks, name): cursor = Transaction().connection.cursor() pool = Pool() Entry = pool.get('lims.entry') Sample = pool.get('lims.sample') Fraction = pool.get('lims.fraction') result = {} for n in notebooks: result[n.id] = '' cursor.execute('SELECT s.entry ' 'FROM "' + Sample._table + '" s ' 'INNER JOIN "' + Fraction._table + '" f ' 'ON s.id = f.sample ' 'WHERE f.id = %s', (str(n.fraction.id),)) entry_id = cursor.fetchone()[0] cursor.execute('SELECT e.number, count(s.id) ' 'FROM "' + Entry._table + '" e ' 'INNER JOIN "' + Sample._table + '" s ' 'ON e.id = s.entry ' 'WHERE e.id = %s ' 'GROUP BY e.number', (str(entry_id),)) res = cursor.fetchone() if not res: continue result[n.id] = '%s/%s' % (res[0], res[1]) return result @classmethod def search_entry_summary(cls, name, clause): return [('fraction.sample.entry.number',) + tuple(clause[1:])] class NotebookLine(ModelSQL, ModelView): 'Laboratory Notebook Line' __name__ = 'lims.notebook.line' _states = {'readonly': Bool(Eval('accepted'))} notebook = fields.Many2One('lims.notebook', 'Laboratory notebook', ondelete='CASCADE', required=True) analysis_detail = fields.Many2One('lims.entry.detail.analysis', 'Analysis detail', ondelete='CASCADE') service = fields.Many2One('lims.service', 'Service', readonly=True, ondelete='CASCADE') analysis = fields.Many2One('lims.analysis', 'Analysis', required=True, readonly=True) repetition = fields.Integer('Repetition', readonly=True) start_date = fields.Date('Start date', states={'readonly': True}) end_date = fields.Date('End date', states={ 'readonly': Or(~Bool(Eval('start_date')), Bool(Eval('accepted'))), }) laboratory = fields.Many2One('lims.laboratory', 'Laboratory', readonly=True) method = fields.Many2One('lims.lab.method', 'Method', required=True, states=_states, domain=['OR', ('id', '=', Eval('method', -1)), ('id', 'in', Eval('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') method_version = fields.Many2One('lims.lab.method.version', 'Method version', readonly=True) device = fields.Many2One('lims.lab.device', 'Device', states=_states, domain=['OR', ('id', '=', Eval('device', -1)), ('id', 'in', Eval('device_domain'))]) device_view = fields.Function(fields.Many2One('lims.lab.device', 'Device'), 'get_views_field') device_domain = fields.Function(fields.Many2Many('lims.lab.device', None, None, 'Device domain'), 'on_change_with_device_domain') analysis_origin = fields.Char('Analysis origin', readonly=True) initial_concentration = fields.Char('Initial concentration', translate=True, states=_states) final_concentration = fields.Char('Final concentration', translate=True, states=_states) literal_final_concentration = fields.Char('Literal Final concentration', translate=True, states=_states) laboratory_professionals = fields.Many2Many( 'lims.notebook.line-laboratory.professional', 'notebook_line', 'professional', 'Preparation professionals', states=_states) initial_unit = fields.Many2One('product.uom', 'Initial unit', domain=[('category.lims_only_available', '=', True)], states=_states) final_unit = fields.Many2One('product.uom', 'Final unit', domain=[('category.lims_only_available', '=', True)], states=_states) result_modifier = fields.Many2One('lims.result_modifier', 'Result modifier', states=_states) converted_result_modifier = fields.Many2One('lims.result_modifier', 'Converted result modifier', states=_states) result = fields.Char('Result', states=_states) converted_result = fields.Char('Converted result', states=_states) formated_result = fields.Function(fields.Char('Result to report'), 'get_formated_result') formated_converted_result = fields.Function(fields.Char( 'Converted result to report'), 'get_formated_converted_result') detection_limit = fields.Char('Detection limit', states=_states) quantification_limit = fields.Char('Quantification limit', states=_states) lower_limit = fields.Char('Lower limit allowed', states=_states) upper_limit = fields.Char('Upper limit allowed', states=_states) check_result_limits = fields.Function(fields.Boolean( 'Validate limits directly on the result'), 'get_typification_field') chromatogram = fields.Char('Chromatogram', states=_states) professionals = fields.One2Many('lims.notebook.line.professional', 'notebook_line', 'Analytic professionals', states=_states) comments = fields.Text('Entry comments', states=_states) theoretical_concentration = fields.Char('Theoretical concentration', states=_states) concentration_level = fields.Many2One('lims.concentration.level', 'Concentration level', states=_states) decimals = fields.Integer('Decimals', states=_states) significant_digits = fields.Integer('Significant digits', states=_states) scientific_notation = fields.Boolean('Scientific notation', states=_states) result_decimals = fields.Integer('Decimals to report result', states=_states) converted_result_decimals = fields.Integer( 'Decimals to report converted result', states=_states) backup = fields.Char('Backup', states=_states) reference = fields.Char('Reference', states=_states) literal_result = fields.Char('Literal result', translate=True, states=_states) rm_correction_formula = fields.Char('RM Correction Formula', states=_states) report = fields.Boolean('Report', states=_states) uncertainty = fields.Char('Uncertainty', states=_states) verification = fields.Char('Verification', states=_states) analysis_order = fields.Function(fields.Integer('Order'), 'get_analysis_order') dilution_factor = fields.Float('Dilution factor', states=_states) accepted = fields.Boolean('Accepted') acceptance_date = fields.DateTime('Acceptance date', states={'readonly': True}) not_accepted_message = fields.Text('Message', readonly=True, states={'invisible': Not(Bool(Eval('not_accepted_message')))}) annulled = fields.Boolean('Annulled', states={'readonly': True}) annulment_date = fields.DateTime('Annulment date', states={'readonly': True}) annulment_reason = fields.Text('Annulment reason', states={'readonly': True, 'invisible': ~Eval('annulled')}) results_report = fields.Many2One('lims.results_report', 'Results Report', readonly=True) planification = fields.Many2One('lims.planification', 'Planification', readonly=True) urgent = fields.Boolean('Urgent', states=_states) priority = fields.Function(fields.Integer('Priority'), 'get_service_field', searcher='search_service_field') fraction = fields.Function(fields.Many2One('lims.fraction', 'Fraction'), 'get_service_field', searcher='search_service_field') fraction_type = fields.Function(fields.Many2One('lims.fraction.type', 'Fraction type'), 'get_fraction_field', searcher='search_fraction_field') sample = fields.Function(fields.Many2One('lims.sample', 'Sample'), 'get_fraction_field', searcher='search_fraction_field') party = fields.Function(fields.Many2One('party.party', 'Party'), 'get_fraction_field', searcher='search_fraction_field') product_type = fields.Function(fields.Many2One('lims.product.type', 'Product type'), 'get_sample_field', searcher='search_sample_field') matrix = fields.Function(fields.Many2One('lims.matrix', 'Matrix'), 'get_sample_field', searcher='search_sample_field') label = fields.Function(fields.Char('Label'), 'get_sample_field', searcher='search_sample_field') date = fields.Function(fields.DateTime('Date'), 'get_sample_field', searcher='search_sample_field') date2 = fields.Function(fields.Date('Date'), 'get_sample_field', searcher='search_sample_field') report_type = fields.Function(fields.Char('Report type'), 'get_typification_field', searcher='search_typification_field') report_result_type = fields.Function(fields.Char('Result type'), 'get_typification_field', searcher='search_typification_field') results_estimated_waiting = fields.Integer( 'Estimated number of days for results', states={'readonly': True}) results_estimated_date = fields.Function(fields.Date( 'Estimated date of result'), 'get_results_estimated_date') laboratory_date = fields.Function(fields.Date('Laboratory deadline'), 'get_service_field', searcher='search_service_field') report_date = fields.Function(fields.Date('Date agreed for result'), 'get_service_field', searcher='search_service_field') department = fields.Many2One('company.department', 'Department', domain=['OR', ('id', '=', Eval('department', -1)), ('id', 'in', Eval('department_domain'))], states=_states) department_domain = fields.Function(fields.Many2Many('company.department', None, None, 'Department domain'), 'on_change_with_department_domain') icon = fields.Function(fields.Char("Icon"), 'get_icon') planning_comments = fields.Function(fields.Text('Planification comments'), 'get_planning_comments') controls = fields.Many2Many('lims.notebook.line-fraction', 'notebook_line', 'fraction', 'Controls', states=_states) referral = fields.Function(fields.Many2One('lims.referral', 'Referral'), 'get_detail_field', searcher='search_detail_field') repetition_reason = fields.Char('Repetition reason', states={'readonly': True, 'invisible': Eval('repetition', 0) == 0}) exceptional_load = fields.Boolean('Exceptionally loaded result', readonly=True) exceptional_load_uid = fields.Many2One('res.user', 'Exceptional loading of results User', readonly=True) rule = fields.Many2One('lims.rule', 'Notebook Rule', readonly=True, ondelete='SET NULL') trace_report = fields.Boolean('Trace report') sample_client_description = fields.Function(fields.Char( 'Product described by the client', translate=True), 'get_sample_field') del _states @classmethod def __register__(cls, module_name): cursor = Transaction().connection.cursor() Service = Pool().get('lims.service') table_h = cls.__table_handler__(module_name) urgent_exist = table_h.column_exist('urgent') migrate_result_modifier = ( table_h.column_exist('result_modifier') and table_h.column_is_type('result_modifier', 'VARCHAR')) if migrate_result_modifier: table_h.column_rename('result_modifier', 'temp_result_modifier') table_h.column_rename('converted_result_modifier', 'temp_converted_result_modifier') super().__register__(module_name) if not urgent_exist: cursor.execute('UPDATE "' + cls._table + '" nl ' 'SET urgent = srv.urgent FROM ' '"' + Service._table + '" srv ' 'WHERE srv.id = nl.service') #if migrate_result_modifier: #cls._migrate_result_modifier() #table_h.drop_column('temp_result_modifier') #table_h.drop_column('temp_converted_result_modifier') @classmethod def _migrate_result_modifier(cls): cursor = Transaction().connection.cursor() ResultModifier = Pool().get('lims.result_modifier') cursor.execute('UPDATE "' + cls._table + '" ' 'SET result_modifier = NULL ' 'WHERE temp_result_modifier = \'eq\'') cursor.execute('UPDATE "' + cls._table + '" nl ' 'SET result_modifier = rm.id FROM ' '"' + ResultModifier._table + '" rm ' 'WHERE rm.code = nl.temp_result_modifier') cursor.execute('UPDATE "' + cls._table + '" ' 'SET converted_result_modifier = NULL ' 'WHERE temp_converted_result_modifier = \'eq\'') cursor.execute('UPDATE "' + cls._table + '" nl ' 'SET converted_result_modifier = rm.id FROM ' '"' + ResultModifier._table + '" rm ' 'WHERE rm.code = nl.temp_converted_result_modifier') @classmethod def __setup__(cls): super().__setup__() cls._order.insert(0, ('analysis_order', 'ASC')) cls._order.insert(1, ('repetition', 'ASC')) t = cls.__table__() #cls._sql_indexes.update({ #Index(t, (t.analysis, Index.Equality())), #Index(t, (t.start_date, Index.Range())), #Index(t, (t.end_date, Index.Range())), #Index(t, (t.laboratory, Index.Equality())), #Index(t, (t.result_modifier, Index.Equality())), #Index(t, (t.converted_result_modifier, Index.Equality())), #Index(t, (t.concentration_level, Index.Equality())), #Index(t, (t.report, Index.Equality())), #Index(t, (t.accepted, Index.Equality())), #Index(t, (t.annulled, Index.Equality())), #Index(t, (t.results_report, Index.Equality())), #}) @staticmethod def default_repetition(): return 0 @staticmethod def default_decimals(): return 2 @staticmethod def default_report(): return True @staticmethod def default_dilution_factor(): return 1.0 @staticmethod def default_accepted(): return False @staticmethod def default_annulled(): return False @classmethod def create(cls, vlist): pool = Pool() LabMethod = pool.get('lims.lab.method') Sample = pool.get('lims.sample') vlist = [x.copy() for x in vlist] for values in vlist: # set method version if 'method' in values and values['method'] is not None: values['method_version'] = LabMethod( values['method']).get_current_version() lines = super().create(vlist) cls.update_detail_report(lines) to_update = Sample.browse(list(set(nl.sample.id for nl in lines))) Sample.__queue__.update_samples_state(to_update) return lines @classmethod def write(cls, *args): pool = Pool() LabMethod = pool.get('lims.lab.method') Sample = pool.get('lims.sample') actions = iter(args) args = [] for lines, values in zip(actions, actions): # set method version if 'method' in values and values['method'] is not None: values['method_version'] = LabMethod( values['method']).get_current_version() args.extend((lines, values)) super().write(*args) actions = iter(args) for lines, vals in zip(actions, actions): if vals.get('not_accepted_message'): cls.write(lines, {'not_accepted_message': None}) if 'accepted' in vals: cls.update_detail_analysis(lines, vals['accepted']) if 'report' in vals: cls.update_detail_report(lines) update_samples_state = False for field in ('start_date', 'end_date', 'acceptance_date', 'report', 'annulled'): if field in vals: update_samples_state = True break if update_samples_state: to_update = Sample.browse(list(set(nl.sample.id for nl in lines))) Sample.__queue__.update_samples_state(to_update) update_referrals_state = False for field in ('accepted', 'annulled', 'result', 'literal_result', 'result_modifier'): if field in vals: update_referrals_state = True break if update_referrals_state: cls.update_referrals_state(lines) @staticmethod def update_detail_analysis(lines, accepted): EntryDetailAnalysis = Pool().get('lims.entry.detail.analysis') details = [nl.analysis_detail.id for nl in lines] if accepted: analysis_details = EntryDetailAnalysis.search([ ('id', 'in', details), ]) if analysis_details: EntryDetailAnalysis.write(analysis_details, { 'state': 'done', }) else: analysis_details = EntryDetailAnalysis.search([ ('id', 'in', details), ('analysis.behavior', '!=', 'internal_relation'), ]) if analysis_details: EntryDetailAnalysis.write(analysis_details, { 'state': 'planned', }) analysis_details = EntryDetailAnalysis.search([ ('id', 'in', details), ('analysis.behavior', '=', 'internal_relation'), ]) if analysis_details: EntryDetailAnalysis.write(analysis_details, { 'state': 'unplanned', }) @staticmethod def update_detail_report(lines): cursor = Transaction().connection.cursor() pool = Pool() EntryDetailAnalysis = pool.get('lims.entry.detail.analysis') NotebookLine = pool.get('lims.notebook.line') to_save = [] details_ids = list(set(nl.analysis_detail.id for nl in lines)) with Transaction().set_context(_check_access=False): analysis_details = EntryDetailAnalysis.browse(details_ids) for d in analysis_details: cursor.execute('SELECT report ' 'FROM "' + NotebookLine._table + '" ' 'WHERE analysis_detail = %s ' 'ORDER BY id DESC LIMIT 1', (d.id,)) value = cursor.fetchone() d.report = value[0] if value else False to_save.append(d) EntryDetailAnalysis.save(to_save) @classmethod def update_referrals_state(cls, lines): Referral = Pool().get('lims.referral') referral_ids = [l.analysis_detail.referral.id for l in lines if l.analysis_detail.referral] if not referral_ids: return referrals = Referral.search([ ('state', '=', 'sent'), ('id', 'in', referral_ids), ]) for referral in referrals: if cls.search_count([ ('analysis_detail.referral', '=', referral.id), ('annulled', '=', False), ('result', 'in', [None, '']), ('literal_result', 'in', [None, '']), ['OR', ('result_modifier', '=', None), ('result_modifier.code', 'not in', ['d', 'nd', 'pos', 'neg', 'ni', 'abs', 'pre', 'na'])], ]) == 0: Referral.write([referral], {'state': 'done'}) @classmethod def validate(cls, notebook_lines): super().validate(notebook_lines) for line in notebook_lines: line.check_end_date() line.check_accepted() def check_end_date(self): if self.end_date: if not self.start_date or self.end_date < self.start_date: raise UserError(gettext('lims.msg_line_end_date', analysis=self.analysis.rec_name)) if not self.start_date or self.end_date > datetime.now().date(): raise UserError(gettext('lims.msg_end_date_wrong', analysis=self.analysis.rec_name)) def check_accepted(self): Config = Pool().get('lims.configuration') if self.accepted: clause = [ ('notebook', '=', self.notebook.id), ('analysis', '=', self.analysis.id), ('accepted', '=', True), ('id', '!=', self.id), ] if Config(1).notebook_lines_acceptance_method: clause.append(('method', '=', self.method.id)) accepted_lines = self.search(clause) if accepted_lines: raise UserError(gettext('lims.msg_accepted', analysis=self.analysis.rec_name)) @classmethod def get_analysis_order(cls, notebook_lines, name): result = {} for nl in notebook_lines: analysis = getattr(nl, 'analysis', None) result[nl.id] = analysis.order if analysis else None return result @staticmethod def order_analysis_order(tables): Analysis = Pool().get('lims.analysis') field = Analysis._fields['order'] 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('order', analysis_tables, Analysis) @classmethod def get_views_field(cls, notebook_lines, names): result = {} for name in names: field_name = name[:-5] result[name] = {} for nl in notebook_lines: field = getattr(nl, field_name, None) result[name][nl.id] = field.id if field else None return result @classmethod def get_detail_field(cls, notebook_lines, names): result = {} for name in names: result[name] = {} if cls._fields[name]._type == 'many2one': for nl in notebook_lines: field = getattr(nl.analysis_detail, name, None) result[name][nl.id] = field.id if field else None else: for nl in notebook_lines: result[name][nl.id] = getattr(nl.analysis_detail, name, None) return result @classmethod def search_detail_field(cls, name, clause): return [('analysis_detail.' + name,) + tuple(clause[1:])] @classmethod def get_service_field(cls, notebook_lines, names): result = {} for name in names: result[name] = {} if name == 'fraction': for nl in notebook_lines: field = getattr(nl.service, name, None) result[name][nl.id] = field.id if field else None else: for nl in notebook_lines: result[name][nl.id] = getattr(nl.service, name, None) return result @classmethod def search_service_field(cls, name, clause): return [('service.' + name,) + tuple(clause[1:])] @classmethod def get_fraction_field(cls, notebook_lines, names): result = {} for name in names: result[name] = {} if name == 'fraction_type': for nl in notebook_lines: fraction = getattr(nl.service, 'fraction', None) if fraction: field = getattr(fraction, 'type', None) result[name][nl.id] = field.id if field else None else: result[name][nl.id] = None else: for nl in notebook_lines: fraction = getattr(nl.service, 'fraction', None) if fraction: field = getattr(fraction, name, None) result[name][nl.id] = field.id if field else None else: result[name][nl.id] = None return result @classmethod def search_fraction_field(cls, name, clause): if name == 'fraction_type': name = 'type' return [('service.fraction.' + name,) + tuple(clause[1:])] @classmethod def get_sample_field(cls, notebook_lines, names): result = {} for name in names: result[name] = {} for nl in notebook_lines: result[name][nl.id] = None fraction = getattr(nl.service, 'fraction', None) if not fraction: continue sample = getattr(fraction, 'sample', None) if not sample: continue field = getattr(sample, name, None) if isinstance(field, ModelSQL): result[name][nl.id] = field.id if field else None elif isinstance(field, tuple): result[name][nl.id] = [f.id for f in field] else: result[name][nl.id] = field return result @classmethod def search_sample_field(cls, name, clause): return [('service.fraction.sample.' + name,) + tuple(clause[1:])] def get_rec_name(self, name): rec_name = '%s / %s' % (self.notebook.rec_name, self.analysis.rec_name) if self.repetition != 0: rec_name += ' (%s)' % self.repetition return rec_name @classmethod def search_rec_name(cls, name, clause): return [('analysis',) + tuple(clause[1:])] @classmethod def _get_custom_views(cls): # All Notebook Lines view return ['notebook_line_all_list'] @classmethod def fields_view_get(cls, view_id=None, view_type='form', level=None): pool = Pool() User = pool.get('res.user') Config = pool.get('lims.configuration') UiView = pool.get('ir.ui.view') result = super().fields_view_get(view_id, view_type, level) if view_id and UiView(view_id).name in cls._get_custom_views(): return result notebook_view = User(Transaction().user).notebook_view if not notebook_view: notebook_view = Config(1).default_notebook_view if not notebook_view: return result if view_type == 'tree': xml = '\n' \ '\n' fields = set() for column in notebook_view.columns: fields.add(column.field.name) attrs = [] if column.field.name == 'analysis': attrs.append('icon="icon"') if column.field.name in ('acceptance_date', 'annulment_date'): attrs.append('widget="date"') xml += ('\n' % (column.field.name, ' '.join(attrs))) for depend in getattr(cls, column.field.name).depends: fields.add(depend) for field in ('report_date', 'result', 'converted_result', 'result_modifier', 'converted_result_modifier', 'literal_result', 'backup', 'verification', 'uncertainty', 'accepted', 'acceptance_date', 'end_date', 'report', 'annulled', 'annulment_date', 'icon'): fields.add(field) xml += '' result['arch'] = xml result['fields'] = cls.fields_get(fields_names=list(fields)) return result @fields.depends('result', 'converted_result', 'converted_result_modifier', 'backup', 'verification', 'uncertainty', 'end_date', 'start_date', 'analysis') def on_change_result(self): self.converted_result = None self.converted_result_modifier = None self.backup = None self.verification = None self.uncertainty = None self.end_date = None if self.analysis and self.analysis.behavior == 'internal_relation': self.start_date = None @fields.depends('accepted', 'report', 'annulled', 'result', 'converted_result', 'literal_result', 'result_modifier', 'converted_result_modifier', 'end_date', 'acceptance_date') def on_change_accepted(self): self.not_accepted_message = '' if self.accepted: if not self.report: self.accepted = False self.not_accepted_message = gettext('lims.msg_not_accepted_1') elif self.annulled: self.accepted = False self.not_accepted_message = gettext('lims.msg_not_accepted_2') elif not self.end_date: self.accepted = False self.not_accepted_message = gettext('lims.msg_not_accepted_3') elif not (self.result or self.converted_result or self.literal_result or (self.result_modifier and self.result_modifier.code in ('d', 'nd', 'pos', 'neg', 'ni', 'abs', 'pre')) or (self.converted_result_modifier and self.converted_result_modifier.code in ('d', 'nd', 'pos', 'neg', 'ni', 'abs', 'pre'))): self.accepted = False self.not_accepted_message = gettext('lims.msg_not_accepted_4') else: if (self.converted_result and ( self.converted_result_modifier and self.converted_result_modifier.code not in ('ni', 'low'))): self.accepted = False self.not_accepted_message = gettext( 'lims.msg_not_accepted_5') elif (self.result and ( self.result_modifier and self.result_modifier.code not in ('ni', 'low'))): self.accepted = False self.not_accepted_message = gettext( 'lims.msg_not_accepted_6') elif (self.result_modifier and self.result_modifier.code == 'ni' and not self.literal_result and (not self.converted_result_modifier or not self.converted_result) and (self.converted_result_modifier and self.converted_result_modifier.code not in ('nd', 'pos', 'neg'))): self.accepted = False self.not_accepted_message = gettext( 'lims.msg_not_accepted_7') else: self.acceptance_date = datetime.now() else: ResultsLine = Pool().get('lims.results_report.version.detail.line') report_lines = ResultsLine.search([ ('notebook_line', '=', self.id), ('detail_sample.version_detail.state', '!=', 'annulled'), ]) if report_lines: self.accepted = True report_detail = report_lines[0].detail_sample.version_detail self.not_accepted_message = gettext('lims.msg_accepted_1', report=report_detail.report_version.results_report.number) else: self.acceptance_date = None @fields.depends('result_modifier', 'annulled', 'annulment_date', 'report') def on_change_result_modifier(self): if (self.result_modifier and self.result_modifier.code == 'na' and not self.annulled): self.annulled = True self.annulment_date = datetime.now() self.report = False elif ((not self.result_modifier or self.result_modifier.code != 'na') and self.annulled): self.annulled = False self.annulment_date = None self.report = True @classmethod def get_typification_field(cls, notebook_lines, names): pool = Pool() Typification = pool.get('lims.typification') result = dict((name, {}) for name in names) for nl in notebook_lines: typification = Typification.get_valid_typification( nl.notebook.product_type.id, nl.notebook.matrix.id, nl.analysis.id, nl.method.id) for name in names: if typification: result[name][nl.id] = getattr(typification, name, None) else: if name == 'report_type': result[name][nl.id] = 'normal' elif name == 'report_result_type': result[name][nl.id] = 'result' elif name == 'check_result_limits': result[name][nl.id] = False else: result[name][nl.id] = None return result @classmethod def search_typification_field(cls, name, clause): cursor = Transaction().connection.cursor() pool = Pool() Notebook = pool.get('lims.notebook') Fraction = pool.get('lims.fraction') Sample = pool.get('lims.sample') Typification = pool.get('lims.typification') operator_ = clause[1:2][0] cursor.execute('SELECT nl.id ' 'FROM "' + cls._table + '" nl ' 'INNER JOIN "' + Notebook._table + '" n ' 'ON nl.notebook = n.id ' 'INNER JOIN "' + Fraction._table + '" f ' 'ON n.fraction = f.id ' 'INNER JOIN "' + Sample._table + '" s ' 'ON f.sample = s.id ' 'INNER JOIN "' + Typification._table + '" t ' 'ON (nl.analysis = t.analysis AND nl.method = t.method ' 'AND s.product_type = t.product_type AND t.matrix = t.matrix) ' 'WHERE t.valid = TRUE ' 'AND t.' + name + ' ' + operator_ + ' %s', clause[2:3]) return [('id', 'in', [x[0] for x in cursor.fetchall()])] @fields.depends('method', 'party', '_parent_method.results_estimated_waiting') def on_change_with_results_estimated_waiting(self, name=None): LabMethodWaitingTime = Pool().get('lims.lab.method.results_waiting') if self.method: waiting_times = LabMethodWaitingTime.search([ ('method', '=', self.method.id), ('party', '=', self.party.id), ]) if waiting_times: return waiting_times[0].results_estimated_waiting return self.method.results_estimated_waiting return None @classmethod def get_results_estimated_date(cls, notebook_lines, name): result = {} for nl in notebook_lines: result[nl.id] = None detail = getattr(nl, 'analysis_detail', None) if not detail: continue confirmation_date = getattr(detail, 'confirmation_date', None) if not confirmation_date: continue estimated_waiting = getattr(nl, 'results_estimated_waiting', None) if not estimated_waiting: continue result[nl.id] = cls._get_results_estimated_date(confirmation_date, estimated_waiting) return result @staticmethod def _get_results_estimated_date(confirmation_date, estimated_waiting): date = (confirmation_date + relativedelta(days=estimated_waiting)) return date @fields.depends('analysis', '_parent_analysis.methods') def on_change_with_method_domain(self, name=None): methods = [] if self.analysis and self.analysis.methods: methods = [m.id for m in self.analysis.methods] return methods @fields.depends('analysis', 'laboratory') def on_change_with_device_domain(self, name=None): cursor = Transaction().connection.cursor() AnalysisDevice = Pool().get('lims.analysis.device') if not self.analysis or not self.laboratory: return [] cursor.execute('SELECT DISTINCT(device) ' 'FROM "' + AnalysisDevice._table + '" ' 'WHERE active IS TRUE ' 'AND analysis = %s ' 'AND laboratory = %s', (self.analysis.id, self.laboratory.id)) res = cursor.fetchall() if not res: return [] return [x[0] for x in res] @fields.depends('analysis', 'laboratory') def on_change_with_department_domain(self, name=None): cursor = Transaction().connection.cursor() AnalysisLaboratory = Pool().get('lims.analysis-laboratory') if not self.analysis or not self.laboratory: return [] cursor.execute('SELECT DISTINCT(department) ' 'FROM "' + AnalysisLaboratory._table + '" ' 'WHERE analysis = %s ' 'AND laboratory = %s', (self.analysis.id, self.laboratory.id)) res = cursor.fetchall() if not res: return [] return [x[0] for x in res] def get_icon(self, name): if self.report_date: return 'lims-red' return 'lims-white' def get_planning_comments(self, name=None): if self.planification: return self.planification.comments return '' def get_formated_result(self, name=None): res = '' result_modifier = self.result_modifier and self.result_modifier.code if self.literal_result: res = self.literal_result else: decimals = (self.result_decimals if self.result_decimals is not None else self.decimals) res = self._format_result(self.result, decimals, self.significant_digits, self.scientific_notation) if not result_modifier: res = res elif result_modifier == 'low': res = gettext('lims.msg_quantification_limit', loq=res) elif result_modifier == 'd': res = gettext('lims.msg_d') elif result_modifier == 'nd': res = gettext('lims.msg_nd') elif result_modifier == 'ni': res = '' elif result_modifier == 'pos': res = gettext('lims.msg_pos') elif result_modifier == 'neg': res = gettext('lims.msg_neg') elif result_modifier == 'pre': res = gettext('lims.msg_pre') elif result_modifier == 'abs': res = gettext('lims.msg_abs') else: res = self.result_modifier.name return res def get_formated_converted_result(self, name=None): res = '' result_modifier = (self.converted_result_modifier and self.converted_result_modifier.code) if not self.literal_result: decimals = (self.converted_result_decimals if self.converted_result_decimals is not None else self.decimals) res = self._format_result(self.converted_result, decimals, self.significant_digits, self.scientific_notation) if not result_modifier: res = res elif result_modifier == 'low': res = gettext('lims.msg_quantification_limit', loq=res) elif result_modifier == 'd': res = gettext('lims.msg_d') elif result_modifier == 'nd': res = gettext('lims.msg_nd') elif result_modifier == 'ni': res = '' elif result_modifier == 'pos': res = gettext('lims.msg_pos') elif result_modifier == 'neg': res = gettext('lims.msg_neg') elif result_modifier == 'pre': res = gettext('lims.msg_pre') elif result_modifier == 'abs': res = gettext('lims.msg_abs') else: res = self.converted_result_modifier.name return res def _format_result(self, result, decimals, significant_digits=None, scientific_notation=False): res = '' if not result: return res try: if significant_digits: res = ("{0:.%ie}" % (significant_digits - 1)).format( float(result)) if not scientific_notation: res = str(float(res)) if float(res) < 1 and len(res) < (significant_digits + 2): res = res.ljust(significant_digits + 2, '0') else: res = round(float(result), decimals) res = format(res, '.{}f'.format(decimals)) except (TypeError, ValueError): pass return res class NotebookLineAllFields(ModelSQL, ModelView): 'Laboratory Notebook Line' __name__ = 'lims.notebook.line.all_fields' line = fields.Many2One('lims.notebook.line', 'Notebook Line') fraction = fields.Many2One('lims.fraction', 'Fraction', readonly=True) fraction_type = fields.Many2One('lims.fraction.type', 'Fraction type', readonly=True) party = fields.Many2One('party.party', 'Party', readonly=True) party_code = fields.Char('Party', readonly=True) product_type = fields.Many2One('lims.product.type', 'Product type', readonly=True) matrix = fields.Many2One('lims.matrix', 'Matrix', readonly=True) label = fields.Char('Label', readonly=True) date = fields.DateTime('Date', readonly=True) analysis = fields.Many2One('lims.analysis', 'Analysis', readonly=True) repetition = fields.Integer('Repetition', readonly=True) start_date = fields.Date('Start date', readonly=True) end_date = fields.Date('End date', readonly=True) laboratory = fields.Many2One('lims.laboratory', 'Laboratory', readonly=True) method = fields.Many2One('lims.lab.method', 'Method', readonly=True) device = fields.Many2One('lims.lab.device', 'Device', readonly=True) service = fields.Many2One('lims.service', 'Service', readonly=True) analysis_origin = fields.Char('Analysis origin', readonly=True) urgent = fields.Boolean('Urgent', readonly=True) priority = fields.Integer('Priority', readonly=True) initial_concentration = fields.Char('Initial concentration', readonly=True) final_concentration = fields.Char('Final concentration', readonly=True) literal_final_concentration = fields.Char('Literal Final concentration', readonly=True) laboratory_professionals = fields.Function(fields.Many2Many( 'lims.laboratory.professional', None, None, 'Preparation professionals'), 'get_line_field', searcher='search_line_field') initial_unit = fields.Many2One('product.uom', 'Initial unit', readonly=True) final_unit = fields.Many2One('product.uom', 'Final unit', readonly=True) result_modifier = fields.Many2One('lims.result_modifier', 'Result modifier', readonly=True) converted_result_modifier = fields.Many2One('lims.result_modifier', 'Converted result modifier', readonly=True) result = fields.Char('Result', readonly=True) converted_result = fields.Char('Converted result', readonly=True) formated_result = fields.Function(fields.Char('Result to report'), 'get_line_field') formated_converted_result = fields.Function(fields.Char( 'Converted result to report'), 'get_line_field') detection_limit = fields.Char('Detection limit', readonly=True) quantification_limit = fields.Char('Quantification limit', readonly=True) lower_limit = fields.Char('Lower limit allowed', readonly=True) upper_limit = fields.Char('Upper limit allowed', readonly=True) chromatogram = fields.Char('Chromatogram', readonly=True) professionals = fields.Function(fields.One2Many( 'lims.notebook.line.professional', None, 'Analytic professionals'), 'get_line_field', searcher='search_line_field') comments = fields.Text('Entry comments', readonly=True) theoretical_concentration = fields.Char('Theoretical concentration', readonly=True) concentration_level = fields.Many2One('lims.concentration.level', 'Concentration level', readonly=True) decimals = fields.Integer('Decimals', readonly=True) significant_digits = fields.Integer('Significant digits', readonly=True) scientific_notation = fields.Boolean('Scientific notation', readonly=True) result_decimals = fields.Integer('Decimals to report result', readonly=True) converted_result_decimals = fields.Integer( 'Decimals to report converted result', readonly=True) backup = fields.Char('Backup', readonly=True) reference = fields.Char('Reference', readonly=True) literal_result = fields.Char('Literal result', readonly=True) rm_correction_formula = fields.Char('RM Correction Formula', readonly=True) report = fields.Boolean('Report', readonly=True) uncertainty = fields.Char('Uncertainty', readonly=True) verification = fields.Char('Verification', readonly=True) dilution_factor = fields.Float('Dilution factor', readonly=True) accepted = fields.Boolean('Accepted', readonly=True) acceptance_date = fields.DateTime('Acceptance date', readonly=True) annulled = fields.Boolean('Annulled', readonly=True) annulment_date = fields.DateTime('Annulment date', readonly=True) annulment_reason = fields.Text('Annulment reason', readonly=True, states={'invisible': ~Eval('annulled')}) results_report = fields.Many2One('lims.results_report', 'Results Report', readonly=True) planification = fields.Many2One('lims.planification', 'Planification', readonly=True) confirmation_date = fields.Date('Confirmation date', readonly=True) results_estimated_waiting = fields.Integer( 'Estimated number of days for results', readonly=True) results_estimated_date = fields.Function(fields.Date( 'Estimated date of result'), 'get_line_field') laboratory_date = fields.Date('Laboratory deadline', readonly=True) report_date = fields.Date('Date agreed for result', readonly=True) department = fields.Many2One('company.department', 'Department', readonly=True) referral = fields.Function(fields.Many2One('lims.referral', 'Referral'), 'get_line_field', searcher='search_line_field') repetition_reason = fields.Char('Repetition reason', readonly=True, states={'invisible': Eval('repetition', 0) == 0}) exceptional_load = fields.Boolean('Exceptionally loaded result', readonly=True) exceptional_load_uid = fields.Many2One('res.user', 'Exceptional loading of results User', readonly=True) trace_report = fields.Boolean('Trace report', readonly=True) sample_client_description = fields.Function(fields.Char( 'Product described by the client', translate=True), 'get_sample_field') @classmethod def __setup__(cls): super().__setup__() cls._order.insert(0, ('fraction', 'DESC')) cls._order.insert(1, ('analysis', 'ASC')) cls._order.insert(2, ('repetition', 'ASC')) @staticmethod def table_query(): pool = Pool() line = pool.get('lims.notebook.line').__table__() detail = pool.get('lims.entry.detail.analysis').__table__() service = pool.get('lims.service').__table__() fraction = pool.get('lims.fraction').__table__() sample = pool.get('lims.sample').__table__() entry = pool.get('lims.entry').__table__() party = pool.get('party.party').__table__() join1 = Join(line, service) join1.condition = join1.right.id == line.service join2 = Join(join1, fraction) join2.condition = join2.right.id == join1.right.fraction join3 = Join(join2, sample) join3.condition = join3.right.id == join2.right.sample join4 = Join(join3, entry) join4.condition = join4.right.id == join3.right.entry join5 = Join(join4, party) join5.condition = join5.right.id == join4.right.party join6 = Join(join5, detail) join6.condition = join6.right.id == join1.left.analysis_detail columns = [ line.id, line.create_uid, line.create_date, line.write_uid, line.write_date, line.id.as_('line'), service.fraction, sample.party, party.code.as_('party_code'), sample.product_type, sample.matrix, sample.label, fraction.type.as_('fraction_type'), sample.date, line.analysis, line.repetition, line.start_date, line.end_date, line.laboratory, line.method, line.device, line.service, line.analysis_origin, line.urgent, service.priority, line.initial_concentration, line.final_concentration, line.literal_final_concentration, line.initial_unit, line.final_unit, line.result_modifier, line.converted_result_modifier, line.result, line.converted_result, line.detection_limit, line.quantification_limit, line.lower_limit, line.upper_limit, line.dilution_factor, line.chromatogram, line.comments, line.theoretical_concentration, line.concentration_level, line.decimals, line.significant_digits, line.scientific_notation, line.result_decimals, line.converted_result_decimals, line.backup, line.reference, line.literal_result, line.rm_correction_formula, line.report, line.uncertainty, line.verification, line.accepted, line.acceptance_date, line.annulled, line.annulment_date, line.annulment_reason, line.results_report, line.planification, detail.confirmation_date, line.results_estimated_waiting, service.laboratory_date, service.report_date, line.department, line.repetition_reason, line.exceptional_load, line.exceptional_load_uid, line.trace_report, ] where = Literal(True) return join6.select(*columns, where=where) def get_rec_name(self, name): return self.line.get_rec_name(name) @classmethod def get_line_field(cls, notebook_lines, names): result = dict((name, {}) for name in names) for nl in notebook_lines: for name in names: field = getattr(nl.line, name, None) if isinstance(field, ModelSQL): result[name][nl.id] = field.id if field else None elif isinstance(field, tuple): result[name][nl.id] = [f.id for f in field] else: result[name][nl.id] = field return result @classmethod def search_line_field(cls, name, clause): return [('line.' + name,) + tuple(clause[1:])] @classmethod def get_sample_field(cls, notebook_lines, names): result = dict((name, {}) for name in names) for nl in notebook_lines: for name in names: result[name][nl.id] = None fraction = getattr(nl.line.service, 'fraction', None) if not fraction: continue sample = getattr(fraction, 'sample', None) if not sample: continue field = getattr(sample, name, None) if isinstance(field, ModelSQL): result[name][nl.id] = field.id if field else None elif isinstance(field, tuple): result[name][nl.id] = [f.id for f in field] else: result[name][nl.id] = field return result class NotebookLineLaboratoryProfessional(ModelSQL): 'Laboratory Notebook Line - Laboratory Professional' __name__ = 'lims.notebook.line-laboratory.professional' notebook_line = fields.Many2One('lims.notebook.line', 'Notebook Line', ondelete='CASCADE', required=True) professional = fields.Many2One('lims.laboratory.professional', 'Laboratory professional', ondelete='CASCADE', required=True) class NotebookLineProfessional(ModelSQL, ModelView): 'Laboratory Notebook Line Professional' __name__ = 'lims.notebook.line.professional' notebook_line = fields.Many2One('lims.notebook.line', 'Notebook Line', ondelete='CASCADE', required=True) professional = fields.Many2One('lims.laboratory.professional', 'Laboratory professional', required=True) class NotebookInitialConcentrationCalcStart(ModelView): 'Initial Concentration Calculation' __name__ = 'lims.notebook.initial_concentration_calc.start' class NotebookInitialConcentrationCalc(Wizard): 'Initial Concentration Calculation' __name__ = 'lims.notebook.initial_concentration_calc' start_state = 'ok' start = StateView('lims.notebook.initial_concentration_calc.start', 'lims.lims_notebook_initial_concentration_calc_view_form', [ Button('Cancel', 'end', 'tryton-cancel'), Button('Ok', 'ok', 'tryton-ok', default=True), ]) ok = StateTransition() def transition_ok(self): NotebookLine = Pool().get('lims.notebook.line') for notebook_id in Transaction().context['active_ids']: with Transaction().set_context(_check_access=True): notebook_lines = NotebookLine.search([ ('notebook', '=', notebook_id), ]) if not notebook_lines: continue self.lines_initial_concentration_calc(notebook_lines) return 'end' def lines_initial_concentration_calc(self, notebook_lines): NotebookLine = Pool().get('lims.notebook.line') lines_to_save = [] for notebook_line in notebook_lines: if notebook_line.accepted: continue ic = notebook_line.initial_concentration if not ic: continue if ic[0] == 'A': analysis_code = ic[1:] result = self._get_analysis_result(analysis_code, notebook_line.notebook) if result is not None: notebook_line.initial_concentration = str(result) lines_to_save.append(notebook_line) elif ic[0] == 'R': analysis_code = ic[1:] result = self._get_relation_result(analysis_code, notebook_line.notebook) if result is not None: notebook_line.initial_concentration = str(result) lines_to_save.append(notebook_line) else: continue if lines_to_save: NotebookLine.save(lines_to_save) def _get_analysis_result(self, analysis_code, notebook, round_=False): NotebookLine = Pool().get('lims.notebook.line') with Transaction().set_user(0): notebook_lines = NotebookLine.search([ ('notebook', '=', notebook.id), ('analysis.code', '=', analysis_code), ('annulment_date', '=', None), ]) if not notebook_lines: return None try: res = float(notebook_lines[0].result) except (TypeError, ValueError): return None if not round_: return res return round(res, notebook_lines[0].decimals) def _get_relation_result(self, analysis_code, notebook, round_=False): pool = Pool() Analysis = pool.get('lims.analysis') NotebookLine = pool.get('lims.notebook.line') internal_relations = Analysis.search([ ('code', '=', analysis_code), ]) if not internal_relations: return None formula = internal_relations[0].result_formula if not formula: return None for i in (' ', '\t', '\n', '\r'): formula = formula.replace(i, '') variables = self._get_variables(formula, notebook) if not variables: return None parser = FormulaParser(formula, variables) value = parser.getValue() if int(value) == value: res = int(value) else: epsilon = 0.0000000001 if int(value + epsilon) != int(value): res = int(value + epsilon) elif int(value - epsilon) != int(value): res = int(value) else: res = float(value) if not round_: return res with Transaction().set_user(0): notebook_lines = NotebookLine.search([ ('notebook', '=', notebook.id), ('analysis.code', '=', analysis_code), ('repetition', '=', 0), ('annulment_date', '=', None), ]) if not notebook_lines: return None return round(res, notebook_lines[0].decimals) def _get_variables(self, formula, notebook): pool = Pool() VolumeConversion = pool.get('lims.volume.conversion') variables = {} for variable in re.findall(r'\{.*?\}', formula): var = variable.replace('{', '').replace('}', '') for prefix in ('A', 'D', 'T', 'Y', 'R'): if var.startswith(prefix): variables[var] = None for var in variables.keys(): if var[0] == 'A': analysis_code = var[1:] result = self._get_analysis_result(analysis_code, notebook, round_=True) if result is not None: variables[var] = result elif var[0] == 'D': analysis_code = var[1:] result = self._get_analysis_result(analysis_code, notebook, round_=True) if result is not None: result = VolumeConversion.brixToDensity(result) if result is not None: variables[var] = result elif var[0] == 'T': analysis_code = var[1:] result = self._get_analysis_result(analysis_code, notebook, round_=True) if result is not None: result = VolumeConversion.brixToSolubleSolids(result) if result is not None: variables[var] = result elif var[0] == 'R': analysis_code = var[1:] result = self._get_relation_result(analysis_code, notebook, round_=True) if result is not None: result = VolumeConversion.brixToSolubleSolids(result) if result is not None: variables[var] = result elif var[0] == 'Y': analysis_code = var[1:] result = self._get_relation_result(analysis_code, notebook, round_=True) if result is not None: result = VolumeConversion.brixToDensity(result) if result is not None: variables[var] = result for var in variables.values(): if var is None: return None return variables def end(self): return 'reload' class NotebookLineInitialConcentrationCalc(NotebookInitialConcentrationCalc): 'Initial Concentration Calculation' __name__ = 'lims.notebook_line.initial_concentration_calc' def transition_ok(self): NotebookLine = Pool().get('lims.notebook.line') notebook_lines = NotebookLine.browse( Transaction().context['active_ids']) if not notebook_lines: return 'end' self.lines_initial_concentration_calc(notebook_lines) return 'end' class NotebookInitialConcentrationCalc2Start(ModelView): 'Initial Concentration Calculation' __name__ = 'lims.notebook.initial_concentration_calc_2.start' variables = fields.One2Many( 'lims.notebook.initial_concentration_calc_2.variable', None, 'Variables') class NotebookInitialConcentrationCalc2Variable(ModelView): 'Formula Variable' __name__ = 'lims.notebook.initial_concentration_calc_2.variable' line = fields.Many2One('lims.notebook.line', 'Line') analysis = fields.Many2One('lims.analysis', 'Analysis', readonly=True) repetition = fields.Integer('Repetition', readonly=True) result_modifier = fields.Many2One('lims.result_modifier', 'Result modifier', readonly=True) result = fields.Char('Result', readonly=True) initial_unit = fields.Many2One('product.uom', 'Initial unit', readonly=True) use = fields.Boolean('Use') class NotebookInitialConcentrationCalc2(Wizard): 'Initial Concentration Calculation' __name__ = 'lims.notebook.initial_concentration_calc_2' start = StateView('lims.notebook.initial_concentration_calc_2.start', 'lims.lims_notebook_initial_concentration_calc_2_start_view_form', [ Button('Cancel', 'end', 'tryton-cancel'), Button('Confirm', 'confirm', 'tryton-ok', default=True), ]) confirm = StateTransition() def _get_notebook_lines(self): NotebookLine = Pool().get('lims.notebook.line') notebook_id = Transaction().context['active_id'] with Transaction().set_context(_check_access=True): notebook_lines = NotebookLine.search([ ('notebook', '=', notebook_id), ]) return notebook_lines def default_start(self, fields): notebook_lines = self._get_notebook_lines() if not notebook_lines: return {} notebook, formula = None, '' for notebook_line in notebook_lines: if notebook_line.accepted: continue analysis_code = notebook_line.analysis.code if not analysis_code or not notebook_line.initial_concentration: continue ic = notebook_line.initial_concentration if ic[0] != 'A' and ic[0] != 'R': continue formula += '{%s}' % ic if not notebook: notebook = notebook_line.notebook if not formula: return {} variables = self._get_variables_list(formula, notebook, {}) if not variables: return {} return {'variables': variables} def _get_variables_list(self, formula, notebook, analysis={}): pool = Pool() Analysis = pool.get('lims.analysis') NotebookLine = pool.get('lims.notebook.line') variables = {} for variable in re.findall(r'\{.*?\}', formula): var = variable.replace('{', '').replace('}', '') for prefix in ('A', 'D', 'T', 'Y', 'R'): if var.startswith(prefix): variables[var] = None for var in variables.keys(): if var[0] in ('A', 'D', 'T'): analysis_code = var[1:] with Transaction().set_user(0): notebook_lines = NotebookLine.search([ ('notebook', '=', notebook.id), ('analysis.code', '=', analysis_code), ('annulment_date', '=', None), ]) if not notebook_lines: continue for nl in notebook_lines: analysis[nl.id] = { 'line': nl.id, 'analysis': nl.analysis.id, 'repetition': nl.repetition, 'use': True if nl.repetition == 0 else False, 'result_modifier': (nl.result_modifier and nl.result_modifier.id), 'result': nl.result, 'initial_unit': (nl.initial_unit and nl.initial_unit.id), } elif var[0] in ('R', 'Y'): analysis_code = var[1:] internal_relations = Analysis.search([ ('code', '=', analysis_code), ]) if not internal_relations: continue more_formulas = internal_relations[0].result_formula for i in (' ', '\t', '\n', '\r'): more_formulas = more_formulas.replace(i, '') self._get_variables_list(more_formulas, notebook, analysis) return [v for v in analysis.values()] def transition_confirm(self): NotebookLine = Pool().get('lims.notebook.line') # check_variables variables = {} for var in self.start.variables: analysis_code = var.analysis.code if analysis_code not in variables: variables[analysis_code] = False if var.use: if variables[analysis_code]: variables[analysis_code] = False else: variables[analysis_code] = True for var in variables.values(): if var is False: return 'start' var_values = {} for var in self.start.variables: if not var.use: continue try: res = float(var.line.result) except (TypeError, ValueError): var_values[var.analysis.code] = None else: var_values[var.analysis.code] = res notebook_lines_to_save = [] notebook_lines = self._get_notebook_lines() for notebook_line in notebook_lines: ic = notebook_line.initial_concentration if not ic: continue analysis_code = ic[1:] if ic[0] == 'A': result = self._get_analysis_result(analysis_code, var_values) if result is not None: notebook_line.initial_concentration = str(result) notebook_lines_to_save.append(notebook_line) elif ic[0] == 'R': result = self._get_relation_result(analysis_code, var_values) if result is not None: notebook_line.initial_concentration = str(result) notebook_lines_to_save.append(notebook_line) else: continue if notebook_lines_to_save: NotebookLine.save(notebook_lines_to_save) return 'end' def _get_analysis_result(self, analysis_code, var_values): res = var_values[analysis_code] return res def _get_relation_result(self, analysis_code, var_values): pool = Pool() Analysis = pool.get('lims.analysis') internal_relations = Analysis.search([ ('code', '=', analysis_code), ]) if not internal_relations: return None formula = internal_relations[0].result_formula if not formula: return None for i in (' ', '\t', '\n', '\r'): formula = formula.replace(i, '') variables = self._get_variables(formula, var_values) if not variables: return None parser = FormulaParser(formula, variables) value = parser.getValue() if int(value) == value: res = int(value) else: epsilon = 0.0000000001 if int(value + epsilon) != int(value): res = int(value + epsilon) elif int(value - epsilon) != int(value): res = int(value) else: res = float(value) return res def _get_variables(self, formula, var_values): pool = Pool() VolumeConversion = pool.get('lims.volume.conversion') variables = {} for variable in re.findall(r'\{.*?\}', formula): var = variable.replace('{', '').replace('}', '') for prefix in ('A', 'D', 'T', 'Y', 'R'): if var.startswith(prefix): variables[var] = None for var in variables.keys(): if var[0] == 'A': analysis_code = var[1:] result = self._get_analysis_result(analysis_code, var_values) if result is not None: variables[var] = result elif var[0] == 'D': analysis_code = var[1:] result = self._get_analysis_result(analysis_code, var_values) if result is not None: result = VolumeConversion.brixToDensity(result) if result is not None: variables[var] = result elif var[0] == 'T': analysis_code = var[1:] result = self._get_analysis_result(analysis_code, var_values) if result is not None: result = VolumeConversion.brixToSolubleSolids(result) if result is not None: variables[var] = result elif var[0] == 'R': analysis_code = var[1:] result = self._get_relation_result(analysis_code, var_values) if result is not None: result = VolumeConversion.brixToSolubleSolids(result) if result is not None: variables[var] = result elif var[0] == 'Y': analysis_code = var[1:] result = self._get_relation_result(analysis_code, var_values) if result is not None: result = VolumeConversion.brixToDensity(result) if result is not None: variables[var] = result for var in variables.values(): if var is None: return None return variables def end(self): return 'reload' class NotebookLineInitialConcentrationCalc2(NotebookInitialConcentrationCalc2): 'Initial Concentration Calculation' __name__ = 'lims.notebook_line.initial_concentration_calc_2' def _get_notebook_lines(self): NotebookLine = Pool().get('lims.notebook.line') notebook_lines = NotebookLine.browse( Transaction().context['active_ids']) return notebook_lines class NotebookResultsConversionStart(ModelView): 'Results Conversion' __name__ = 'lims.notebook.results_conversion.start' class NotebookResultsConversion(Wizard): 'Results Conversion' __name__ = 'lims.notebook.results_conversion' start_state = 'ok' start = StateView('lims.notebook.results_conversion.start', 'lims.lims_notebook_results_conversion_view_form', [ Button('Cancel', 'end', 'tryton-cancel'), Button('Ok', 'ok', 'tryton-ok', default=True), ]) ok = StateTransition() def transition_ok(self): NotebookLine = Pool().get('lims.notebook.line') for notebook_id in Transaction().context['active_ids']: with Transaction().set_context(_check_access=True): notebook_lines = NotebookLine.search([ ('notebook', '=', notebook_id), ]) if not notebook_lines: continue self.lines_results_conversion(notebook_lines) return 'end' def lines_results_conversion(self, notebook_lines): pool = Pool() NotebookLine = pool.get('lims.notebook.line') UomConversion = pool.get('lims.uom.conversion') VolumeConversion = pool.get('lims.volume.conversion') lines_to_save = [] for notebook_line in notebook_lines: if notebook_line.accepted: continue if (notebook_line.converted_result or not notebook_line.result or notebook_line.result_modifier): continue iu = notebook_line.initial_unit if not iu: continue fu = notebook_line.final_unit if not fu: continue try: ic = float(notebook_line.initial_concentration) except (TypeError, ValueError): continue try: fc = float(notebook_line.final_concentration) except (TypeError, ValueError): continue try: result = float(notebook_line.result) except (TypeError, ValueError): continue if (iu == fu and ic == fc): converted_result = result notebook_line.converted_result = str(converted_result) notebook_line.converted_result_modifier = None lines_to_save.append(notebook_line) elif (iu != fu and ic == fc): formula = UomConversion.get_conversion_formula(iu, fu) if not formula: continue variables = self._get_variables(formula, notebook_line) parser = FormulaParser(formula, variables) formula_result = parser.getValue() converted_result = result * formula_result notebook_line.converted_result = str(converted_result) notebook_line.converted_result_modifier = None lines_to_save.append(notebook_line) elif (iu == fu and ic != fc): converted_result = result * (fc / ic) notebook_line.converted_result = str(converted_result) notebook_line.converted_result_modifier = None lines_to_save.append(notebook_line) else: formula = None conversions = UomConversion.search([ ('initial_uom', '=', iu), ('final_uom', '=', fu), ]) if conversions: formula = conversions[0].conversion_formula if not formula: continue initial_uom_volume = conversions[0].initial_uom_volume final_uom_volume = conversions[0].final_uom_volume variables = self._get_variables(formula, notebook_line, initial_uom_volume, final_uom_volume) parser = FormulaParser(formula, variables) formula_result = parser.getValue() if initial_uom_volume and final_uom_volume: d_ic = VolumeConversion.brixToDensity(ic) d_fc = VolumeConversion.brixToDensity(fc) converted_result = (result * (fc / ic) * (d_fc / d_ic) * formula_result) notebook_line.converted_result = str(converted_result) notebook_line.converted_result_modifier = None lines_to_save.append(notebook_line) else: converted_result = result * (fc / ic) * formula_result notebook_line.converted_result = str(converted_result) notebook_line.converted_result_modifier = None lines_to_save.append(notebook_line) if lines_to_save: NotebookLine.save(lines_to_save) def _get_variables(self, formula, notebook_line, initial_uom_volume=False, final_uom_volume=False): pool = Pool() VolumeConversion = pool.get('lims.volume.conversion') variables = {} for var in ('DI',): while True: idx = formula.find(var) if idx >= 0: variables[var] = 0 formula = formula.replace(var, '_') else: break for var in variables.keys(): if var == 'DI': if initial_uom_volume: c = float(notebook_line.initial_concentration) result = VolumeConversion.brixToDensity(c) if result: variables[var] = result elif final_uom_volume: c = float(notebook_line.final_concentration) result = VolumeConversion.brixToDensity(c) if result: variables[var] = result return variables def end(self): return 'reload' class NotebookLineResultsConversion(NotebookResultsConversion): 'Results Conversion' __name__ = 'lims.notebook_line.results_conversion' def transition_ok(self): NotebookLine = Pool().get('lims.notebook.line') notebook_lines = NotebookLine.browse( Transaction().context['active_ids']) if not notebook_lines: return 'end' self.lines_results_conversion(notebook_lines) return 'end' class NotebookLimitsValidationStart(ModelView): 'Limits Validation' __name__ = 'lims.notebook.limits_validation.start' class NotebookLimitsValidation(Wizard): 'Limits Validation' __name__ = 'lims.notebook.limits_validation' start_state = 'ok' start = StateView('lims.notebook.limits_validation.start', 'lims.lims_notebook_limits_validation_view_form', [ Button('Cancel', 'end', 'tryton-cancel'), Button('Ok', 'ok', 'tryton-ok', default=True), ]) ok = StateTransition() def transition_ok(self): NotebookLine = Pool().get('lims.notebook.line') for notebook_id in Transaction().context['active_ids']: with Transaction().set_context(_check_access=True): notebook_lines = NotebookLine.search([ ('notebook', '=', notebook_id), ]) if not notebook_lines: continue self.lines_limits_validation(notebook_lines) return 'end' def lines_limits_validation(self, notebook_lines): pool = Pool() ModelData = pool.get('ir.model.data') NotebookLine = pool.get('lims.notebook.line') result_modifier_low = ModelData.get_id('lims', 'result_modifier_low') result_modifier_nd = ModelData.get_id('lims', 'result_modifier_nd') result_modifier_ni = ModelData.get_id('lims', 'result_modifier_ni') lines_to_save = [] for notebook_line in notebook_lines: if notebook_line.accepted: continue try: dl = float(notebook_line.detection_limit) ql = float(notebook_line.quantification_limit) except (TypeError, ValueError): continue ll = (notebook_line.lower_limit and float(notebook_line.lower_limit) or None) ul = (notebook_line.upper_limit and float(notebook_line.upper_limit) or None) if (notebook_line.result and ( notebook_line.check_result_limits or not notebook_line.converted_result)): if notebook_line.result_modifier: continue try: value = float(notebook_line.result) except ValueError: continue if (ll and value < ll) or (ul and value > ul): raise UserError(gettext('lims.msg_error_limits_allowed', line=notebook_line.rec_name)) if dl < value and value < ql: notebook_line.result = str(ql) notebook_line.result_modifier = result_modifier_low notebook_line.converted_result = None notebook_line.converted_result_modifier = None notebook_line.rm_correction_formula = None elif value < dl: notebook_line.result = None notebook_line.result_modifier = result_modifier_nd notebook_line.converted_result = None notebook_line.converted_result_modifier = None notebook_line.rm_correction_formula = None elif value == dl: notebook_line.result = str(ql) notebook_line.result_modifier = result_modifier_low notebook_line.converted_result = None notebook_line.converted_result_modifier = None notebook_line.rm_correction_formula = None notebook_line.backup = str(value) lines_to_save.append(notebook_line) elif notebook_line.converted_result: if notebook_line.converted_result_modifier: continue try: value = float(notebook_line.converted_result) except ValueError: continue if (ll and value < ll) or (ul and value > ul): raise UserError(gettext('lims.msg_error_limits_allowed', line=notebook_line.rec_name)) if dl < value and value < ql: notebook_line.converted_result = str(ql) notebook_line.converted_result_modifier = ( result_modifier_low) notebook_line.result_modifier = result_modifier_ni notebook_line.rm_correction_formula = None elif value < dl: notebook_line.converted_result = None notebook_line.converted_result_modifier = ( result_modifier_nd) notebook_line.result_modifier = result_modifier_ni notebook_line.rm_correction_formula = None elif value == dl: notebook_line.converted_result = str(ql) notebook_line.converted_result_modifier = ( result_modifier_low) notebook_line.result_modifier = result_modifier_ni notebook_line.rm_correction_formula = None notebook_line.backup = str(value) lines_to_save.append(notebook_line) else: continue if lines_to_save: NotebookLine.save(lines_to_save) def end(self): return 'reload' class NotebookLineLimitsValidation(NotebookLimitsValidation): 'Limits Validation' __name__ = 'lims.notebook_line.limits_validation' def transition_ok(self): NotebookLine = Pool().get('lims.notebook.line') notebook_lines = NotebookLine.browse( Transaction().context['active_ids']) if not notebook_lines: return 'end' self.lines_limits_validation(notebook_lines) return 'end' class NotebookInternalRelationsCalc1Start(ModelView): 'Internal Relations Calculation' __name__ = 'lims.notebook.internal_relations_calc_1.start' class NotebookInternalRelationsCalc1Relation(ModelSQL): 'Internal Relations Calculation' __name__ = 'lims.notebook.internal_relations_calc_1.relation' _table = 'lims_notebook_internal_relations_c_1_rel' notebook = fields.Many2One('lims.notebook', 'Laboratory notebook') internal_relation = fields.Many2One('lims.analysis', 'Internal relation') variables = fields.One2Many( 'lims.notebook.internal_relations_calc_1.variable', 'relation', 'Variables') session_id = fields.Integer('Session ID') @classmethod def __register__(cls, module_name): super().__register__(module_name) cursor = Transaction().connection.cursor() cursor.execute('DELETE FROM "' + cls._table + '"') class NotebookInternalRelationsCalc1Variable(ModelSQL): 'Formula Variable' __name__ = 'lims.notebook.internal_relations_calc_1.variable' relation = fields.Many2One( 'lims.notebook.internal_relations_calc_1.relation', 'Relation', ondelete='CASCADE', readonly=True) line = fields.Many2One('lims.notebook.line', 'Line') analysis = fields.Many2One('lims.analysis', 'Analysis', readonly=True) repetition = fields.Integer('Repetition', readonly=True) use = fields.Boolean('Use') @classmethod def __register__(cls, module_name): super().__register__(module_name) cursor = Transaction().connection.cursor() cursor.execute('DELETE FROM "' + cls._table + '"') class NotebookInternalRelationsCalc1(Wizard): 'Internal Relations Calculation' __name__ = 'lims.notebook.internal_relations_calc_1' start_state = 'search' start = StateView('lims.notebook.internal_relations_calc_1.start', 'lims.lims_notebook_internal_relations_calc_1_start_view_form', [ Button('Cancel', 'end', 'tryton-cancel'), Button('Search', 'search', 'tryton-forward', default=True), ]) search = StateTransition() confirm = StateTransition() def transition_search(self): NotebookLine = Pool().get('lims.notebook.line') for notebook_id in Transaction().context['active_ids']: with Transaction().set_context(_check_access=True): notebook_lines = NotebookLine.search([ ('notebook', '=', notebook_id), ]) if not notebook_lines: continue self.get_relations(notebook_lines) return 'confirm' def get_relations(self, notebook_lines): NotebookInternalRelationsCalc1Relation = Pool().get( 'lims.notebook.internal_relations_calc_1.relation') relations = {} for notebook_line in notebook_lines: if notebook_line.accepted: continue analysis_code = notebook_line.analysis.code if (not analysis_code or notebook_line.analysis.behavior != 'internal_relation'): continue if notebook_line.result or notebook_line.converted_result: continue formulas = notebook_line.analysis.result_formula formulas += ("+" + notebook_line.analysis.converted_result_formula) for i in (' ', '\t', '\n', '\r'): formulas = formulas.replace(i, '') variables = self._get_variables_list(formulas, notebook_line.notebook, {}) if not variables: continue has_repetition_zero = False for var in variables: if var['use']: has_repetition_zero = True if not has_repetition_zero: continue relations[notebook_line.analysis.id] = { 'notebook': notebook_line.notebook.id, 'internal_relation': notebook_line.analysis.id, 'variables': [('create', variables)], 'session_id': self._session_id, } if relations: NotebookInternalRelationsCalc1Relation.create( [ir for ir in relations.values()]) return True return False def _get_variables_list(self, formula, notebook, analysis={}): pool = Pool() Analysis = pool.get('lims.analysis') NotebookLine = pool.get('lims.notebook.line') variables = {} for variable in re.findall(r'\{.*?\}', formula): var = variable.replace('{', '').replace('}', '') for prefix in ('A', 'D', 'T', 'Y', 'R'): if var.startswith(prefix): variables[var] = None for var in variables.keys(): if var[0] in ('A', 'D', 'T'): analysis_code = var[1:] with Transaction().set_user(0): notebook_lines = NotebookLine.search([ ('notebook', '=', notebook.id), ('analysis.code', '=', analysis_code), ('annulment_date', '=', None), ]) if not notebook_lines: continue for nl in notebook_lines: analysis[nl.id] = { 'line': nl.id, 'analysis': nl.analysis.id, 'repetition': nl.repetition, 'use': True if nl.repetition == 0 else False, } elif var[0] in ('Y', 'R'): analysis_code = var[1:] internal_relations = Analysis.search([ ('code', '=', analysis_code), ]) if not internal_relations: continue more_formulas = internal_relations[0].converted_result_formula more_formulas += "+" + internal_relations[0].result_formula for i in (' ', '\t', '\n', '\r'): more_formulas = more_formulas.replace(i, '') self._get_variables_list(more_formulas, notebook, analysis) return [v for v in analysis.values()] def transition_confirm(self): pool = Pool() Date = pool.get('ir.date') NotebookInternalRelationsCalc1Relation = pool.get( 'lims.notebook.internal_relations_calc_1.relation') NotebookLine = pool.get('lims.notebook.line') date = Date.today() relations = NotebookInternalRelationsCalc1Relation.search([ ('session_id', '=', self._session_id), ]) lines_to_save = [] lines_to_validate_limits = [] for relation in relations: notebook_lines = NotebookLine.search([ ('notebook', '=', relation.notebook.id), ('analysis', '=', relation.internal_relation.id) ]) if len(notebook_lines) != 1: continue analysis_code = relation.internal_relation.code result = self._get_relation_result(analysis_code, relation.notebook, analysis_code, round_=True) converted_result = self._get_relation_result(analysis_code, relation.notebook, analysis_code, converted=True) notebook_line = notebook_lines[0] if result is not None: notebook_line.result = str(result) if converted_result is not None: notebook_line.converted_result = str(converted_result) if result is not None or converted_result is not None: notebook_line.start_date = date notebook_line.end_date = date if notebook_line.laboratory.automatic_accept_result: notebook_line.accepted = True notebook_line.acceptance_date = datetime.now() lines_to_save.append(notebook_line) if notebook_line.analysis.validate_limits_after_calculation: lines_to_validate_limits.append(notebook_line) NotebookLine.save(lines_to_save) if lines_to_validate_limits: self.validate_limits(lines_to_validate_limits) return 'end' def validate_limits(self, notebook_lines): pool = Pool() LimitsValidation = pool.get('lims.notebook.limits_validation', type='wizard') session_id, _, _ = LimitsValidation.create() limits_validation = LimitsValidation(session_id) limits_validation.lines_limits_validation(notebook_lines) def _get_analysis_result(self, analysis_code, notebook, relation_code, converted=False): NotebookInternalRelationsCalc1Variable = Pool().get( 'lims.notebook.internal_relations_calc_1.variable') variables = NotebookInternalRelationsCalc1Variable.search([ ('relation.session_id', '=', self._session_id), ('relation.notebook', '=', notebook.id), ('relation.internal_relation.code', '=', relation_code), ('analysis.code', '=', analysis_code), ('use', '=', True), ]) if not variables: return None notebook_line = variables[0].line if not notebook_line: return None try: if converted: res = float(notebook_line.converted_result) else: res = float(notebook_line.result) except (TypeError, ValueError): return None return round(res, notebook_line.decimals) def _get_relation_result(self, analysis_code, notebook, relation_code, converted=False, round_=False): pool = Pool() Analysis = pool.get('lims.analysis') NotebookLine = pool.get('lims.notebook.line') internal_relations = Analysis.search([ ('code', '=', analysis_code), ]) if not internal_relations: return None if converted: formula = internal_relations[0].converted_result_formula else: formula = internal_relations[0].result_formula if not formula: return None for i in (' ', '\t', '\n', '\r'): formula = formula.replace(i, '') variables = self._get_variables(formula, notebook, relation_code, converted) if not variables: return None parser = FormulaParser(formula, variables) value = parser.getValue() if int(value) == value: res = int(value) else: epsilon = 0.0000000001 if int(value + epsilon) != int(value): res = int(value + epsilon) elif int(value - epsilon) != int(value): res = int(value) else: res = float(value) if not round_: return res with Transaction().set_user(0): notebook_lines = NotebookLine.search([ ('notebook', '=', notebook.id), ('analysis.code', '=', analysis_code), ('repetition', '=', 0), ('annulment_date', '=', None), ]) if not notebook_lines: return None return round(res, notebook_lines[0].decimals) def _get_variables(self, formula, notebook, relation_code, converted=False): pool = Pool() VolumeConversion = pool.get('lims.volume.conversion') variables = {} for variable in re.findall(r'\{.*?\}', formula): var = variable.replace('{', '').replace('}', '') for prefix in ('A', 'D', 'T', 'Y', 'R'): if var.startswith(prefix): variables[var] = None for var in variables.keys(): if var[0] == 'A': analysis_code = var[1:] result = self._get_analysis_result(analysis_code, notebook, relation_code, converted) if result is not None: variables[var] = result elif var[0] == 'D': analysis_code = var[1:] result = self._get_analysis_result(analysis_code, notebook, relation_code, converted) if result is not None: result = VolumeConversion.brixToDensity(result) if result is not None: variables[var] = result elif var[0] == 'T': analysis_code = var[1:] result = self._get_analysis_result(analysis_code, notebook, relation_code, converted) if result is not None: result = VolumeConversion.brixToSolubleSolids(result) if result is not None: variables[var] = result elif var[0] == 'R': analysis_code = var[1:] result = self._get_relation_result(analysis_code, notebook, relation_code, converted, round_=True) if result is not None: result = VolumeConversion.brixToSolubleSolids(result) if result is not None: variables[var] = result elif var[0] == 'Y': analysis_code = var[1:] result = self._get_relation_result(analysis_code, notebook, relation_code, converted, round_=True) if result is not None: result = VolumeConversion.brixToDensity(result) if result is not None: variables[var] = result for var in variables.values(): if var is None: return None return variables def end(self): return 'reload' class NotebookLineInternalRelationsCalc1(NotebookInternalRelationsCalc1): 'Internal Relations Calculation' __name__ = 'lims.notebook_line.internal_relations_calc_1' def transition_search(self): NotebookLine = Pool().get('lims.notebook.line') notebook_lines = NotebookLine.browse( Transaction().context['active_ids']) if not notebook_lines: return 'end' if self.get_relations(notebook_lines): return 'confirm' return 'end' class NotebookInternalRelationsCalc2Start(ModelView): 'Internal Relations Calculation' __name__ = 'lims.notebook.internal_relations_calc_2.start' class NotebookInternalRelationsCalc2Result(ModelView): 'Internal Relations Calculation' __name__ = 'lims.notebook.internal_relations_calc_2.result' relations = fields.Many2Many( 'lims.notebook.internal_relations_calc_2.relation', None, None, 'Relation') total = fields.Integer('Total') index = fields.Integer('Index') class NotebookInternalRelationsCalc2Relation(ModelSQL, ModelView): 'Internal Relations Calculation' __name__ = 'lims.notebook.internal_relations_calc_2.relation' _table = 'lims_notebook_internal_relations_c_2_rel' notebook = fields.Many2One('lims.notebook', 'Laboratory notebook') internal_relation = fields.Many2One('lims.analysis', 'Internal relation') variables = fields.One2Many( 'lims.notebook.internal_relations_calc_2.variable', 'relation', 'Variables') session_id = fields.Integer('Session ID') @classmethod def __register__(cls, module_name): super().__register__(module_name) cursor = Transaction().connection.cursor() cursor.execute('DELETE FROM "' + cls._table + '"') class NotebookInternalRelationsCalc2Variable(ModelSQL, ModelView): 'Formula Variable' __name__ = 'lims.notebook.internal_relations_calc_2.variable' relation = fields.Many2One( 'lims.notebook.internal_relations_calc_2.relation', 'Relation', ondelete='CASCADE', readonly=True) line = fields.Many2One('lims.notebook.line', 'Line') analysis = fields.Many2One('lims.analysis', 'Analysis', readonly=True) repetition = fields.Integer('Repetition', readonly=True) result_modifier = fields.Function(fields.Many2One('lims.result_modifier', 'Result modifier'), 'get_line_field') result = fields.Function(fields.Char('Result'), 'get_line_field') initial_unit = fields.Function(fields.Many2One('product.uom', 'Initial unit'), 'get_line_field') initial_concentration = fields.Function(fields.Char( 'Initial concentration'), 'get_line_field') converted_result_modifier = fields.Function(fields.Many2One( 'lims.result_modifier', 'Converted result modifier'), 'get_line_field') converted_result = fields.Function(fields.Char('Converted result'), 'get_line_field') final_unit = fields.Function(fields.Many2One('product.uom', 'Final unit'), 'get_line_field') final_concentration = fields.Function(fields.Char('Final concentration'), 'get_line_field') use = fields.Boolean('Use') @classmethod def __setup__(cls): super().__setup__() cls._order.insert(0, ('relation', 'ASC')) cls._order.insert(1, ('analysis', 'ASC')) cls._order.insert(2, ('repetition', 'ASC')) @classmethod def get_line_field(cls, variables, names): result = {} for name in names: result[name] = {} if cls._fields[name]._type == 'many2one': for v in variables: field = getattr(v.line, name, None) result[name][v.id] = field.id if field else None else: for v in variables: result[name][v.id] = getattr(v.line, name, None) return result class NotebookInternalRelationsCalc2Process(ModelView): 'Internal Relations Calculation' __name__ = 'lims.notebook.internal_relations_calc_2.process' notebook = fields.Many2One('lims.notebook', 'Laboratory notebook', readonly=True) internal_relation = fields.Many2One('lims.analysis', 'Internal relation', readonly=True) variables = fields.One2Many( 'lims.notebook.internal_relations_calc_2.variable', None, 'Variables') class NotebookInternalRelationsCalc2(Wizard): 'Internal Relations Calculation' __name__ = 'lims.notebook.internal_relations_calc_2' start_state = 'search' start = StateView('lims.notebook.internal_relations_calc_2.start', 'lims.lims_notebook_internal_relations_calc_2_start_view_form', [ Button('Cancel', 'end', 'tryton-cancel'), Button('Search', 'search', 'tryton-forward', default=True), ]) search = StateTransition() result = StateView('lims.notebook.internal_relations_calc_2.result', 'lims.lims_notebook_internal_relations_calc_2_result_view_form', []) next_ = StateTransition() process = StateView('lims.notebook.internal_relations_calc_2.process', 'lims.lims_notebook_internal_relations_calc_2_process_view_form', [ Button('Next', 'check_variables', 'tryton-forward', default=True), ]) check_variables = StateTransition() confirm = StateTransition() def transition_search(self): NotebookLine = Pool().get('lims.notebook.line') for notebook_id in Transaction().context['active_ids']: with Transaction().set_context(_check_access=True): notebook_lines = NotebookLine.search([ ('notebook', '=', notebook_id), ]) if not notebook_lines: continue if self.get_relations(notebook_lines): return 'next_' return 'end' def get_relations(self, notebook_lines): NotebookInternalRelationsCalc2Relation = Pool().get( 'lims.notebook.internal_relations_calc_2.relation') relations = {} for notebook_line in notebook_lines: if notebook_line.accepted: continue analysis_code = notebook_line.analysis.code if (not analysis_code or notebook_line.analysis.behavior != 'internal_relation'): continue if notebook_line.result or notebook_line.converted_result: continue formulas = notebook_line.analysis.result_formula formulas += ("+" + notebook_line.analysis.converted_result_formula) for i in (' ', '\t', '\n', '\r'): formulas = formulas.replace(i, '') variables = self._get_variables_list(formulas, notebook_line.notebook, {}) if not variables: continue has_repetitions = False for var in variables: if var['repetition'] > 0: has_repetitions = True if not has_repetitions: continue relations[notebook_line.analysis.id] = { 'notebook': notebook_line.notebook.id, 'internal_relation': notebook_line.analysis.id, 'variables': [('create', variables)], 'session_id': self._session_id, } if relations: res_lines = NotebookInternalRelationsCalc2Relation.create( [ir for ir in relations.values()]) self.result.relations = res_lines self.result.total = len(self.result.relations) self.result.index = 0 return True return False def transition_next_(self): if self.result.index < self.result.total: relation = self.result.relations[self.result.index] self.process.notebook = relation.notebook.id self.process.internal_relation = relation.internal_relation.id self.process.variables = None self.result.index += 1 return 'process' return 'confirm' def default_process(self, fields): NotebookInternalRelationsCalc2Variable = Pool().get( 'lims.notebook.internal_relations_calc_2.variable') if not self.process.internal_relation: return {} default = {} default['notebook'] = self.process.notebook.id default['internal_relation'] = self.process.internal_relation.id if self.process.variables: default['variables'] = [v.id for v in self.process.variables] else: variables = NotebookInternalRelationsCalc2Variable.search([ ('relation.session_id', '=', self._session_id), ('relation.notebook', '=', self.process.notebook.id), ('relation.internal_relation', '=', self.process.internal_relation.id), ]) if variables: default['variables'] = [v.id for v in variables] return default def _get_variables_list(self, formula, notebook, analysis={}): pool = Pool() Analysis = pool.get('lims.analysis') NotebookLine = pool.get('lims.notebook.line') variables = {} for variable in re.findall(r'\{.*?\}', formula): var = variable.replace('{', '').replace('}', '') for prefix in ('A', 'D', 'T', 'Y', 'R'): if var.startswith(prefix): variables[var] = None for var in variables.keys(): if var[0] in ('A', 'D', 'T'): analysis_code = var[1:] with Transaction().set_user(0): notebook_lines = NotebookLine.search([ ('notebook', '=', notebook.id), ('analysis.code', '=', analysis_code), ('annulment_date', '=', None), ]) if not notebook_lines: continue for nl in notebook_lines: analysis[nl.id] = { 'line': nl.id, 'analysis': nl.analysis.id, 'repetition': nl.repetition, 'use': True if nl.repetition == 0 else False, } elif var[0] in ('R', 'Y'): analysis_code = var[1:] internal_relations = Analysis.search([ ('code', '=', analysis_code), ]) if not internal_relations: continue more_formulas = internal_relations[0].converted_result_formula more_formulas += "+" + internal_relations[0].result_formula for i in (' ', '\t', '\n', '\r'): more_formulas = more_formulas.replace(i, '') self._get_variables_list(more_formulas, notebook, analysis) return [v for v in analysis.values()] def transition_check_variables(self): variables = {} for var in self.process.variables: analysis_code = var.analysis.code if analysis_code not in variables: variables[analysis_code] = False if var.use: if variables[analysis_code]: variables[analysis_code] = False else: variables[analysis_code] = True var.save() for var in variables.values(): if not var: return 'process' return 'next_' def transition_confirm(self): pool = Pool() Date = pool.get('ir.date') NotebookInternalRelationsCalc2Relation = pool.get( 'lims.notebook.internal_relations_calc_2.relation') NotebookLine = pool.get('lims.notebook.line') date = Date.today() relations = NotebookInternalRelationsCalc2Relation.search([ ('session_id', '=', self._session_id), ]) notebook_lines_to_save = [] for relation in relations: notebook_lines = NotebookLine.search([ ('notebook', '=', relation.notebook.id), ('analysis', '=', relation.internal_relation.id) ]) if len(notebook_lines) != 1: continue analysis_code = relation.internal_relation.code result = self._get_relation_result(analysis_code, relation.notebook, analysis_code, round_=True) converted_result = self._get_relation_result(analysis_code, relation.notebook, analysis_code, converted=True) notebook_line = notebook_lines[0] if result is not None: notebook_line.result = str(result) if converted_result is not None: notebook_line.converted_result = str(converted_result) if result is not None or converted_result is not None: notebook_line.start_date = date notebook_line.end_date = date if notebook_line.laboratory.automatic_accept_result: notebook_line.accepted = True notebook_line.acceptance_date = datetime.now() notebook_lines_to_save.append(notebook_line) NotebookLine.save(notebook_lines_to_save) return 'end' def _get_analysis_result(self, analysis_code, notebook, relation_code, converted=False): NotebookInternalRelationsCalc2Variable = Pool().get( 'lims.notebook.internal_relations_calc_2.variable') variables = NotebookInternalRelationsCalc2Variable.search([ ('relation.session_id', '=', self._session_id), ('relation.notebook', '=', notebook.id), ('relation.internal_relation.code', '=', relation_code), ('analysis.code', '=', analysis_code), ('use', '=', True), ]) if not variables: return None notebook_line = variables[0].line if not notebook_line: return None try: if converted: res = float(notebook_line.converted_result) else: res = float(notebook_line.result) except (TypeError, ValueError): return None return round(res, notebook_line.decimals) def _get_relation_result(self, analysis_code, notebook, relation_code, converted=False, round_=False): pool = Pool() Analysis = pool.get('lims.analysis') NotebookLine = pool.get('lims.notebook.line') internal_relations = Analysis.search([ ('code', '=', analysis_code), ]) if not internal_relations: return None if converted: formula = internal_relations[0].converted_result_formula else: formula = internal_relations[0].result_formula if not formula: return None for i in (' ', '\t', '\n', '\r'): formula = formula.replace(i, '') variables = self._get_variables(formula, notebook, relation_code, converted) if not variables: return None parser = FormulaParser(formula, variables) value = parser.getValue() if int(value) == value: res = int(value) else: epsilon = 0.0000000001 if int(value + epsilon) != int(value): res = int(value + epsilon) elif int(value - epsilon) != int(value): res = int(value) else: res = float(value) if not round_: return res with Transaction().set_user(0): notebook_lines = NotebookLine.search([ ('notebook', '=', notebook.id), ('analysis.code', '=', analysis_code), ('repetition', '=', 0), ('annulment_date', '=', None), ]) if not notebook_lines: return None return round(res, notebook_lines[0].decimals) def _get_variables(self, formula, notebook, relation_code, converted=False): pool = Pool() VolumeConversion = pool.get('lims.volume.conversion') variables = {} for variable in re.findall(r'\{.*?\}', formula): var = variable.replace('{', '').replace('}', '') for prefix in ('A', 'D', 'T', 'Y', 'R'): if var.startswith(prefix): variables[var] = None for var in variables.keys(): if var[0] == 'A': analysis_code = var[1:] result = self._get_analysis_result(analysis_code, notebook, relation_code, converted) if result is not None: variables[var] = result elif var[0] == 'D': analysis_code = var[1:] result = self._get_analysis_result(analysis_code, notebook, relation_code, converted) if result is not None: result = VolumeConversion.brixToDensity(result) if result is not None: variables[var] = result elif var[0] == 'T': analysis_code = var[1:] result = self._get_analysis_result(analysis_code, notebook, relation_code, converted) if result is not None: result = VolumeConversion.brixToSolubleSolids(result) if result is not None: variables[var] = result elif var[0] == 'R': analysis_code = var[1:] result = self._get_relation_result(analysis_code, notebook, relation_code, converted, round_=True) if result is not None: result = VolumeConversion.brixToSolubleSolids(result) if result is not None: variables[var] = result elif var[0] == 'Y': analysis_code = var[1:] result = self._get_relation_result(analysis_code, notebook, relation_code, converted, round_=True) if result is not None: result = VolumeConversion.brixToDensity(result) if result is not None: variables[var] = result for var in variables.values(): if var is None: return None return variables def end(self): return 'reload' class NotebookLineInternalRelationsCalc2(NotebookInternalRelationsCalc2): 'Internal Relations Calculation' __name__ = 'lims.notebook_line.internal_relations_calc_2' def transition_search(self): NotebookLine = Pool().get('lims.notebook.line') notebook_lines = NotebookLine.browse( Transaction().context['active_ids']) if not notebook_lines: return 'end' if self.get_relations(notebook_lines): return 'next_' return 'end' class NotebookLoadResultsFormulaStart(ModelView): 'Load Results by Formula' __name__ = 'lims.notebook.load_results_formula.start' analysis = fields.Many2One('lims.analysis', 'Analysis', domain=[('state', '=', 'active'), ('formula', '!=', None)]) method = fields.Many2One('lims.lab.method', 'Method') start_date = fields.Date('Start date', required=True) class NotebookLoadResultsFormulaEmpty(ModelView): 'Load Results by Formula' __name__ = 'lims.notebook.load_results_formula.empty' class NotebookLoadResultsFormulaResult(ModelView): 'Load Results by Formula' __name__ = 'lims.notebook.load_results_formula.result' lines = fields.Many2Many('lims.notebook.load_results_formula.line', None, None, 'Lines') total = fields.Integer('Total') index = fields.Integer('Index') class NotebookLoadResultsFormulaLine(ModelSQL, ModelView): 'Load Results by Formula' __name__ = 'lims.notebook.load_results_formula.line' index = fields.Integer('Index') line = fields.Many2One('lims.notebook.line', 'Line') session_id = fields.Integer('Session ID') @classmethod def __register__(cls, module_name): super().__register__(module_name) cursor = Transaction().connection.cursor() cursor.execute('DELETE FROM "' + cls._table + '"') class NotebookLoadResultsFormulaAction(ModelSQL): 'Load Results by Formula' __name__ = 'lims.notebook.load_results_formula.action' line = fields.Many2One('lims.notebook.line', 'Line') result = fields.Char('Result') result_modifier = fields.Many2One('lims.result_modifier', 'Result modifier') end_date = fields.Date('End date') professional = fields.Many2One('lims.laboratory.professional', 'Laboratory professional') chromatogram = fields.Char('Chromatogram') initial_concentration = fields.Char('Initial concentration') comments = fields.Text('Comments') formula = fields.Many2One('lims.formula', 'Formula') variables = fields.One2Many('lims.notebook.load_results_formula.variable', 'action', 'Variables') session_id = fields.Integer('Session ID') @classmethod def __register__(cls, module_name): cursor = Transaction().connection.cursor() table_h = cls.__table_handler__(module_name) migrate_result_modifier = ( table_h.column_exist('result_modifier') and table_h.column_is_type('result_modifier', 'VARCHAR')) if migrate_result_modifier: table_h.column_rename('result_modifier', 'temp_result_modifier') super().__register__(module_name) if migrate_result_modifier: table_h.drop_column('temp_result_modifier') cursor.execute('DELETE FROM "' + cls._table + '"') class NotebookLoadResultsFormulaProcess(ModelView): 'Load Results by Formula' __name__ = 'lims.notebook.load_results_formula.process' line = fields.Many2One('lims.notebook.line', 'Line', readonly=True) repetition = fields.Integer('Repetition', readonly=True) fraction = fields.Many2One('lims.fraction', 'Fraction', readonly=True) product_type = fields.Many2One('lims.product.type', 'Product type', readonly=True) matrix = fields.Many2One('lims.matrix', 'Matrix', readonly=True) formula = fields.Many2One('lims.formula', 'Formula', readonly=True) formula_formula = fields.Function(fields.Char('Formula'), 'on_change_with_formula_formula') variables = fields.One2Many('lims.notebook.load_results_formula.variable', None, 'Variables') result = fields.Char('Result', required=True) result_modifier = fields.Many2One('lims.result_modifier', 'Result modifier') end_date = fields.Date('End date') end_date_copy = fields.Boolean('Field copy') professional = fields.Many2One('lims.laboratory.professional', 'Laboratory professional', required=True, readonly=True) chromatogram = fields.Char('Chromatogram') chromatogram_copy = fields.Boolean('Field copy') initial_concentration = fields.Char('Initial concentration') initial_concentration_copy = fields.Boolean('Field copy') comments = fields.Text('Comments') comments_copy = fields.Boolean('Field copy') @fields.depends('formula', 'variables', '_parent_formula.formula') def on_change_with_result(self, name=None): if not self.formula or not self.variables: return None formula = self.formula.formula variables = {} for variable in self.variables: if not variable.value: return '' variables[variable.number] = variable.value parser = FormulaParser(formula, variables) value = parser.getValue() return str(value) @fields.depends('formula', '_parent_formula.formula') def on_change_with_formula_formula(self, name=None): if self.formula: formula = self.formula.formula variables = {} for variable in self.variables: variables[variable.number] = variable.description for k, v in variables.items(): formula = formula.replace(k, v) return formula return '' class NotebookLoadResultsFormulaVariable(ModelSQL, ModelView): 'Formula Variable' __name__ = 'lims.notebook.load_results_formula.variable' action = fields.Many2One('lims.notebook.load_results_formula.action', 'Action', ondelete='CASCADE') number = fields.Char('Number', readonly=True) description = fields.Char('Description', readonly=True) value = fields.Char('Value') class NotebookLoadResultsFormulaBeginning(ModelView): 'Load Results by Formula' __name__ = 'lims.notebook.load_results_formula.beginning' class NotebookLoadResultsFormulaConfirm(ModelView): 'Load Results by Formula' __name__ = 'lims.notebook.load_results_formula.confirm' class NotebookLoadResultsFormulaSit1(ModelView): 'Professionals Control' __name__ = 'lims.notebook.load_results_formula.sit1' msg = fields.Text('Message') class NotebookLoadResultsFormulaSit2(ModelView): 'Professionals Control' __name__ = 'lims.notebook.load_results_formula.sit2' details = fields.One2Many('lims.notebook.load_results_formula.sit2.detail', None, 'Supervisors') class NotebookLoadResultsFormulaSit2Detail(ModelSQL, ModelView): 'Supervisor' __name__ = 'lims.notebook.load_results_formula.sit2.detail' _table = 'lims_notebook_load_results_formula_s2_detail' professional = fields.Many2One('lims.laboratory.professional', 'Professional', readonly=True) method = fields.Many2One('lims.lab.method', 'Method', readonly=True) supervisor = fields.Many2One('lims.laboratory.professional', 'Supervisor', domain=[('id', 'in', Eval('supervisor_domain'))]) supervisor_domain = fields.Function(fields.Many2Many( 'lims.laboratory.professional', None, None, 'Supervisor domain'), 'get_supervisor_domain') lines = fields.Many2Many( 'lims.notebook.load_results_formula.sit2.detail.line', 'load_results', 'notebook_line', 'Lines') session_id = fields.Integer('Session ID') @classmethod def __register__(cls, module_name): super().__register__(module_name) cursor = Transaction().connection.cursor() cursor.execute('DELETE FROM "' + cls._table + '"') def get_supervisor_domain(self, name=None): LabProfessionalMethod = Pool().get('lims.lab.professional.method') res = [] qualifications = LabProfessionalMethod.search([ ('method', '=', self.method.id), ('type', '=', 'analytical'), ('state', 'in', ('qualified', 'requalified')), ]) if qualifications: res = [q.professional.id for q in qualifications] return res class NotebookLoadResultsFormulaSit2DetailLine(ModelSQL): 'Notebook Line' __name__ = 'lims.notebook.load_results_formula.sit2.detail.line' _table = 'lims_notebook_load_results_formula_sit2_d_l' load_results = fields.Many2One( 'lims.notebook.load_results_formula.sit2.detail', 'Load Results', ondelete='CASCADE', required=True) notebook_line = fields.Many2One('lims.notebook.line', 'Notebook Line', ondelete='CASCADE', required=True) class NotebookLoadResultsFormula(Wizard): 'Load Results by Formula' __name__ = 'lims.notebook.load_results_formula' start = StateView('lims.notebook.load_results_formula.start', 'lims.lims_notebook_load_results_formula_start_view_form', [ Button('Cancel', 'end', 'tryton-cancel'), Button('Search', 'search', 'tryton-forward', default=True), ]) search = StateTransition() empty = StateView('lims.notebook.load_results_formula.empty', 'lims.lims_notebook_load_results_formula_empty_view_form', [ Button('Cancel', 'end', 'tryton-cancel'), Button('Search again', 'start', 'tryton-forward', default=True), ]) result = StateView('lims.notebook.load_results_formula.result', 'lims.lims_notebook_load_results_formula_result_view_form', []) next_ = StateTransition() prev_ = StateTransition() process = StateView('lims.notebook.load_results_formula.process', 'lims.lims_notebook_load_results_formula_process_view_form', [ Button('Cancel', 'end', 'tryton-cancel'), Button('Previous', 'prev_', 'tryton-back'), Button('Next', 'next_', 'ttryton-forward', default=True), ]) beginning = StateView('lims.notebook.load_results_formula.beginning', 'lims.lims_notebook_load_results_formula_beginning_view_form', [ Button('Cancel', 'end', 'tryton-cancel'), Button('Next', 'next_', 'tryton-forward', default=True), ]) confirm = StateView('lims.notebook.load_results_formula.confirm', 'lims.lims_notebook_load_results_formula_confirm_view_form', [ Button('Cancel', 'end', 'tryton-cancel'), Button('Previous', 'prev_', 'tryton-back'), Button('Confirm', 'check_professional', 'tryton-ok', default=True), ]) check_professional = StateTransition() sit1 = StateView('lims.notebook.load_results_formula.sit1', 'lims.lims_notebook_load_results_formula_sit1_view_form', [ Button('Cancel', 'end', 'tryton-cancel', default=True), ]) sit2 = StateView('lims.notebook.load_results_formula.sit2', 'lims.lims_notebook_load_results_formula_sit2_view_form', [ Button('Cancel', 'end', 'tryton-cancel'), Button('Confirm', 'sit2_ok', 'tryton-ok', default=True), ]) sit2_ok = StateTransition() confirm_ = StateTransition() def transition_search(self): pool = Pool() NotebookLine = pool.get('lims.notebook.line') NotebookLoadResultsFormulaLine = pool.get( 'lims.notebook.load_results_formula.line') clause = [ ('start_date', '=', self.start.start_date), ('end_date', '=', None), ('analysis.formula', '!=', None), ] if self.start.analysis: clause.append(('analysis', '=', self.start.analysis.id)) if self.start.method: clause.append(('method', '=', self.start.method.id)) lines = NotebookLine.search(clause, order=[ ('analysis_order', 'ASC'), ('id', 'ASC')]) if lines: res_lines = [] count = 1 for line in lines: res_line, = NotebookLoadResultsFormulaLine.create([{ 'session_id': self._session_id, 'index': count, 'line': line.id, }]) res_lines.append(res_line.id) count += 1 self.result.lines = res_lines self.result.total = len(res_lines) self.result.index = 0 return 'next_' return 'empty' def transition_next_(self): pool = Pool() NotebookLoadResultsFormulaAction = pool.get( 'lims.notebook.load_results_formula.action') NotebookLoadResultsFormulaLine = pool.get( 'lims.notebook.load_results_formula.line') LaboratoryProfessional = pool.get('lims.laboratory.professional') has_prev = (hasattr(self.process, 'line') and getattr(self.process, 'line')) if has_prev: defaults = { 'session_id': self._session_id, 'line': self.process.line.id, 'result': self.process.result, 'result_modifier': (self.process.result_modifier.id if self.process.result_modifier else None), 'end_date': self.process.end_date, 'professional': self.process.professional.id, 'chromatogram': self.process.chromatogram, 'initial_concentration': self.process.initial_concentration, 'comments': self.process.comments, 'formula': (self.process.formula.id if self.process.formula else None), } variables = [] for var in self.process.variables: variables.append({ 'number': var.number, 'description': var.description, 'value': var.value, }) defaults['variables'] = [('create', variables)] action = NotebookLoadResultsFormulaAction.search([ ('session_id', '=', self._session_id), ('line', '=', self.process.line.id), ]) if action: defaults['variables'] = [( 'delete', [v.id for a in action for v in a.variables], )] + defaults['variables'] NotebookLoadResultsFormulaAction.write(action, defaults) else: NotebookLoadResultsFormulaAction.create([defaults]) self.result.index += 1 if self.result.index <= self.result.total: line = NotebookLoadResultsFormulaLine.search([ ('session_id', '=', self._session_id), ('index', '=', self.result.index), ]) self.process.line = line[0].line.id action = NotebookLoadResultsFormulaAction.search([ ('session_id', '=', self._session_id), ('line', '=', line[0].line.id), ]) if action: self.process.result = action[0].result self.process.result_modifier = (action[0].result_modifier.id if action[0].result_modifier else None) self.process.end_date = action[0].end_date self.process.professional = action[0].professional.id self.process.chromatogram = action[0].chromatogram self.process.initial_concentration = ( action[0].initial_concentration) self.process.comments = action[0].comments self.process.formula = action[0].formula self.process.variables = [v.id for v in action[0].variables] elif has_prev: self.process.result = None self.process.result_modifier = None if not self.process.end_date_copy: self.process.end_date = None if not self.process.chromatogram_copy: self.process.chromatogram = None if not self.process.initial_concentration_copy: self.process.initial_concentration = None if not self.process.comments_copy: self.process.comments = None self.process.formula = None self.process.variables = None else: professional_id = ( LaboratoryProfessional.get_lab_professional()) self.process.professional = professional_id return 'process' return 'confirm' def transition_prev_(self): pool = Pool() NotebookLoadResultsFormulaAction = pool.get( 'lims.notebook.load_results_formula.action') NotebookLoadResultsFormulaLine = pool.get( 'lims.notebook.load_results_formula.line') self.result.index -= 1 if self.result.index >= 1: line = NotebookLoadResultsFormulaLine.search([ ('session_id', '=', self._session_id), ('index', '=', self.result.index), ]) self.process.line = line[0].line.id action = NotebookLoadResultsFormulaAction.search([ ('session_id', '=', self._session_id), ('line', '=', line[0].line.id), ]) if action: self.process.result = action[0].result self.process.result_modifier = (action[0].result_modifier.id if action[0].result_modifier else None) self.process.end_date = action[0].end_date self.process.professional = action[0].professional.id self.process.chromatogram = action[0].chromatogram self.process.initial_concentration = ( action[0].initial_concentration) self.process.comments = action[0].comments self.process.formula = action[0].formula self.process.variables = [v.id for v in action[0].variables] else: self.process.result = None self.process.result_modifier = None self.process.end_date = None self.process.professional = None self.process.chromatogram = None self.process.initial_concentration = None self.process.comments = None self.process.formula = None self.process.variables = None return 'process' self.process.line = None return 'beginning' def default_process(self, fields): if not self.process.line: return {} default = {} default['line'] = self.process.line.id default['fraction'] = self.process.line.notebook.fraction.id default['repetition'] = self.process.line.repetition default['product_type'] = ( self.process.line.notebook.product_type.id) default['matrix'] = self.process.line.notebook.matrix.id if (hasattr(self.process, 'formula') and getattr(self.process, 'formula')): formula = self.process.formula default['formula'] = formula.id variables = [] variables_desc = {} for var in self.process.variables: variables.append({ 'number': var.number, 'description': var.description, 'value': var.value, }) variables_desc[var.number] = var.description default['variables'] = variables formula_formula = formula.formula for k, v in variables_desc.items(): formula_formula = formula_formula.replace(k, v) default['formula_formula'] = formula_formula default['result'] = self.process.result default['result_modifier'] = (self.process.result_modifier.id if self.process.result_modifier else None) default['initial_concentration'] = ( self.process.initial_concentration) default['comments'] = self.process.comments default['professional'] = self.process.professional.id default['end_date'] = self.process.end_date default['chromatogram'] = self.process.chromatogram else: formula = self.process.line.analysis.formula if formula: default['formula'] = formula.id variables = [] variables_desc = {} for var in formula.variables: variables.append({ 'number': var.number, 'description': var.description, 'value': var.constant, }) variables_desc[var.number] = var.description default['variables'] = variables formula_formula = formula.formula for k, v in variables_desc.items(): formula_formula = formula_formula.replace(k, v) default['formula_formula'] = formula_formula default['result_modifier'] = None for field in ('initial_concentration', 'comments'): if (hasattr(self.process, field + '_copy') and getattr(self.process, field + '_copy')): default[field] = getattr(self.process, field) default[field + '_copy'] = getattr(self.process, field + '_copy') else: default[field] = getattr(self.process.line, field) for field in ('professional',): if (hasattr(self.process, field) and getattr(self.process, field)): default[field] = getattr(self.process, field).id for field in ('end_date', 'chromatogram'): if (hasattr(self.process, field) and getattr(self.process, field)): default[field] = getattr(self.process, field) for field in ('end_date_copy', 'chromatogram_copy'): if (hasattr(self.process, field) and getattr(self.process, field)): default[field] = getattr(self.process, field) return default def transition_check_professional(self): pool = Pool() NotebookLoadResultsFormulaAction = pool.get( 'lims.notebook.load_results_formula.action') LabProfessionalMethod = pool.get('lims.lab.professional.method') LaboratoryProfessional = pool.get('lims.laboratory.professional') LabMethod = pool.get('lims.lab.method') NotebookLoadResultsFormulaSit2Detail = pool.get( 'lims.notebook.load_results_formula.sit2.detail') actions = NotebookLoadResultsFormulaAction.search([ ('session_id', '=', self._session_id), ]) situations = {} prof_lines = {} for data in actions: key = (data.professional.id, data.line.method.id) if key not in situations: situations[key] = 0 if key not in prof_lines: prof_lines[key] = [] prof_lines[key].append(data.line.id) situation_1 = [] for key in situations.keys(): qualifications = LabProfessionalMethod.search([ ('professional', '=', key[0]), ('method', '=', key[1]), ('type', '=', 'analytical'), ]) if not qualifications: situations[key] = 1 situation_1.append(key) elif qualifications[0].state == 'training': situations[key] = 2 elif (qualifications[0].state in ('qualified', 'requalified')): situations[key] = 3 if situation_1: msg = '' for key in situation_1: professional = LaboratoryProfessional(key[0]) method = LabMethod(key[1]) msg += '%s: %s\n' % (professional.rec_name, method.code) self.sit1.msg = msg return 'sit1' situation_2 = [] for key, sit in situations.items(): if sit == 2: situation_2.append({ 'session_id': self._session_id, 'professional': key[0], 'method': key[1], 'lines': [('add', prof_lines[key])], }) if situation_2: details = NotebookLoadResultsFormulaSit2Detail.create( situation_2) self.sit2.details = details return 'sit2' return 'confirm_' def default_sit1(self, fields): defaults = {} if self.sit1.msg: defaults['msg'] = self.sit1.msg return defaults def default_sit2(self, fields): defaults = {} if self.sit2.details: defaults['details'] = [d.id for d in self.sit2.details] return defaults def transition_sit2_ok(self): for detail in self.sit2.details: if not detail.supervisor: return 'sit2' return 'confirm_' def transition_confirm_(self): pool = Pool() NotebookLoadResultsFormulaAction = pool.get( 'lims.notebook.load_results_formula.action') NotebookLine = pool.get('lims.notebook.line') LabProfessionalMethod = pool.get('lims.lab.professional.method') LabProfessionalMethodRequalification = pool.get( 'lims.lab.professional.method.requalification') Date = pool.get('ir.date') # Write Results to Notebook lines actions = NotebookLoadResultsFormulaAction.search([ ('session_id', '=', self._session_id), ]) for data in actions: notebook_line = NotebookLine(data.line.id) if not notebook_line: continue notebook_line_write = { 'result': data.result, 'result_modifier': (data.result_modifier.id if data.result_modifier else None), 'end_date': data.end_date, 'chromatogram': data.chromatogram, 'initial_concentration': data.initial_concentration, 'comments': data.comments, 'converted_result': None, 'converted_result_modifier': None, 'backup': None, 'verification': None, 'uncertainty': None, } if data.result_modifier and data.result_modifier.code == 'na': notebook_line_write['annulled'] = True notebook_line_write['annulment_date'] = datetime.now() notebook_line_write['report'] = False professionals = [{'professional': data.professional.id}] notebook_line_write['professionals'] = ( [('delete', [p.id for p in notebook_line.professionals])] + [('create', professionals)]) NotebookLine.write([notebook_line], notebook_line_write) # Write Supervisors to Notebook lines supervisor_lines = {} if hasattr(self.sit2, 'details'): for detail in self.sit2.details: if detail.supervisor.id not in supervisor_lines: supervisor_lines[detail.supervisor.id] = [] supervisor_lines[detail.supervisor.id].extend([ l.id for l in detail.lines]) for prof_id, lines in supervisor_lines.items(): notebook_lines = NotebookLine.search([ ('id', 'in', lines), ]) if notebook_lines: professionals = [{'professional': prof_id}] notebook_line_write = { 'professionals': [('create', professionals)], } NotebookLine.write(notebook_lines, notebook_line_write) # Write the execution of method all_prof = {} for data in actions: key = (data.professional.id, data.line.method.id) if key not in all_prof: all_prof[key] = [] if hasattr(self.sit2, 'details'): for detail in self.sit2.details: key = (detail.supervisor.id, detail.method.id) if key not in all_prof: all_prof[key] = [] key = (detail.professional.id, detail.method.id) if detail.supervisor.id not in all_prof[key]: all_prof[key].append(detail.supervisor.id) today = Date.today() for key, sup in all_prof.items(): professional_method, = LabProfessionalMethod.search([ ('professional', '=', key[0]), ('method', '=', key[1]), ('type', '=', 'analytical'), ]) if professional_method.state == 'training': history = LabProfessionalMethodRequalification.search([ ('professional_method', '=', professional_method.id), ('type', '=', 'training'), ]) if history: prev_supervisors = [s.supervisor.id for s in history[0].supervisors] supervisors = [{'supervisor': s} for s in sup if s not in prev_supervisors] LabProfessionalMethodRequalification.write(history, { 'last_execution_date': today, 'supervisors': [('create', supervisors)], }) else: supervisors = [{'supervisor': s} for s in sup] to_create = [{ 'professional_method': professional_method.id, 'type': 'training', 'date': today, 'last_execution_date': today, 'supervisors': [('create', supervisors)], }] LabProfessionalMethodRequalification.create(to_create) elif professional_method.state == 'qualified': history = LabProfessionalMethodRequalification.search([ ('professional_method', '=', professional_method.id), ('type', '=', 'qualification'), ]) if history: LabProfessionalMethodRequalification.write(history, { 'last_execution_date': today, }) else: to_create = [{ 'professional_method': professional_method.id, 'type': 'qualification', 'date': today, 'last_execution_date': today, }] LabProfessionalMethodRequalification.create(to_create) else: history = LabProfessionalMethodRequalification.search([ ('professional_method', '=', professional_method.id), ('type', '=', 'requalification'), ]) if history: LabProfessionalMethodRequalification.write(history, { 'last_execution_date': today, }) else: to_create = [{ 'professional_method': professional_method.id, 'type': 'requalification', 'date': today, 'last_execution_date': today, }] LabProfessionalMethodRequalification.create(to_create) return 'end' class NotebookLoadResultsManualStart(ModelView): 'Load Results Manually' __name__ = 'lims.notebook.load_results_manual.start' method = fields.Many2One('lims.lab.method', 'Method', required=True) start_date = fields.Date('Start date', required=True) class NotebookLoadResultsManualEmpty(ModelView): 'Load Results Manually' __name__ = 'lims.notebook.load_results_manual.empty' class NotebookLoadResultsManualResult(ModelView): 'Load Results Manually' __name__ = 'lims.notebook.load_results_manual.result' method = fields.Many2One('lims.lab.method', 'Method', readonly=True) start_date = fields.Date('Start date', readonly=True) professional = fields.Many2One('lims.laboratory.professional', 'Laboratory professional', required=True) lines = fields.One2Many('lims.notebook.load_results_manual.line', None, 'Lines') class NotebookLoadResultsManualLine(ModelSQL, ModelView): 'Load Results Manually' __name__ = 'lims.notebook.load_results_manual.line' line = fields.Many2One('lims.notebook.line', 'Analysis', readonly=True, ondelete='CASCADE') repetition = fields.Integer('Repetition', readonly=True) fraction = fields.Many2One('lims.fraction', 'Fraction', readonly=True) result = fields.Char('Result') result_modifier = fields.Many2One('lims.result_modifier', 'Result modifier') end_date = fields.Date('End date') chromatogram = fields.Char('Chromatogram') initial_unit = fields.Many2One('product.uom', 'Initial unit', domain=[('category.lims_only_available', '=', True)]) comments = fields.Text('Comments') literal_result = fields.Char('Literal result') fraction_type = fields.Many2One('lims.fraction.type', 'Fraction type', readonly=True) device = fields.Many2One('lims.lab.device', 'Device', domain=[('id', 'in', Eval('device_domain'))]) device_domain = fields.Function(fields.Many2Many('lims.lab.device', None, None, 'Device domain'), 'on_change_with_device_domain') uncertainty = fields.Char('Uncertainty') session_id = fields.Integer('Session ID') @classmethod def __register__(cls, module_name): cursor = Transaction().connection.cursor() table_h = cls.__table_handler__(module_name) migrate_result_modifier = ( table_h.column_exist('result_modifier') and table_h.column_is_type('result_modifier', 'VARCHAR')) if migrate_result_modifier: table_h.column_rename('result_modifier', 'temp_result_modifier') super().__register__(module_name) if migrate_result_modifier: table_h.drop_column('temp_result_modifier') cursor.execute('DELETE FROM "' + cls._table + '"') @fields.depends('result', 'literal_result', 'result_modifier', 'end_date') def on_change_with_end_date(self): pool = Pool() Date = pool.get('ir.date') if self.end_date: return self.end_date if (self.result or self.literal_result or (self.result_modifier and self.result_modifier.code != 'low')): return Date.today() return None @fields.depends('line', '_parent_line.analysis', '_parent_line.laboratory') def on_change_with_device_domain(self, name=None): cursor = Transaction().connection.cursor() AnalysisDevice = Pool().get('lims.analysis.device') if not self.line: return [] cursor.execute('SELECT DISTINCT(device) ' 'FROM "' + AnalysisDevice._table + '" ' 'WHERE active IS TRUE ' 'AND analysis = %s ' 'AND laboratory = %s', (self.line.analysis.id, self.line.laboratory.id)) res = cursor.fetchall() if not res: return [] return [x[0] for x in res] class NotebookLoadResultsManualSit1(ModelView): 'Professionals Control' __name__ = 'lims.notebook.load_results_manual.sit1' msg = fields.Text('Message') class NotebookLoadResultsManualSit2(ModelView): 'Professionals Control' __name__ = 'lims.notebook.load_results_manual.sit2' supervisor = fields.Many2One('lims.laboratory.professional', 'Supervisor', required=True, domain=[('id', 'in', Eval('supervisor_domain'))]) supervisor_domain = fields.Many2Many('lims.laboratory.professional', None, None, 'Supervisor domain') lines = fields.Many2Many('lims.notebook.line', None, None, 'Lines') class NotebookLoadResultsManual(Wizard): 'Load Results Manually' __name__ = 'lims.notebook.load_results_manual' start = StateView('lims.notebook.load_results_manual.start', 'lims.lims_notebook_load_results_manual_start_view_form', [ Button('Cancel', 'end', 'tryton-cancel'), Button('Search', 'search', 'tryton-forward', default=True), ]) search = StateTransition() empty = StateView('lims.notebook.load_results_manual.empty', 'lims.lims_notebook_load_results_manual_empty_view_form', [ Button('Cancel', 'end', 'tryton-cancel'), Button('Search again', 'start', 'tryton-forward', default=True), ]) result = StateView('lims.notebook.load_results_manual.result', 'lims.lims_notebook_load_results_manual_result_view_form', [ Button('Cancel', 'end', 'tryton-cancel'), Button('Confirm', 'check_professional', 'tryton-ok', default=True), ]) check_professional = StateTransition() sit1 = StateView('lims.notebook.load_results_manual.sit1', 'lims.lims_notebook_load_results_manual_sit1_view_form', [ Button('Cancel', 'end', 'tryton-cancel', default=True), ]) sit2 = StateView('lims.notebook.load_results_manual.sit2', 'lims.lims_notebook_load_results_manual_sit2_view_form', [ Button('Cancel', 'end', 'tryton-cancel'), Button('Confirm', 'confirm_', 'tryton-ok', default=True), ]) confirm_ = StateTransition() def transition_search(self): pool = Pool() NotebookLine = pool.get('lims.notebook.line') LaboratoryProfessional = pool.get('lims.laboratory.professional') NotebookLoadResultsManualLine = pool.get( 'lims.notebook.load_results_manual.line') clause = [ ('end_date', '=', None), ('method', '=', self.start.method.id), ('start_date', '=', self.start.start_date), ] with Transaction().set_context(_check_access=True): lines = NotebookLine.search(clause, order=[('analysis_order', 'ASC'), ('id', 'ASC')]) if lines: res_lines = [] for line in lines: res_line, = NotebookLoadResultsManualLine.create([{ 'session_id': self._session_id, 'line': line.id, 'repetition': line.repetition, 'result': line.result, 'result_modifier': (line.result_modifier.id if line.result_modifier else None), 'end_date': line.end_date, 'chromatogram': line.chromatogram, 'initial_unit': (line.initial_unit.id if line.initial_unit else None), 'comments': line.comments, 'fraction': line.fraction.id, 'fraction_type': line.fraction_type.id, 'literal_result': line.literal_result, 'device': (line.device.id if line.device else None), 'uncertainty': line.uncertainty, }]) res_lines.append(res_line.id) professional_id = LaboratoryProfessional.get_lab_professional() self.result.method = self.start.method.id self.result.start_date = self.start.start_date self.result.professional = professional_id self.result.lines = res_lines return 'result' return 'empty' def default_result(self, fields): default = {} default['method'] = self.result.method.id default['start_date'] = self.result.start_date default['professional'] = (self.result.professional.id if self.result.professional else None) default['lines'] = [l.id for l in self.result.lines] return default def transition_check_professional(self): pool = Pool() NotebookLoadResultsManualLine = pool.get( 'lims.notebook.load_results_manual.line') LabProfessionalMethod = pool.get('lims.lab.professional.method') lines_to_save = [] for line in self.result.lines: if not line.line: # Avoid empty lines created with ENTER key continue lines_to_save.append(line) NotebookLoadResultsManualLine.save(lines_to_save) professional = self.result.professional method = self.result.method qualifications = LabProfessionalMethod.search([ ('professional', '=', professional.id), ('method', '=', method.id), ('type', '=', 'analytical'), ]) if not qualifications: msg = '%s: %s' % (professional.rec_name, method.code) self.sit1.msg = msg return 'sit1' elif qualifications[0].state == 'training': situation_2_lines = [l.line.id for l in lines_to_save] supervisor_domain = [] qualifications = LabProfessionalMethod.search([ ('method', '=', method.id), ('type', '=', 'analytical'), ('state', 'in', ('qualified', 'requalified')), ]) if qualifications: supervisor_domain = [q.professional.id for q in qualifications] self.sit2.lines = situation_2_lines self.sit2.supervisor_domain = supervisor_domain return 'sit2' return 'confirm_' def default_sit1(self, fields): defaults = {} if self.sit1.msg: defaults['msg'] = self.sit1.msg return defaults def default_sit2(self, fields): defaults = {} if self.sit2.supervisor_domain: defaults['supervisor_domain'] = [p.id for p in self.sit2.supervisor_domain] if self.sit2.lines: defaults['lines'] = [l.id for l in self.sit2.lines] return defaults def transition_confirm_(self): pool = Pool() NotebookLoadResultsManualLine = pool.get( 'lims.notebook.load_results_manual.line') NotebookLine = pool.get('lims.notebook.line') LabProfessionalMethod = pool.get('lims.lab.professional.method') LabProfessionalMethodRequalification = pool.get( 'lims.lab.professional.method.requalification') Date = pool.get('ir.date') # Write Results to Notebook lines actions = NotebookLoadResultsManualLine.search([ ('session_id', '=', self._session_id), ]) for data in actions: notebook_line = NotebookLine(data.line.id) if not notebook_line: continue notebook_line_write = { 'result': data.result, 'result_modifier': (data.result_modifier.id if data.result_modifier else None), 'chromatogram': data.chromatogram, 'initial_unit': (data.initial_unit.id if data.initial_unit else None), 'comments': data.comments, 'literal_result': data.literal_result, 'converted_result': None, 'converted_result_modifier': None, 'backup': None, 'verification': None, 'uncertainty': data.uncertainty, 'device': (data.device.id if data.device else None), } if (not (not data.result_modifier and not data.result) or data.literal_result): notebook_line_write['end_date'] = data.end_date if data.result_modifier and data.result_modifier.code == 'na': notebook_line_write['annulled'] = True notebook_line_write['annulment_date'] = datetime.now() notebook_line_write['report'] = False if (notebook_line_write.get('end_date') or notebook_line_write.get('annulment_date')): professionals = [{'professional': self.result.professional.id}] notebook_line_write['professionals'] = ( [('delete', [p.id for p in notebook_line.professionals])] + [('create', professionals)]) NotebookLine.write([notebook_line], notebook_line_write) # Write Supervisors to Notebook lines supervisor_lines = {} if hasattr(self.sit2, 'supervisor'): supervisor_lines[self.sit2.supervisor.id] = [ l.id for l in self.sit2.lines] for prof_id, lines in supervisor_lines.items(): notebook_lines = NotebookLine.search([ ('id', 'in', lines), ]) if notebook_lines: professionals = [{'professional': prof_id}] notebook_line_write = { 'professionals': [('create', professionals)], } NotebookLine.write(notebook_lines, notebook_line_write) # Write the execution of method all_prof = {} key = (self.result.professional.id, self.result.method.id) all_prof[key] = [] if hasattr(self.sit2, 'supervisor'): for detail in self.sit2.lines: key = (self.sit2.supervisor.id, detail.method.id) if key not in all_prof: all_prof[key] = [] key = (self.result.professional.id, detail.method.id) if self.sit2.supervisor.id not in all_prof[key]: all_prof[key].append(self.sit2.supervisor.id) today = Date.today() for key, sup in all_prof.items(): professional_method, = LabProfessionalMethod.search([ ('professional', '=', key[0]), ('method', '=', key[1]), ('type', '=', 'analytical'), ]) if professional_method.state == 'training': history = LabProfessionalMethodRequalification.search([ ('professional_method', '=', professional_method.id), ('type', '=', 'training'), ]) if history: prev_supervisors = [s.supervisor.id for s in history[0].supervisors] supervisors = [{'supervisor': s} for s in sup if s not in prev_supervisors] LabProfessionalMethodRequalification.write(history, { 'last_execution_date': today, 'supervisors': [('create', supervisors)], }) else: supervisors = [{'supervisor': s} for s in sup] to_create = [{ 'professional_method': professional_method.id, 'type': 'training', 'date': today, 'last_execution_date': today, 'supervisors': [('create', supervisors)], }] LabProfessionalMethodRequalification.create(to_create) elif professional_method.state == 'qualified': history = LabProfessionalMethodRequalification.search([ ('professional_method', '=', professional_method.id), ('type', '=', 'qualification'), ]) if history: LabProfessionalMethodRequalification.write(history, { 'last_execution_date': today, }) else: to_create = [{ 'professional_method': professional_method.id, 'type': 'qualification', 'date': today, 'last_execution_date': today, }] LabProfessionalMethodRequalification.create(to_create) else: history = LabProfessionalMethodRequalification.search([ ('professional_method', '=', professional_method.id), ('type', '=', 'requalification'), ]) if history: LabProfessionalMethodRequalification.write(history, { 'last_execution_date': today, }) else: to_create = [{ 'professional_method': professional_method.id, 'type': 'requalification', 'date': today, 'last_execution_date': today, }] LabProfessionalMethodRequalification.create(to_create) return 'end' class NotebookLoadResultsExceptionalStart(ModelView): 'Exceptional Loading of Results' __name__ = 'lims.notebook.load_results_exceptional.start' notebook = fields.Many2One('lims.notebook', 'Notebook', required=True) class NotebookLoadResultsExceptionalResult(ModelView): 'Exceptional Loading of Results' __name__ = 'lims.notebook.load_results_exceptional.result' start_date = fields.Date('Start date', required=True) end_date = fields.Date('End date', required=True) lines = fields.One2Many('lims.notebook.load_results_exceptional.line', None, 'Lines') class NotebookLoadResultsExceptionalLine(ModelSQL, ModelView): 'Exceptional Loading of Results' __name__ = 'lims.notebook.load_results_exceptional.line' line = fields.Many2One('lims.notebook.line', 'Analysis', readonly=True) result = fields.Char('Result') literal_result = fields.Char('Literal result') class NotebookLoadResultsExceptional(Wizard): 'Exceptional Loading of Results' __name__ = 'lims.notebook.load_results_exceptional' start = StateView('lims.notebook.load_results_exceptional.start', 'lims.lims_notebook_load_results_exceptional_start_view_form', [ Button('Cancel', 'end', 'tryton-cancel'), Button('Load results', 'result', 'tryton-forward', default=True), ]) result = StateView('lims.notebook.load_results_exceptional.result', 'lims.lims_notebook_load_results_exceptional_result_view_form', [ Button('Cancel', 'end', 'tryton-cancel'), Button('Confirm', 'confirm', 'tryton-ok', default=True), ]) confirm = StateTransition() def default_result(self, fields): pool = Pool() NotebookLine = pool.get('lims.notebook.line') Date = pool.get('ir.date') today = Date.today() res_lines = [] with Transaction().set_context(_check_access=True): lines = NotebookLine.search([ ('notebook', '=', self.start.notebook), ('start_date', '=', None), ], order=[('analysis_order', 'ASC'), ('id', 'ASC')]) for line in lines: res_lines.append({ 'line': line.id, 'result': None, 'literal_result': None, }) default = {} default['start_date'] = today default['end_date'] = today default['lines'] = res_lines return default def transition_confirm(self): pool = Pool() NotebookLine = pool.get('lims.notebook.line') start_date = self.result.start_date end_date = self.result.end_date lines_to_save = [] for line in self.result.lines: if not line.line: # Avoid empty lines created with ENTER key continue if not line.result and not line.literal_result: continue notebook_line = NotebookLine(line.line.id) notebook_line.result = line.result notebook_line.literal_result = line.literal_result notebook_line.start_date = start_date notebook_line.end_date = end_date notebook_line.exceptional_load = True notebook_line.exceptional_load_uid = int(Transaction().user) lines_to_save.append(notebook_line) NotebookLine.save(lines_to_save) return 'end' class NotebookAddInternalRelationsStart(ModelView): 'Add Internal Relations' __name__ = 'lims.notebook.add_internal_relations.start' analysis = fields.Many2Many('lims.analysis', None, None, 'Internal relations', required=True, domain=[('id', 'in', Eval('analysis_domain'))]) analysis_domain = fields.One2Many('lims.analysis', None, 'Internal relations domain') class NotebookAddInternalRelations(Wizard): 'Add Internal Relations' __name__ = 'lims.notebook.add_internal_relations' start = StateView('lims.notebook.add_internal_relations.start', 'lims.lims_notebook_add_internal_relations_start_view_form', [ Button('Cancel', 'end', 'tryton-cancel'), Button('Add', 'add', 'tryton-ok', default=True), ]) add = StateTransition() def default_start(self, fields): cursor = Transaction().connection.cursor() pool = Pool() Notebook = pool.get('lims.notebook') NotebookLine = pool.get('lims.notebook.line') Analysis = pool.get('lims.analysis') Typification = pool.get('lims.typification') default = { 'analysis_domain': [], } notebook = Notebook(Transaction().context['active_id']) with Transaction().set_context(_check_access=True): notebook_lines = NotebookLine.search([ ('notebook', '=', notebook.id), ]) if not notebook_lines: return default notebook_analysis = [] for notebook_line in notebook_lines: notebook_analysis.append(notebook_line.analysis.code) notebook_analysis_codes = '\', \''.join(str(a) for a in notebook_analysis) cursor.execute('SELECT DISTINCT(a.id, a.result_formula) ' 'FROM "' + Analysis._table + '" a ' 'INNER JOIN "' + Typification._table + '" t ' 'ON a.id = t.analysis ' 'WHERE t.product_type = %s ' 'AND t.matrix = %s ' 'AND t.valid ' 'AND a.behavior = \'internal_relation\' ' 'AND a.code NOT IN (\'' + notebook_analysis_codes + '\')', (notebook.fraction.product_type.id, notebook.fraction.matrix.id)) internal_relations = cursor.fetchall() if not internal_relations: return default for internal_relation in internal_relations: formula = internal_relation[0].split(',')[1][:-1] if not formula: continue for i in (' ', '\t', '\n', '\r'): formula = formula.replace(i, '') variables = self._get_variables(formula) available = True if variables: for v in variables: if v not in notebook_analysis: available = False break if available: ir_id = int(internal_relation[0].split(',')[0][1:]) default['analysis_domain'].append(ir_id) return default def _get_variables(self, formula): variables = [] for variable in re.findall(r'\{.*?\}', formula): var = variable.replace('{', '').replace('}', '') for prefix in ('A', 'D', 'T', 'Y', 'R'): if var.startswith(prefix): variables.append(var[1:]) return variables def transition_add(self): Notebook = Pool().get('lims.notebook') notebook = Notebook(Transaction().context['active_id']) for analysis in self.start.analysis: self._create_service(analysis, notebook.fraction) return 'end' def _create_service(self, analysis, fraction): cursor = Transaction().connection.cursor() pool = Pool() Typification = pool.get('lims.typification') Service = pool.get('lims.service') EntryDetailAnalysis = pool.get('lims.entry.detail.analysis') divide, report_grouper = self._get_report_grouper(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', (fraction.product_type.id, fraction.matrix.id, analysis.id)) res = cursor.fetchone() if res: laboratory_id = res[0] if not laboratory_id and analysis.laboratories: for l in analysis.laboratories: if l.by_default is True: laboratory_id = l.laboratory.id method_id = 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', (fraction.product_type.id, fraction.matrix.id, analysis.id)) res = cursor.fetchone() if res: method_id = res[0] device_id = None if analysis.devices: for d in analysis.devices: if (d.laboratory.id == laboratory_id and d.by_default is True): device_id = d.device.id service_create = [{ 'fraction': fraction.id, 'analysis': analysis.id, 'laboratory': laboratory_id, 'method': method_id, 'device': device_id, 'divide': divide, }] new_service, = Service.create(service_create) Service.set_confirmation_date([new_service]) analysis_detail = list(new_service.analysis_detail) if report_grouper != 0: EntryDetailAnalysis.write(analysis_detail, { 'report_grouper': report_grouper, }) EntryDetailAnalysis.create_notebook_lines(analysis_detail, fraction) EntryDetailAnalysis.write(analysis_detail, { 'state': 'unplanned', }) def _get_report_grouper(self, analysis): pool = Pool() Notebook = pool.get('lims.notebook') divide = False report_grouper = 0 notebook = Notebook(Transaction().context['active_id']) notebook_analysis = {} for notebook_line in notebook.lines: notebook_analysis[notebook_line.analysis.code] = notebook_line formula = analysis.result_formula for i in (' ', '\t', '\n', '\r'): formula = formula.replace(i, '') variables = self._get_variables(formula) for v in variables: if v in notebook_analysis: divide = notebook_analysis[v].service.divide report_grouper = ( notebook_analysis[v].analysis_detail.report_grouper) break return divide, report_grouper def end(self): return 'reload' class NotebookRepeatAnalysisStart(ModelView): 'Repeat Analysis' __name__ = 'lims.notebook.repeat_analysis.start' analysis = fields.Many2Many('lims.analysis', None, None, 'Analysis', required=True, domain=[('id', 'in', Eval('analysis_domain'))]) analysis_domain = fields.One2Many('lims.analysis', None, 'Analysis domain') repetition_reason = fields.Char('Reason') class NotebookRepeatAnalysis(Wizard): 'Repeat Analysis' __name__ = 'lims.notebook.repeat_analysis' start = StateView('lims.notebook.repeat_analysis.start', 'lims.lims_notebook_repeat_analysis_start_view_form', [ Button('Cancel', 'end', 'tryton-cancel'), Button('Repeat', 'repeat', 'tryton-ok', default=True), ]) repeat = StateTransition() @classmethod def check_access(cls): pass def default_start(self, fields): pool = Pool() NotebookLine = pool.get('lims.notebook.line') Analysis = pool.get('lims.analysis') analysis_domain = set() first = True for notebook_id in Transaction().context['active_ids']: analysis_origin_list = [] with Transaction().set_context(_check_access=True): notebook_lines = NotebookLine.search([ ('notebook', '=', notebook_id), ]) for nl in notebook_lines: analysis_origin_list.extend(nl.analysis_origin.split(' > ')) analysis_origin_list.append(nl.analysis.code) analysis = Analysis.search([ ('code', 'in', analysis_origin_list), ('behavior', '=', 'normal'), ]) notebook_analysis = set(a.id for a in analysis) if first: analysis_domain = notebook_analysis first = False continue analysis_domain = analysis_domain.intersection( notebook_analysis) default = { 'analysis_domain': list(analysis_domain), } return default def transition_repeat(self): cursor = Transaction().connection.cursor() pool = Pool() Analysis = pool.get('lims.analysis') NotebookLine = pool.get('lims.notebook.line') Notebook = pool.get('lims.notebook') EntryDetailAnalysis = pool.get('lims.entry.detail.analysis') Config = pool.get('lims.configuration') Typification = pool.get('lims.typification') config = Config(1) default_language = config.results_report_language analysis_to_repeat = [] for analysis in self.start.analysis: if analysis.type == 'analysis': analysis_to_repeat.append(analysis.id) else: analysis_to_repeat.extend( Analysis.get_included_analysis_analysis(analysis.id)) for notebook in Notebook.browse(Transaction().context['active_ids']): rm_type = (notebook.fraction.special_type == 'rm') if rm_type: rm_start_uom = (config.rm_start_uom.id if config.rm_start_uom else None) lines_to_create = [] lines_to_update = [] details_to_update = [] for analysis_id in analysis_to_repeat: clause = self._get_original_clause(notebook.id, analysis_id) nlines = NotebookLine.search(clause, order=[('repetition', 'DESC')], limit=1) if not nlines: continue nline_to_repeat = nlines[0] defaults = self._get_repetition_defaults(nline_to_repeat) if rm_type: defaults['final_concentration'] = None defaults['literal_final_concentration'] = None defaults['initial_unit'] = rm_start_uom defaults['final_unit'] = None defaults['detection_limit'] = None defaults['quantification_limit'] = None defaults['lower_limit'] = None defaults['upper_limit'] = None lines_to_create.append(defaults) if not nline_to_repeat.results_report: lines_to_update.append(nline_to_repeat) details_to_update.append(nline_to_repeat.analysis_detail.id) with Transaction().set_context(_check_access=False): lines = NotebookLine.create(lines_to_create) # copy translated fields from typification for line in lines: t = Typification.get_valid_typification( line.product_type.id, line.matrix.id, line.analysis.id, line.method.id) if not t: continue for field in ['initial_concentration', 'final_concentration', 'literal_final_concentration']: cursor.execute("SELECT lang, src, value " "FROM ir_translation " "WHERE name = %s " "AND res_id = %s " "AND type = 'model' " "AND lang != %s", ('lims.typification,' + field, str(t.id), default_language.code)) for x in cursor.fetchall(): cursor.execute("INSERT INTO ir_translation " "(name, res_id, type, lang, src, value) " "VALUES (%s, %s, 'model', %s, %s, %s)", ('lims.notebook.line,' + field, str(line.id), x[0], x[1], x[2])) if self._unaccept_original(): NotebookLine.write(lines_to_update, { 'accepted': False, 'acceptance_date': None, 'report': False, }) details = EntryDetailAnalysis.search([ ('id', 'in', details_to_update), ]) if details: EntryDetailAnalysis.write(details, { 'state': 'unplanned', }) return 'end' def _get_original_clause(self, notebook_id, analysis_id): clause = [ ('notebook', '=', notebook_id), ('analysis', '=', analysis_id), ('analysis.behavior', '=', 'normal'), ] return clause def _get_repetition_defaults(self, line): defaults = { 'notebook': line.notebook.id, 'analysis_detail': line.analysis_detail.id, 'service': line.service.id, 'analysis': line.analysis.id, 'analysis_origin': line.analysis_origin, 'urgent': line.urgent, 'repetition': line.repetition + 1, 'laboratory': line.laboratory.id, 'method': line.method.id, 'device': line.device.id if line.device else None, 'decimals': line.decimals, 'significant_digits': line.significant_digits, 'scientific_notation': line.scientific_notation, 'result_decimals': line.result_decimals, 'converted_result_decimals': line.converted_result_decimals, 'report': line.report, 'results_estimated_waiting': ( line.results_estimated_waiting), 'department': line.department, 'concentration_level': (line.concentration_level.id if line.concentration_level else None), 'initial_concentration': line.initial_concentration, 'final_concentration': line.final_concentration, 'literal_final_concentration': line.literal_final_concentration, 'initial_unit': (line.initial_unit.id if line.initial_unit else None), 'final_unit': line.final_unit.id if line.final_unit else None, 'detection_limit': line.detection_limit, 'quantification_limit': line.quantification_limit, 'lower_limit': line.lower_limit, 'upper_limit': line.upper_limit, } defaults['repetition_reason'] = self.start.repetition_reason if line.results_report: defaults['report'] = False return defaults def _unaccept_original(self): return True class NotebookLineRepeatAnalysisStart(ModelView): 'Repeat Analysis' __name__ = 'lims.notebook.line.repeat_analysis.start' analysis = fields.Many2One('lims.analysis', 'Analysis', required=True, domain=[('id', 'in', Eval('analysis_domain'))]) analysis_domain = fields.One2Many('lims.analysis', None, 'Analysis domain') repetition_reason = fields.Char('Reason') analysis_type = fields.Function(fields.Selection([ (None, ''), ('analysis', 'Analysis'), ('set', 'Set'), ('group', 'Group'), ], 'Type', sort=False), 'on_change_with_analysis_type') method = fields.Many2One('lims.lab.method', 'Method', domain=[('id', 'in', Eval('method_domain'))], states={'invisible': Eval('analysis_type') != 'analysis'}) method_domain = fields.Function(fields.Many2Many('lims.lab.method', None, None, 'Method domain'), 'on_change_with_method_domain') @fields.depends('analysis') def on_change_with_analysis_type(self, name=None): return self.analysis and self.analysis.type or None @fields.depends('analysis') def on_change_with_method_domain(self, name=None): methods = [] if self.analysis and self.analysis.methods: methods = [m.id for m in self.analysis.methods] return methods class NotebookLineRepeatAnalysis(Wizard): 'Repeat Analysis' __name__ = 'lims.notebook.line.repeat_analysis' start = StateView('lims.notebook.line.repeat_analysis.start', 'lims.lims_notebook_line_repeat_analysis_start_view_form', [ Button('Cancel', 'end', 'tryton-cancel'), Button('Repeat', 'repeat', 'tryton-ok', default=True), ]) repeat = StateTransition() @classmethod def check_access(cls): pass def _get_notebook_line_id(self): return Transaction().context.get('active_id', None) def default_start(self, fields): pool = Pool() NotebookLine = pool.get('lims.notebook.line') Analysis = pool.get('lims.analysis') line_id = self._get_notebook_line_id() if not line_id: return {} notebook_line = NotebookLine(line_id) analysis_origin = notebook_line.analysis_origin analysis_origin_list = analysis_origin.split(' > ') analysis_code = notebook_line.analysis.code analysis_origin_list.append(analysis_code) analysis = Analysis.search([ ('code', 'in', analysis_origin_list), ('behavior', '=', 'normal'), ]) notebook_analysis = [a.id for a in analysis] default = { 'analysis_domain': notebook_analysis, } if len(notebook_analysis) == 1: default['analysis'] = notebook_line.analysis.id default['method'] = notebook_line.method.id return default def transition_repeat(self): cursor = Transaction().connection.cursor() pool = Pool() Analysis = pool.get('lims.analysis') NotebookLine = pool.get('lims.notebook.line') Notebook = pool.get('lims.notebook') EntryDetailAnalysis = pool.get('lims.entry.detail.analysis') Config = pool.get('lims.configuration') Typification = pool.get('lims.typification') config = Config(1) default_language = config.results_report_language analysis = self.start.analysis analysis_type = analysis.type if analysis_type == 'analysis': analysis_to_repeat = [analysis.id] else: analysis_to_repeat = Analysis.get_included_analysis_analysis( analysis.id) line_id = self._get_notebook_line_id() notebook_line = NotebookLine(line_id) notebook = Notebook(notebook_line.notebook.id) rm_type = (notebook.fraction.special_type == 'rm') if rm_type: rm_start_uom = (config.rm_start_uom.id if config.rm_start_uom else None) lines_to_create = [] lines_to_update = [] details_to_update = [] for analysis_id in analysis_to_repeat: clause = self._get_original_clause(notebook.id, analysis_id) nlines = NotebookLine.search(clause, order=[('repetition', 'DESC')], limit=1) if not nlines: continue nline_to_repeat = nlines[0] defaults = self._get_repetition_defaults(nline_to_repeat) if analysis_type == 'analysis' and self.start.method: defaults['method'] = self.start.method.id if rm_type: defaults['final_concentration'] = None defaults['literal_final_concentration'] = None defaults['initial_unit'] = rm_start_uom defaults['final_unit'] = None defaults['detection_limit'] = None defaults['quantification_limit'] = None defaults['lower_limit'] = None defaults['upper_limit'] = None lines_to_create.append(defaults) if not nline_to_repeat.results_report: lines_to_update.append(nline_to_repeat) details_to_update.append(nline_to_repeat.analysis_detail.id) with Transaction().set_context(_check_access=False): lines = NotebookLine.create(lines_to_create) # copy translated fields from typification for line in lines: t = Typification.get_valid_typification( line.product_type.id, line.matrix.id, line.analysis.id, line.method.id) if not t: continue for field in ['initial_concentration', 'final_concentration', 'literal_final_concentration']: cursor.execute("SELECT lang, src, value " "FROM ir_translation " "WHERE name = %s " "AND res_id = %s " "AND type = 'model' " "AND lang != %s", ('lims.typification,' + field, str(t.id), default_language.code)) for x in cursor.fetchall(): cursor.execute("INSERT INTO ir_translation " "(name, res_id, type, lang, src, value) " "VALUES (%s, %s, 'model', %s, %s, %s)", ('lims.notebook.line,' + field, str(line.id), x[0], x[1], x[2])) if self._unaccept_original(): NotebookLine.write(lines_to_update, { 'accepted': False, 'acceptance_date': None, 'report': False, }) details = EntryDetailAnalysis.search([ ('id', 'in', details_to_update), ]) if details: EntryDetailAnalysis.write(details, { 'state': 'unplanned', }) return 'end' def _get_original_clause(self, notebook_id, analysis_id): clause = [ ('notebook', '=', notebook_id), ('analysis', '=', analysis_id), ('analysis.behavior', '=', 'normal'), ] if self.start.method: clause.append(('method', '=', self.start.method.id)) return clause def _get_repetition_defaults(self, line): defaults = { 'notebook': line.notebook.id, 'analysis_detail': line.analysis_detail.id, 'service': line.service.id, 'analysis': line.analysis.id, 'analysis_origin': line.analysis_origin, 'urgent': line.urgent, 'repetition': line.repetition + 1, 'laboratory': line.laboratory.id, 'method': line.method.id, 'device': line.device.id if line.device else None, 'decimals': line.decimals, 'significant_digits': line.significant_digits, 'scientific_notation': line.scientific_notation, 'result_decimals': line.result_decimals, 'converted_result_decimals': line.converted_result_decimals, 'report': line.report, 'results_estimated_waiting': ( line.results_estimated_waiting), 'department': line.department, 'concentration_level': (line.concentration_level.id if line.concentration_level else None), 'initial_concentration': line.initial_concentration, 'final_concentration': line.final_concentration, 'literal_final_concentration': line.literal_final_concentration, 'initial_unit': (line.initial_unit.id if line.initial_unit else None), 'final_unit': line.final_unit.id if line.final_unit else None, 'detection_limit': line.detection_limit, 'quantification_limit': line.quantification_limit, 'lower_limit': line.lower_limit, 'upper_limit': line.upper_limit, } defaults['repetition_reason'] = self.start.repetition_reason if line.results_report: defaults['report'] = False return defaults def _unaccept_original(self): return True def end(self): return 'reload' class NotebookAcceptLinesStart(ModelView): 'Accept Lines' __name__ = 'lims.notebook.accept_lines.start' class NotebookAcceptLines(Wizard): 'Accept Lines' __name__ = 'lims.notebook.accept_lines' start_state = 'ok' start = StateView('lims.notebook.accept_lines.start', 'lims.lims_notebook_accept_lines_start_view_form', [ Button('Cancel', 'end', 'tryton-cancel'), Button('Ok', 'ok', 'tryton-ok', default=True), ]) ok = StateTransition() def transition_ok(self): pool = Pool() Config = pool.get('lims.configuration') NotebookLine = pool.get('lims.notebook.line') notebook_lines_acceptance = Config(1).notebook_lines_acceptance for notebook_id in Transaction().context['active_ids']: if notebook_lines_acceptance == 'none': repeated_analysis = [] with Transaction().set_context(_check_access=True): repetitions = NotebookLine.search([ ('notebook', '=', notebook_id), ('repetition', '>', 0), ]) if repetitions: repeated_analysis = [l.analysis.id for l in repetitions] with Transaction().set_context(_check_access=True): notebook_lines = NotebookLine.search([ ('notebook', '=', notebook_id), ('analysis', 'not in', repeated_analysis), ]) else: # last with Transaction().set_context(_check_access=True): notebook_lines = NotebookLine.search([ ('notebook', '=', notebook_id), ], order=[('repetition', 'DESC')]) if not notebook_lines: continue self.lines_accept(notebook_lines) return 'end' def lines_accept(self, notebook_lines): pool = Pool() Config = pool.get('lims.configuration') NotebookLine = pool.get('lims.notebook.line') allow_same_analysis = Config(1).notebook_lines_acceptance_method accepted_analysis = {} lines_to_write = [] for notebook_line in notebook_lines: if notebook_line.accepted: continue if not notebook_line.report: continue if notebook_line.annulled: continue if not notebook_line.end_date: continue if not (notebook_line.result or notebook_line.converted_result or notebook_line.literal_result or (notebook_line.result_modifier and notebook_line.result_modifier.code in ('d', 'nd', 'pos', 'neg', 'ni', 'abs', 'pre')) or (notebook_line.converted_result_modifier and notebook_line.converted_result_modifier.code in ('d', 'nd', 'pos', 'neg', 'ni', 'abs', 'pre'))): continue if (notebook_line.converted_result and ( notebook_line.converted_result_modifier and notebook_line.converted_result_modifier.code not in ('ni', 'low'))): continue if (notebook_line.result and ( notebook_line.result_modifier and notebook_line.result_modifier.code not in ('ni', 'low'))): continue notebook_id = notebook_line.notebook.id if notebook_id not in accepted_analysis: accepted_lines = NotebookLine.search([ ('notebook', '=', notebook_id), ('accepted', '=', True), ]) if allow_same_analysis: accepted_analysis[notebook_id] = [ (l.analysis.id, l.method.id) for l in accepted_lines] else: accepted_analysis[notebook_id] = [ l.analysis.id for l in accepted_lines] if allow_same_analysis: key = (notebook_line.analysis.id, notebook_line.method.id) else: key = notebook_line.analysis.id if key in accepted_analysis[notebook_id]: continue accepted_analysis[notebook_id].append(key) lines_to_write.append(notebook_line) if lines_to_write: acceptance_date = datetime.now() NotebookLine.write(lines_to_write, { 'accepted': True, 'acceptance_date': acceptance_date, }) def end(self): return 'reload' class NotebookLineAcceptLines(NotebookAcceptLines): 'Accept Lines' __name__ = 'lims.notebook_line.accept_lines' def transition_ok(self): NotebookLine = Pool().get('lims.notebook.line') notebook_lines = NotebookLine.browse( Transaction().context['active_ids']) if not notebook_lines: return 'end' self.lines_accept(notebook_lines) return 'end' class NotebookLineUnacceptLines(Wizard): 'Revert Lines Acceptance' __name__ = 'lims.notebook_line.unaccept_lines' start_state = 'ok' ok = StateTransition() def transition_ok(self): NotebookLine = Pool().get('lims.notebook.line') notebook_lines = NotebookLine.browse( Transaction().context['active_ids']) if not notebook_lines: return 'end' self.lines_unaccept(notebook_lines) return 'end' def lines_unaccept(self, notebook_lines): pool = Pool() NotebookLine = pool.get('lims.notebook.line') ResultsLine = pool.get('lims.results_report.version.detail.line') lines_to_write = [] for notebook_line in notebook_lines: if not notebook_line.accepted: continue report_lines = ResultsLine.search([ ('notebook_line', '=', notebook_line.id), ('detail_sample.version_detail.state', '!=', 'annulled'), ]) if report_lines: continue lines_to_write.append(notebook_line) if lines_to_write: NotebookLine.write(lines_to_write, { 'accepted': False, 'acceptance_date': None, }) def end(self): return 'reload' class NotebookAnnulLinesStart(ModelView): 'Annul Lines' __name__ = 'lims.notebook.annul_lines.start' annulment_reason = fields.Text('Annulment reason') class NotebookAnnulLines(Wizard): 'Annul Lines' __name__ = 'lims.notebook.annul_lines' start = StateView('lims.notebook.annul_lines.start', 'lims.lims_notebook_annul_lines_start_view_form', [ Button('Cancel', 'end', 'tryton-cancel'), Button('Ok', 'ok', 'tryton-ok', default=True), ]) ok = StateTransition() def transition_ok(self): NotebookLine = Pool().get('lims.notebook.line') with Transaction().set_context(_check_access=True): notebook_lines = NotebookLine.search([ ('notebook', 'in', Transaction().context['active_ids']), ('accepted', '=', False), ('annulled', '=', False), ]) if notebook_lines: self.lines_annul(notebook_lines) return 'end' def lines_annul(self, notebook_lines): pool = Pool() ModelData = pool.get('ir.model.data') NotebookLine = pool.get('lims.notebook.line') result_modifier_na = ModelData.get_id('lims', 'result_modifier_na') NotebookLine.write(notebook_lines, { 'result_modifier': result_modifier_na, 'annulled': True, 'annulment_date': datetime.now(), 'annulment_reason': self.start.annulment_reason, 'report': False, }) def end(self): return 'reload' class NotebookLineAnnulLines(NotebookAnnulLines): 'Annul Lines' __name__ = 'lims.notebook_line.annul_lines' def transition_ok(self): NotebookLine = Pool().get('lims.notebook.line') notebook_lines = NotebookLine.search([ ('id', 'in', Transaction().context['active_ids']), ('accepted', '=', False), ('annulled', '=', False), ]) if notebook_lines: self.lines_annul(notebook_lines) return 'end' class NotebookUnannulLines(Wizard): 'Revert Lines Annulment' __name__ = 'lims.notebook.unannul_lines' start_state = 'ok' ok = StateTransition() def transition_ok(self): NotebookLine = Pool().get('lims.notebook.line') for notebook_id in Transaction().context['active_ids']: with Transaction().set_context(_check_access=True): notebook_lines = NotebookLine.search([ ('notebook', '=', notebook_id), ('annulled', '=', True), ]) if notebook_lines: self.lines_unannul(notebook_lines) return 'end' def lines_unannul(self, notebook_lines): NotebookLine = Pool().get('lims.notebook.line') NotebookLine.write(notebook_lines, { 'result_modifier': None, 'annulled': False, 'annulment_date': None, 'annulment_reason': None, 'report': True, }) def end(self): return 'reload' class NotebookLineUnannulLines(NotebookUnannulLines): 'Revert Lines Annulment' __name__ = 'lims.notebook_line.unannul_lines' def transition_ok(self): NotebookLine = Pool().get('lims.notebook.line') notebook_lines = NotebookLine.search([ ('id', 'in', Transaction().context['active_ids']), ('annulled', '=', True), ]) if notebook_lines: self.lines_unannul(notebook_lines) return 'end' class NotebookResultsVerificationStart(ModelView): 'Results Verification' __name__ = 'lims.notebook.results_verification.start' range_type = fields.Many2One('lims.range.type', 'Origin', required=True, domain=[('use', '=', 'results_verification')]) class NotebookResultsVerification(Wizard): 'Results Verification' __name__ = 'lims.notebook.results_verification' start = StateView('lims.notebook.results_verification.start', 'lims.lims_notebook_results_verification_view_form', [ Button('Cancel', 'end', 'tryton-cancel'), Button('Ok', 'ok', 'tryton-ok', default=True), ]) ok = StateTransition() def default_start(self, fields): RangeType = Pool().get('lims.range.type') default = {} default_range_type = RangeType.search([ ('use', '=', 'results_verification'), ('by_default', '=', True), ]) if default_range_type: default['range_type'] = default_range_type[0].id return default def transition_ok(self): NotebookLine = Pool().get('lims.notebook.line') for notebook_id in Transaction().context['active_ids']: with Transaction().set_context(_check_access=True): notebook_lines = NotebookLine.search([ ('notebook', '=', notebook_id), ]) if not notebook_lines: continue self.lines_results_verification(notebook_lines) return 'end' def lines_results_verification(self, notebook_lines): pool = Pool() NotebookLine = pool.get('lims.notebook.line') Range = pool.get('lims.range') UomConversion = pool.get('lims.uom.conversion') VolumeConversion = pool.get('lims.volume.conversion') verifications = self._get_verifications() lines_to_save = [] for notebook_line in notebook_lines: if notebook_line.accepted: continue result = notebook_line.converted_result if not result: result = notebook_line.result iu = notebook_line.initial_unit if not iu: continue try: ic = float(notebook_line.initial_concentration) except (TypeError, ValueError): continue else: iu = notebook_line.final_unit if not iu: continue try: ic = float(notebook_line.final_concentration) except (TypeError, ValueError): continue try: result = float(result) except (TypeError, ValueError): continue ranges = Range.search([ ('range_type', '=', self.start.range_type), ('analysis', '=', notebook_line.analysis.id), ('product_type', '=', notebook_line.notebook.product_type.id), ('matrix', '=', notebook_line.notebook.matrix.id), ]) if not ranges: continue fu = ranges[0].uom try: fc = float(ranges[0].concentration) except (TypeError, ValueError): continue if fu and fu.rec_name != '-': converted_result = None if (iu == fu and ic == fc): converted_result = result elif (iu != fu and ic == fc): formula = UomConversion.get_conversion_formula(iu, fu) if not formula: continue variables = self._get_variables(formula, notebook_line) parser = FormulaParser(formula, variables) formula_result = parser.getValue() converted_result = result * formula_result elif (iu == fu and ic != fc): converted_result = result * (fc / ic) else: formula = None conversions = UomConversion.search([ ('initial_uom', '=', iu), ('final_uom', '=', fu), ]) if conversions: formula = conversions[0].conversion_formula if not formula: continue variables = self._get_variables(formula, notebook_line) parser = FormulaParser(formula, variables) formula_result = parser.getValue() if (conversions[0].initial_uom_volume and conversions[0].final_uom_volume): d_ic = VolumeConversion.brixToDensity(ic) d_fc = VolumeConversion.brixToDensity(fc) converted_result = (result * (fc / ic) * (d_fc / d_ic) * formula_result) else: converted_result = (result * (fc / ic) * formula_result) result = float(converted_result) verification = self._verificate_result(result, ranges[0]) notebook_line.verification = verifications.get(verification) lines_to_save.append(notebook_line) if lines_to_save: NotebookLine.save(lines_to_save) def _get_variables(self, formula, notebook_line): pool = Pool() VolumeConversion = pool.get('lims.volume.conversion') variables = {} for var in ('DI',): while True: idx = formula.find(var) if idx >= 0: variables[var] = 0 formula = formula.replace(var, '_') else: break for var in variables.keys(): if var == 'DI': ic = float(notebook_line.final_concentration) result = VolumeConversion.brixToDensity(ic) if result: variables[var] = result return variables def _verificate_result(self, result, range_): if range_.min95 and range_.max95: if result < range_.min: return 'out' elif result < range_.min95: return 'ok*' elif result <= range_.max95: return 'ok' elif result <= range_.max: return 'ok*' else: return 'out' else: if (range_.min and result < range_.min): return 'out' elif (range_.max and result <= range_.max): return 'ok' else: return 'out' def _get_verifications(self): pool = Pool() User = pool.get('res.user') Lang = pool.get('ir.lang') lang = User(Transaction().user).language if not lang: lang, = Lang.search([ ('code', '=', 'en'), ], limit=1) verifications = {} with Transaction().set_context(language=lang.code): verifications['ok'] = gettext('lims.msg_ok') verifications['ok*'] = gettext('lims.msg_ok*') verifications['out'] = gettext('lims.msg_out') return verifications def end(self): return 'reload' class NotebookLineResultsVerification(NotebookResultsVerification): 'Results Verification' __name__ = 'lims.notebook_line.results_verification' def transition_ok(self): NotebookLine = Pool().get('lims.notebook.line') notebook_lines = NotebookLine.browse( Transaction().context['active_ids']) if not notebook_lines: return 'end' self.lines_results_verification(notebook_lines) return 'end' class UncertaintyCalcStart(ModelView): 'Uncertainty Calculation' __name__ = 'lims.notebook.uncertainty_calc.start' range_type = fields.Many2One('lims.range.type', 'Origin', required=True, domain=[('use', '=', 'uncertainty_calc')]) class UncertaintyCalc(Wizard): 'Uncertainty Calculation' __name__ = 'lims.notebook.uncertainty_calc' start_state = 'check' check = StateTransition() start = StateView('lims.notebook.uncertainty_calc.start', 'lims.lims_notebook_uncertainty_calc_view_form', [ Button('Cancel', 'end', 'tryton-cancel'), Button('Ok', 'ok', 'tryton-ok', default=True), ]) ok = StateTransition() def transition_check(self): RangeType = Pool().get('lims.range.type') default_range_type = RangeType.search([ ('use', '=', 'uncertainty_calc'), ('by_default', '=', True), ]) if default_range_type: self.start.range_type = default_range_type[0].id return 'ok' return 'start' def default_start(self, fields): RangeType = Pool().get('lims.range.type') default = {} default_range_type = RangeType.search([ ('use', '=', 'uncertainty_calc'), ('by_default', '=', True), ]) if default_range_type: default['range_type'] = default_range_type[0].id return default def transition_ok(self): NotebookLine = Pool().get('lims.notebook.line') for notebook_id in Transaction().context['active_ids']: with Transaction().set_context(_check_access=True): notebook_lines = NotebookLine.search([ ('notebook', '=', notebook_id), ]) if not notebook_lines: continue self.lines_uncertainty_calc(notebook_lines) return 'end' def lines_uncertainty_calc(self, notebook_lines): pool = Pool() NotebookLine = pool.get('lims.notebook.line') Range = pool.get('lims.range') lines_to_save = [] for notebook_line in notebook_lines: if notebook_line.accepted: continue result = notebook_line.converted_result if not result: result = notebook_line.result try: result = float(result) except (TypeError, ValueError): continue ranges = Range.search([ ('range_type', '=', self.start.range_type), ('analysis', '=', notebook_line.analysis.id), ('product_type', '=', notebook_line.notebook.product_type.id), ('matrix', '=', notebook_line.notebook.matrix.id), ]) if not ranges: continue uncertainty = self._get_uncertainty(result, notebook_line, ranges[0]) if uncertainty is None: continue notebook_line.uncertainty = str(uncertainty) lines_to_save.append(notebook_line) if lines_to_save: NotebookLine.save(lines_to_save) def _get_uncertainty(self, result, notebook_line, range_): dilution_factor = notebook_line.dilution_factor if not dilution_factor or dilution_factor == 0.0: dilution_factor = 1.0 diluted_result = result / dilution_factor try: factor = range_.factor or 1.0 low_level = range_.low_level or 0.0 * factor middle_level = range_.middle_level or 0.0 * factor high_level = range_.high_level or 0.0 * factor except TypeError: return None uncertainty = 0.0 if (range_.low_level_value and not (range_.middle_level_value and range_.high_level_value) and (diluted_result > low_level)): uncertainty = range_.low_level_value elif (range_.low_level_value and range_.middle_level_value and range_.high_level_value): if (low_level <= diluted_result and diluted_result < middle_level): uncertainty = range_.low_level_value elif (middle_level <= diluted_result and diluted_result < high_level): uncertainty = range_.middle_level_value elif diluted_result >= high_level: uncertainty = range_.high_level_value if (range_.low_level_value and range_.middle_level_value and not range_.high_level_value): if (low_level <= diluted_result and diluted_result < middle_level): uncertainty = range_.low_level_value elif (middle_level <= diluted_result and diluted_result < high_level): uncertainty = range_.middle_level_value if uncertainty > 0.0: uncertainty = result * uncertainty / 100 return uncertainty def end(self): return 'reload' class NotebookLineUncertaintyCalc(UncertaintyCalc): 'Uncertainty Calculation' __name__ = 'lims.notebook_line.uncertainty_calc' def transition_ok(self): NotebookLine = Pool().get('lims.notebook.line') notebook_lines = NotebookLine.browse( Transaction().context['active_ids']) if not notebook_lines: return 'end' self.lines_uncertainty_calc(notebook_lines) return 'end' class NotebookPrecisionControlStart(ModelView): 'Precision Control' __name__ = 'lims.notebook.precision_control.start' range_type = fields.Many2One('lims.range.type', 'Origin', required=True, domain=[('use', '=', 'repeatability_calc')]) product_type = fields.Many2One('lims.product.type', 'Product type', required=True) matrix = fields.Many2One('lims.matrix', 'Matrix', required=True, domain=[('id', 'in', Eval('matrix_domain'))]) matrix_domain = fields.Function(fields.Many2Many('lims.matrix', None, None, 'Matrix domain'), 'on_change_with_matrix_domain') factor = fields.Float('Factor', required=True) @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] class NotebookPrecisionControl(Wizard): 'Precision Control' __name__ = 'lims.notebook.precision_control' start_state = 'check' check = StateTransition() start = StateView('lims.notebook.precision_control.start', 'lims.lims_notebook_precision_control_view_form', [ Button('Cancel', 'end', 'tryton-cancel'), Button('Ok', 'ok', 'tryton-ok', default=True), ]) ok = StateTransition() def transition_check(self): Notebook = Pool().get('lims.notebook') notebook = Notebook(Transaction().context['active_id']) if notebook.fraction.special_type in ('rm', 'con'): return 'start' return 'end' def default_start(self, fields): pool = Pool() Notebook = pool.get('lims.notebook') RangeType = pool.get('lims.range.type') notebook = Notebook(Transaction().context['active_id']) default = { 'product_type': notebook.product_type.id, 'matrix': notebook.matrix.id, 'factor': 2, } default_range_type = RangeType.search([ ('use', '=', 'repeatability_calc'), ('by_default', '=', True), ]) if default_range_type: default['range_type'] = default_range_type[0].id return default def transition_ok(self): NotebookLine = Pool().get('lims.notebook.line') with Transaction().set_context(_check_access=True): notebook_lines = NotebookLine.search([ ('notebook', '=', Transaction().context['active_id']), ]) if not notebook_lines: return 'end' self.lines_precision_control(notebook_lines) return 'end' def lines_precision_control(self, notebook_lines): pool = Pool() NotebookLine = pool.get('lims.notebook.line') Range = pool.get('lims.range') lines_to_save = [] for notebook_line in notebook_lines: if notebook_line.verification: continue concentration_level = notebook_line.concentration_level if not concentration_level: continue ranges = Range.search([ ('range_type', '=', self.start.range_type.id), ('analysis', '=', notebook_line.analysis.id), ('product_type', '=', self.start.product_type.id), ('matrix', '=', self.start.matrix.id), ]) if not ranges: continue if concentration_level.code == 'NC': cv = ranges[0].low_level_coefficient_variation elif concentration_level.code == 'NM': cv = ranges[0].middle_level_coefficient_variation elif concentration_level.code == 'NA': cv = ranges[0].high_level_coefficient_variation else: continue if not cv: continue try: if notebook_line.repetition == 0: rep_0 = float(notebook_line.result) rep_1 = float(self._get_repetition_result(notebook_line, 1)) elif notebook_line.repetition == 1: rep_0 = float(self._get_repetition_result(notebook_line, 0)) rep_1 = float(notebook_line.result) else: continue except (TypeError, ValueError): continue if not rep_0 or not rep_1: continue average = (rep_0 + rep_1) / 2 error = abs(rep_0 - rep_1) / average * 100 if error < (cv * self.start.factor): res = gettext( 'lims.msg_acceptable', factor=self.start.factor, cv=cv) else: res = gettext( 'lims.msg_unacceptable', factor=self.start.factor, cv=cv) notebook_line.verification = res lines_to_save.append(notebook_line) if lines_to_save: NotebookLine.save(lines_to_save) def _get_repetition_result(self, notebook_line, repetition): NotebookLine = Pool().get('lims.notebook.line') repetition = NotebookLine.search([ ('notebook', '=', notebook_line.notebook.id), ('analysis', '=', notebook_line.analysis.id), ('repetition', '=', repetition), ]) if not repetition: return None return repetition[0].result def end(self): return 'reload' class NotebookLinePrecisionControl(NotebookPrecisionControl): 'Precision Control' __name__ = 'lims.notebook_line.precision_control' def transition_check(self): NotebookLine = Pool().get('lims.notebook.line') reference_line = NotebookLine(Transaction().context['active_id']) if reference_line.notebook.fraction.special_type in ('rm', 'con'): return 'start' return 'end' def default_start(self, fields): NotebookLine = Pool().get('lims.notebook.line') reference_line = NotebookLine(Transaction().context['active_id']) with Transaction().set_context(active_id=reference_line.notebook.id): return super().default_start(fields) def transition_ok(self): NotebookLine = Pool().get('lims.notebook.line') reference_line = NotebookLine(Transaction().context['active_id']) notebook_lines = NotebookLine.browse( Transaction().context['active_ids']) notebook_lines = [l for l in notebook_lines if l.notebook.id == reference_line.notebook.id] if not notebook_lines: return 'end' self.lines_precision_control(notebook_lines) return 'end' class OpenNotebookLines(Wizard): 'Open Notebook Lines' __name__ = 'lims.open_notebook_lines' start_state = 'open_' open_ = StateAction('lims.act_lims_notebook_line_related1') def do_open_(self, action): Notebook = Pool().get('lims.notebook') notebook = Notebook.browse(Transaction().context['active_ids'])[0] action['pyson_domain'] = PYSONEncoder().encode( [('notebook', 'in', Transaction().context['active_ids'])]) action['name'] = \ '%s - %s - %s - %s' % (notebook.fraction.number, notebook.product_type.description, notebook.matrix.description, notebook.label) return action, {} class ChangeEstimatedDaysForResultsStart(ModelView): 'Change Estimated Days For Results' __name__ = 'lims.change_results_estimated_waiting.start' date_from = fields.Date('Confirmation date From', required=True) date_to = fields.Date('Confirmation date To', required=True) methods = fields.Many2Many('lims.lab.method', None, None, 'Methods', required=True) results_estimated_waiting = fields.Integer('Days to add') party = fields.Many2One('party.party', 'Party') class ChangeEstimatedDaysForResults(Wizard): 'Change Estimated Days For Results' __name__ = 'lims.change_results_estimated_waiting' start = StateView('lims.change_results_estimated_waiting.start', 'lims.change_results_estimated_waiting_start_view_form', [ Button('Cancel', 'end', 'tryton-cancel'), Button('Change', 'change', 'tryton-ok', default=True), ]) change = StateTransition() def transition_change(self): NotebookLine = Pool().get('lims.notebook.line') methods_ids = [m.id for m in self.start.methods] clause = [('method', 'in', methods_ids)] clause.append(('analysis_detail.confirmation_date', '>=', self.start.date_from)) clause.append(('analysis_detail.confirmation_date', '<=', self.start.date_to)) clause.append(('accepted', '=', False)) party_id = self.start.party and self.start.party.id or None if party_id: clause.append(('party', '=', party_id)) with Transaction().set_context(_check_access=True): notebook_lines = NotebookLine.search(clause) if notebook_lines: lines_to_save = [] for line in notebook_lines: line.results_estimated_waiting = (( line.results_estimated_waiting or 0) + self.start.results_estimated_waiting) lines_to_save.append(line) NotebookLine.save(lines_to_save) return 'end' class PrintAnalysisPendingInformStart(ModelView): 'Analysis Pending of Inform' __name__ = 'lims.print_analysis_pending_inform.start' date_from = fields.Date('From confirmation date', required=True) date_to = fields.Date('To confirmation date', required=True) laboratory = fields.Many2One('lims.laboratory', 'Laboratory', required=True) party = fields.Many2One('party.party', 'Party') include_comments_of_fraction = fields.Boolean( 'Include comments of the fraction') class PrintAnalysisPendingInform(Wizard): 'Analysis Pending of Inform' __name__ = 'lims.print_analysis_pending_inform' start = StateView('lims.print_analysis_pending_inform.start', 'lims.print_analysis_pending_inform_start_view_form', [ Button('Cancel', 'end', 'tryton-cancel'), Button('Print', 'print_', 'tryton-print', default=True), ]) print_ = StateReport('lims.analysis_pending_inform') def do_print_(self, action): data = { 'date_from': self.start.date_from, 'date_to': self.start.date_to, 'laboratory': self.start.laboratory.id, 'party': self.start.party and self.start.party.id or None, 'include_comments_of_fraction': ( self.start.include_comments_of_fraction), } return action, data def transition_print_(self): return 'end' class AnalysisPendingInform(Report): 'Analysis Pending of Inform' __name__ = 'lims.analysis_pending_inform' @classmethod def get_context(cls, records, header, data): pool = Pool() Laboratory = pool.get('lims.laboratory') Party = pool.get('party.party') report_context = super().get_context(records, header, data) today = get_print_date() report_context['today'] = today report_context['company'] = report_context['user'].company report_context['date_from'] = data['date_from'] report_context['date_to'] = data['date_to'] report_context['laboratory'] = Laboratory(data['laboratory']).rec_name report_context['party'] = '' if data['party']: report_context['party'] = Party(data['party']).rec_name report_context['include_comments_of_fraction'] = \ data['include_comments_of_fraction'] objects = cls._get_report_records(data['date_from'], data['date_to'], data['laboratory'], data['party']) report_context['records'] = objects report_context['get_notice'] = cls.get_notice return report_context @classmethod def _get_report_records(cls, date_from, date_to, laboratory, party): pool = Pool() NotebookLine = pool.get('lims.notebook.line') res = [] excluded_notebooks = cls._get_excluded_notebooks(date_from, date_to, laboratory, party) if excluded_notebooks: for n_id, a_ids in excluded_notebooks.items(): clause = [ ('notebook.id', '=', n_id), ('analysis', 'in', a_ids), ('laboratory', '=', laboratory), ('report', '=', True), ('annulled', '=', False), ] excluded_lines = NotebookLine.search(clause) if excluded_lines: res.extend(excluded_lines) return res @classmethod def _get_excluded_notebooks(cls, date_from, date_to, laboratory, party): cursor = Transaction().connection.cursor() pool = Pool() NotebookLine = pool.get('lims.notebook.line') Service = pool.get('lims.service') Fraction = pool.get('lims.fraction') Sample = pool.get('lims.sample') Entry = pool.get('lims.entry') FractionType = pool.get('lims.fraction.type') party_clause = '' if party: party_clause = 'AND e.party = ' + str(party) cursor.execute('SELECT nl.notebook, nl.analysis, nl.method, ' 'nl.accepted ' 'FROM "' + NotebookLine._table + '" nl ' 'INNER JOIN "' + Service._table + '" srv ' 'ON srv.id = nl.service ' 'INNER JOIN "' + Fraction._table + '" f ' 'ON f.id = srv.fraction ' 'INNER JOIN "' + Sample._table + '" s ' 'ON s.id = f.sample ' 'INNER JOIN "' + Entry._table + '" e ' 'ON e.id = s.entry ' 'INNER JOIN "' + FractionType._table + '" ft ' 'ON ft.id = f.type ' 'WHERE ft.report = TRUE ' 'AND srv.confirmation_date::date >= %s::date ' 'AND srv.confirmation_date::date <= %s::date ' 'AND nl.laboratory = %s ' 'AND nl.report = TRUE ' 'AND nl.annulled = FALSE ' + party_clause, (date_from, date_to, laboratory,)) notebook_lines = cursor.fetchall() # Check repetitions oks, to_check = [], [] for line in notebook_lines: key = (line[0], line[1], line[2]) if not line[3]: to_check.append(key) else: oks.append(key) to_check = list(set(to_check) - set(oks)) excluded_notebooks = {} for n_id, a_id, m_id in to_check: if n_id not in excluded_notebooks: excluded_notebooks[n_id] = [] excluded_notebooks[n_id].append(a_id) return excluded_notebooks @classmethod def get_notice(cls, line): pool = Pool() Date = pool.get('ir.date') today = Date.today() if line.report_date: if line.report_date < today: return 'Timed out' if line.results_estimated_date and not line.report_date: if line.results_estimated_date < today: return 'Timed out' if line.report_date: if ((line.report_date - today).days < 3 and (line.report_date - today).days >= 0): return 'To expire' if line.results_estimated_date and not line.report_date: if ((line.results_estimated_date - today).days < 3 and (line.results_estimated_date - today).days >= 0): return 'To expire' return '' class PrintAnalysisCheckedPendingInformStart(ModelView): 'Analysis checked pending of Inform' __name__ = 'lims.print_analysis_checked_pending_inform.start' date_from = fields.Date('Date from', required=True) date_to = fields.Date('Date to', required=True) laboratory = fields.Many2One('lims.laboratory', 'Laboratory', required=True) party = fields.Many2One('party.party', 'Party') class PrintAnalysisCheckedPendingInform(Wizard): 'Analysis Checked pending of Inform' __name__ = 'lims.print_analysis_checked_pending_inform' start = StateView('lims.print_analysis_checked_pending_inform.start', 'lims.print_analysis_checked_pending_inform_start_view_form', [ Button('Cancel', 'end', 'tryton-cancel'), Button('Print', 'print_', 'tryton-print', default=True), ]) print_ = StateReport('lims.analysis_checked_pending_inform') def do_print_(self, action): data = { 'date_from': self.start.date_from, 'date_to': self.start.date_to, 'laboratory': self.start.laboratory.id, 'party': self.start.party and self.start.party.id or None, } return action, data def transition_print_(self): return 'end' class AnalysisCheckedPendingInform(Report): 'Analysis checked pending of Inform' __name__ = 'lims.analysis_checked_pending_inform' @classmethod def get_context(cls, records, header, data): pool = Pool() Laboratory = pool.get('lims.laboratory') Party = pool.get('party.party') report_context = super().get_context(records, header, data) today = get_print_date() report_context['today'] = today report_context['company'] = report_context['user'].company report_context['date_from'] = data['date_from'] report_context['date_to'] = data['date_to'] report_context['laboratory'] = Laboratory(data['laboratory']).rec_name report_context['party'] = '' if data['party']: report_context['party'] = Party(data['party']).rec_name objects = cls._get_report_records(data['date_from'], data['date_to'], data['laboratory'], data['party']) report_context['records'] = objects report_context['get_notice'] = cls.get_notice return report_context @classmethod def _get_report_records(cls, date_from, date_to, laboratory, party): pool = Pool() NotebookLine = pool.get('lims.notebook.line') res = [] included_notebooks = cls._get_included_notebooks(date_from, date_to, laboratory, party) if included_notebooks: for n_id, a_ids in included_notebooks: clause = [ ('notebook.id', '=', n_id), ('analysis', 'in', [a_ids]), ('laboratory', '=', laboratory), ('report', '=', True), ('annulled', '=', False), ] included_lines = NotebookLine.search(clause) if included_lines: res.extend(line for line in included_lines) return res @classmethod def _get_included_notebooks(cls, date_from, date_to, laboratory, party): cursor = Transaction().connection.cursor() pool = Pool() NotebookLine = pool.get('lims.notebook.line') Notebook = pool.get('lims.notebook') Fraction = pool.get('lims.fraction') Sample = pool.get('lims.sample') Entry = pool.get('lims.entry') FractionType = pool.get('lims.fraction.type') party_clause = '' if party: party_clause = 'AND e.party = ' + str(party) cursor.execute('SELECT nl.notebook, nl.analysis ' 'FROM "' + NotebookLine._table + '" nl ' 'INNER JOIN "' + Notebook._table + '" n ' 'ON n.id = nl.notebook ' 'INNER JOIN "' + Fraction._table + '" f ' 'ON f.id = n.fraction ' 'INNER JOIN "' + Sample._table + '" s ' 'ON s.id = f.sample ' 'INNER JOIN "' + Entry._table + '" e ' 'ON e.id = s.entry ' 'INNER JOIN "' + FractionType._table + '" ft ' 'ON ft.id = f.type ' 'WHERE ft.report = TRUE ' 'AND s.date::date >= %s::date ' 'AND s.date::date <= %s::date ' 'AND nl.laboratory = %s ' 'AND nl.report = TRUE ' 'AND nl.accepted = TRUE ' 'AND nl.results_report IS NULL ' 'AND nl.annulled = FALSE ' + party_clause, (date_from, date_to, laboratory,)) notebook_lines = cursor.fetchall() return notebook_lines @classmethod def get_notice(cls, line): pool = Pool() Date = pool.get('ir.date') today = Date.today() if line.report_date: if line.report_date < today: return 'Timed out' if line.results_estimated_date and not line.report_date: if line.results_estimated_date < today: return 'Timed out' if line.report_date: if ((line.report_date - today).days < 3 and (line.report_date - today).days >= 0): return 'To expire' if line.results_estimated_date and not line.report_date: if ((line.results_estimated_date - today).days < 3 and (line.results_estimated_date - today).days >= 0): return 'To expire' return '' class NotebookEvaluateRulesStart(ModelView): 'Evaluate Rules' __name__ = 'lims.notebook.evaluate_rules.start' class NotebookEvaluateRules(Wizard): 'Evaluate Rules' __name__ = 'lims.notebook.evaluate_rules' start_state = 'ok' start = StateView('lims.notebook.evaluate_rules.start', 'lims.notebook_evaluate_rules_start_view_form', [ Button('Cancel', 'end', 'tryton-cancel'), Button('Ok', 'ok', 'tryton-ok', default=True), ]) ok = StateTransition() def transition_ok(self): NotebookLine = Pool().get('lims.notebook.line') for notebook_id in Transaction().context['active_ids']: with Transaction().set_context(_check_access=True): notebook_lines = NotebookLine.search([ ('notebook', '=', notebook_id), ]) if not notebook_lines: continue self.evaluate_rules(notebook_lines) return 'end' def evaluate_rules(self, notebook_lines): pool = Pool() NotebookRule = pool.get('lims.rule') for line in notebook_lines: rules = NotebookRule.search([ ('analysis', '=', line.analysis), ]) for rule in rules: if rule.eval_condition(line): rule.exec_action(line) def end(self): return 'reload' class NotebookLineEvaluateRules(NotebookEvaluateRules): 'Evaluate Rules' __name__ = 'lims.notebook_line.evaluate_rules' def transition_ok(self): NotebookLine = Pool().get('lims.notebook.line') notebook_lines = NotebookLine.browse( Transaction().context['active_ids']) if not notebook_lines: return 'end' self.evaluate_rules(notebook_lines) return 'end' class SampleNotebook(Wizard): 'Sample Notebook' __name__ = 'lims.sample.notebook' start = StateAction('lims.act_lims_notebook_list') @classmethod def check_access(cls): pass def do_start(self, action): pool = Pool() Sample = pool.get('lims.sample') Notebook = pool.get('lims.notebook') active_ids = Transaction().context['active_ids'] samples = Sample.browse(active_ids) notebooks = Notebook.search([ ('fraction.sample', 'in', active_ids), ]) action['pyson_domain'] = PYSONEncoder().encode([ ('id', 'in', [nb.id for nb in notebooks]), ]) action['name'] += ' (%s)' % ', '.join( s.rec_name for s in samples) return action, {}