1
|
|
|
import json |
2
|
|
|
from collections import defaultdict |
3
|
|
|
|
4
|
|
|
from DateTime import DateTime |
5
|
|
|
from Products.CMFPlone.i18nl10n import ulocalized_time |
6
|
|
|
from bika.lims import api |
7
|
|
|
from bika.lims import bikaMessageFactory as _ |
8
|
|
|
from bika.lims import logger |
9
|
|
|
from bika.lims.api.analysis import is_out_of_range |
10
|
|
|
from bika.lims.browser.referenceanalysis import AnalysesRetractedListReport |
11
|
|
|
from bika.lims.browser.workflow import WorkflowActionGenericAdapter |
12
|
|
|
from bika.lims.catalog.analysis_catalog import CATALOG_ANALYSIS_LISTING |
13
|
|
|
from bika.lims.interfaces import IReferenceAnalysis |
14
|
|
|
|
15
|
|
|
|
16
|
|
|
class WorkflowActionSubmitAdapter(WorkflowActionGenericAdapter): |
17
|
|
|
"""Adapter in charge of submission of analyses |
18
|
|
|
""" |
19
|
|
|
|
20
|
|
|
def __call__(self, action, objects): |
21
|
|
|
# Store invalid instruments-ref.analyses |
22
|
|
|
invalid_instrument_refs = defaultdict(set) |
23
|
|
|
|
24
|
|
|
# Get interims data |
25
|
|
|
interims_data = self.get_interims_data() |
26
|
|
|
|
27
|
|
|
for analysis in objects: |
28
|
|
|
uid = api.get_uid(analysis) |
29
|
|
|
|
30
|
|
|
# Need to save remarks? |
31
|
|
|
remarks = self.get_form_value("Remarks", uid, default="") |
32
|
|
|
analysis.setRemarks(remarks) |
33
|
|
|
|
34
|
|
|
# Need to save the instrument? |
35
|
|
|
instrument = self.get_form_value("Instrument", uid, None) |
36
|
|
|
if instrument is not None: |
37
|
|
|
# Could be an empty string |
38
|
|
|
instrument = instrument or None |
39
|
|
|
analysis.setInstrument(instrument) |
40
|
|
|
if instrument and IReferenceAnalysis.providedBy(analysis): |
41
|
|
|
if is_out_of_range(analysis): |
42
|
|
|
# This reference analysis is out of range, so we have |
43
|
|
|
# to retract all analyses assigned to this same |
44
|
|
|
# instrument that are awaiting for verification |
45
|
|
|
invalid_instrument_refs[uid].add(analysis) |
46
|
|
|
else: |
47
|
|
|
# The reference result is valid, so make the instrument |
48
|
|
|
# available again for further analyses |
49
|
|
|
instrument.setDisposeUntilNextCalibrationTest(False) |
50
|
|
|
|
51
|
|
|
# Need to save the method? |
52
|
|
|
method = self.get_form_value("Method", uid, default=None) |
53
|
|
|
if method is not None: |
54
|
|
|
method = method or None |
55
|
|
|
analysis.setMethod(method) |
56
|
|
|
|
57
|
|
|
# Need to save analyst? |
58
|
|
|
analyst = self.get_form_value("Analyst", uid, default=None) |
59
|
|
|
if analyst is not None: |
60
|
|
|
analysis.setAnalyst(analyst) |
61
|
|
|
|
62
|
|
|
# Save uncertainty |
63
|
|
|
uncertainty = self.get_form_value("Uncertainty", uid, "") |
64
|
|
|
analysis.setUncertainty(uncertainty) |
65
|
|
|
|
66
|
|
|
# Save detection limit |
67
|
|
|
dlimit = self.get_form_value("DetectionLimit", uid, "") |
68
|
|
|
analysis.setDetectionLimitOperand(dlimit) |
69
|
|
|
|
70
|
|
|
# Interim fields |
71
|
|
|
interims = interims_data.get(uid, analysis.getInterimFields()) |
72
|
|
|
analysis.setInterimFields(interims) |
73
|
|
|
|
74
|
|
|
# Save Hidden |
75
|
|
|
hidden = self.get_form_value("Hidden", uid, "") |
76
|
|
|
analysis.setHidden(hidden == "on") |
77
|
|
|
|
78
|
|
|
# Result |
79
|
|
|
result = self.get_form_value("Result", uid, |
80
|
|
|
default=analysis.getResult()) |
81
|
|
|
analysis.setResult(result) |
82
|
|
|
|
83
|
|
|
# Submit all analyses |
84
|
|
|
transitioned = self.do_action(action, objects) |
85
|
|
|
if not transitioned: |
86
|
|
|
return self.redirect(message=_("No changes made"), level="warning") |
87
|
|
|
|
88
|
|
|
# If a reference analysis with an out-of-range result and instrument |
89
|
|
|
# assigned has been submitted, retract then routine analyses that are |
90
|
|
|
# awaiting for verification and with same instrument associated |
91
|
|
|
retracted = list() |
92
|
|
|
for invalid_instrument_uid in invalid_instrument_refs.keys(): |
93
|
|
|
query = dict(getInstrumentUID=invalid_instrument_uid, |
94
|
|
|
portal_type=['Analysis', 'DuplicateAnalysis'], |
95
|
|
|
review_state='to_be_verified', |
96
|
|
|
cancellation_state='active', ) |
97
|
|
|
brains = api.search(query, CATALOG_ANALYSIS_LISTING) |
98
|
|
|
for brain in brains: |
99
|
|
|
analysis = api.get_object(brain) |
100
|
|
|
failed_msg = '{0}: {1}'.format( |
101
|
|
|
ulocalized_time(DateTime(), long_format=1), |
102
|
|
|
_("Instrument failed reference test")) |
103
|
|
|
an_remarks = analysis.getRemarks() |
104
|
|
|
analysis.setRemarks('. '.join([an_remarks, failed_msg])) |
105
|
|
|
retracted.append(analysis) |
106
|
|
|
|
107
|
|
|
# If some analyses have been retracted because instrument failed a |
108
|
|
|
# reference test, then generate a pdf report |
109
|
|
|
if self.do_action("retract", retracted): |
110
|
|
|
# Create the Retracted Analyses List |
111
|
|
|
portal_url = api.get_url(api.get_portal()) |
112
|
|
|
report = AnalysesRetractedListReport(self.context, self.request, |
113
|
|
|
portal_url, |
114
|
|
|
'Retracted analyses', |
115
|
|
|
retracted) |
116
|
|
|
|
117
|
|
|
# Attach the pdf to all ReferenceAnalysis that failed (accessible |
118
|
|
|
# from Instrument's Internal Calibration Tests list |
119
|
|
|
pdf = report.toPdf() |
120
|
|
|
for ref in invalid_instrument_refs.values(): |
121
|
|
|
ref.setRetractedAnalysesPdfReport(pdf) |
122
|
|
|
|
123
|
|
|
# Send the email |
124
|
|
|
try: |
125
|
|
|
report.sendEmail() |
126
|
|
|
except Exception as err_msg: |
127
|
|
|
message = "Unable to send email: {}".format(err_msg) |
128
|
|
|
logger.warn(message) |
129
|
|
|
|
130
|
|
|
# Redirect to success view |
131
|
|
|
return self.success(transitioned) |
132
|
|
|
|
133
|
|
|
def get_interims_data(self): |
134
|
|
|
"""Returns a dictionary with the interims data |
135
|
|
|
""" |
136
|
|
|
form = self.request.form |
137
|
|
|
if 'item_data' not in form: |
138
|
|
|
return {} |
139
|
|
|
|
140
|
|
|
item_data = {} |
141
|
|
|
if type(form['item_data']) == list: |
142
|
|
|
for i_d in form['item_data']: |
143
|
|
|
for i, d in json.loads(i_d).items(): |
144
|
|
|
item_data[i] = d |
145
|
|
|
return item_data |
146
|
|
|
|
147
|
|
|
return json.loads(form['item_data']) |
148
|
|
|
|