Passed
Push — master ( f31c62...c3750e )
by Jordi
07:28 queued 03:00
created

AbstractBaseAnalysis.getInstrument()   A

Complexity

Conditions 1

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 3
dl 0
loc 7
rs 10
c 0
b 0
f 0
cc 1
nop 1
1
# -*- coding: utf-8 -*-
2
#
3
# This file is part of SENAITE.CORE
4
#
5
# Copyright 2018 by it's authors.
6
# Some rights reserved. See LICENSE.rst, CONTRIBUTORS.rst.
7
8
from AccessControl import ClassSecurityInfo
9
from bika.lims import api
10
from bika.lims import bikaMessageFactory as _
11
from bika.lims.browser.fields import DurationField
12
from bika.lims.browser.fields import UIDReferenceField
13
from bika.lims.browser.widgets.durationwidget import DurationWidget
14
from bika.lims.browser.widgets.recordswidget import RecordsWidget
15
from bika.lims.browser.widgets.referencewidget import ReferenceWidget
16
from bika.lims.config import ATTACHMENT_OPTIONS
17
from bika.lims.config import SERVICE_POINT_OF_CAPTURE
18
from bika.lims.content.bikaschema import BikaSchema
19
from bika.lims.interfaces import IBaseAnalysis
20
from bika.lims.permissions import FieldEditAnalysisHidden
21
from bika.lims.permissions import FieldEditAnalysisRemarks
22
from bika.lims.permissions import FieldEditAnalysisResult
23
from bika.lims.utils import to_utf8 as _c
24
from Products.Archetypes.BaseContent import BaseContent
25
from Products.Archetypes.Field import BooleanField
26
from Products.Archetypes.Field import FixedPointField
27
from Products.Archetypes.Field import FloatField
28
from Products.Archetypes.Field import IntegerField
29
from Products.Archetypes.Field import StringField
30
from Products.Archetypes.Field import TextField
31
from Products.Archetypes.Schema import Schema
32
from Products.Archetypes.utils import DisplayList
33
from Products.Archetypes.utils import IntDisplayList
34
from Products.Archetypes.Widget import BooleanWidget
35
from Products.Archetypes.Widget import DecimalWidget
36
from Products.Archetypes.Widget import IntegerWidget
37
from Products.Archetypes.Widget import SelectionWidget
38
from Products.Archetypes.Widget import StringWidget
39
from Products.ATExtensions.ateapi import RecordsField
40
from Products.CMFCore.permissions import View
41
from Products.CMFCore.utils import getToolByName
42
from zope.interface import implements
43
44
# Anywhere that there just isn't space for unpredictably long names,
45
# this value will be used instead.  It's set on the AnalysisService,
46
# but accessed on all analysis objects.
47
ShortTitle = StringField(
48
    'ShortTitle',
49
    schemata="Description",
50
    widget=StringWidget(
51
        label=_("Short title"),
52
        description=_(
53
            "If text is entered here, it is used instead of the title when "
54
            "the service is listed in column headings. HTML formatting is "
55
            "allowed.")
56
    )
57
)
58
59
# A simple integer to sort items.
60
SortKey = FloatField(
61
    'SortKey',
62
    schemata="Description",
63
    validators=('SortKeyValidator',),
64
    widget=DecimalWidget(
65
        label=_("Sort Key"),
66
        description=_(
67
            "Float value from 0.0 - 1000.0 indicating the sort order. "
68
            "Duplicate values are ordered alphabetically."),
69
    )
70
)
71
72
# Is the title of the analysis a proper Scientific Name?
73
ScientificName = BooleanField(
74
    'ScientificName',
75
    schemata="Description",
76
    default=False,
77
    widget=BooleanWidget(
78
        label=_("Scientific name"),
79
        description=_(
80
            "If enabled, the name of the analysis will be written in italics."),
81
    )
82
)
83
84
# The units of measurement used for representing results in reports and in
85
# manage_results screen.
86
Unit = StringField(
87
    'Unit',
88
    schemata="Description",
89
    widget=StringWidget(
90
        label=_("Unit"),
91
        description=_(
92
            "The measurement units for this analysis service' results, e.g. "
93
            "mg/l, ppm, dB, mV, etc."),
94
    )
95
)
96
97
# Decimal precision for printing normal decimal results.
98
Precision = IntegerField(
99
    'Precision',
100
    schemata="Analysis",
101
    widget=IntegerWidget(
102
        label=_("Precision as number of decimals"),
103
        description=_(
104
            "Define the number of decimals to be used for this result."),
105
    )
106
)
107
108
# If the precision of the results as entered is higher than this value,
109
# the results will be represented in scientific notation.
110
ExponentialFormatPrecision = IntegerField(
111
    'ExponentialFormatPrecision',
112
    schemata="Analysis",
113
    required=True,
114
    default=7,
115
    widget=IntegerWidget(
116
        label=_("Exponential format precision"),
117
        description=_(
118
            "Define the precision when converting values to exponent "
119
            "notation.  The default is 7."),
120
    )
121
)
122
123
# If the value is below this limit, it means that the measurement lacks
124
# accuracy and this will be shown in manage_results and also on the final
125
# report.
126
LowerDetectionLimit = FixedPointField(
127
    'LowerDetectionLimit',
128
    schemata="Analysis",
129
    default='0.0',
130
    precision=7,
131
    widget=DecimalWidget(
132
        label=_("Lower Detection Limit (LDL)"),
133
        description=_(
134
            "The Lower Detection Limit is the lowest value to which the "
135
            "measured parameter can be measured using the specified testing "
136
            "methodology. Results entered which are less than this value will "
137
            "be reported as < LDL")
138
    )
139
)
140
141
# If the value is above this limit, it means that the measurement lacks
142
# accuracy and this will be shown in manage_results and also on the final
143
# report.
144
UpperDetectionLimit = FixedPointField(
145
    'UpperDetectionLimit',
146
    schemata="Analysis",
147
    default='1000000000.0',
148
    precision=7,
149
    widget=DecimalWidget(
150
        label=_("Upper Detection Limit (UDL)"),
151
        description=_(
152
            "The Upper Detection Limit is the highest value to which the "
153
            "measured parameter can be measured using the specified testing "
154
            "methodology. Results entered which are greater than this value "
155
            "will be reported as > UDL")
156
    )
157
)
158
159
# Allow to select LDL or UDL defaults in results with readonly mode
160
# Some behavior of AnalysisServices is controlled with javascript: If checked,
161
# the field "AllowManualDetectionLimit" will be displayed.
162
# See browser/js/bika.lims.analysisservice.edit.js
163
#
164
# Use cases:
165
# a) If "DetectionLimitSelector" is enabled and
166
# "AllowManualDetectionLimit" is enabled too, then:
167
# the analyst will be able to select an '>', '<' operand from the
168
# selection list and also set the LD manually.
169
#
170
# b) If "DetectionLimitSelector" is enabled and
171
# "AllowManualDetectionLimit" is unchecked, the analyst will be
172
# able to select an operator from the selection list, but not set
173
# the LD manually: the default LD will be displayed in the result
174
# field as usuall, but in read-only mode.
175
#
176
# c) If "DetectionLimitSelector" is disabled, no LD selector will be
177
# displayed in the results table.
178
DetectionLimitSelector = BooleanField(
179
    'DetectionLimitSelector',
180
    schemata="Analysis",
181
    default=False,
182
    widget=BooleanWidget(
183
        label=_("Display a Detection Limit selector"),
184
        description=_(
185
            "If checked, a selection list will be displayed next to the "
186
            "analysis' result field in results entry views. By using this "
187
            "selector, the analyst will be able to set the value as a "
188
            "Detection Limit (LDL or UDL) instead of a regular result"),
189
    )
190
)
191
192
# Behavior of AnalysisService controlled with javascript: Only visible when the
193
# "DetectionLimitSelector" is checked
194
# See browser/js/bika.lims.analysisservice.edit.js
195
# Check inline comment for "DetecionLimitSelector" field for
196
# further information.
197
AllowManualDetectionLimit = BooleanField(
198
    'AllowManualDetectionLimit',
199
    schemata="Analysis",
200
    default=False,
201
    widget=BooleanWidget(
202
        label=_("Allow Manual Detection Limit input"),
203
        description=_(
204
            "Allow the analyst to manually replace the default Detection "
205
            "Limits (LDL and UDL) on results entry views"),
206
    )
207
)
208
209
# Specify attachment requirements for these analyses
210
AttachmentOption = StringField(
211
    'AttachmentOption',
212
    schemata="Analysis",
213
    default='p',
214
    vocabulary=ATTACHMENT_OPTIONS,
215
    widget=SelectionWidget(
216
        label=_("Attachment Option"),
217
        description=_(
218
            "Indicates whether file attachments, e.g. microscope images, "
219
            "are required for this analysis and whether file upload function "
220
            "will be available for it on data capturing screens"),
221
        format='select',
222
    )
223
)
224
225
# The keyword for the service is used as an identifier during instrument
226
# imports, and other places too.  It's also used as the ID analyses.
227
Keyword = StringField(
228
    'Keyword',
229
    schemata="Description",
230
    required=1,
231
    searchable=True,
232
    validators=('servicekeywordvalidator',),
233
    widget=StringWidget(
234
        label=_("Analysis Keyword"),
235
        description=_(
236
            "The unique keyword used to identify the analysis service in "
237
            "import files of bulk Sample requests and results imports from "
238
            "instruments. It is also used to identify dependent analysis "
239
            "services in user defined results calculations"),
240
    )
241
)
242
243
# Allow/Disallow manual entry of results
244
# Behavior of AnalysisServices controlled by javascript depending on
245
# Instruments field:
246
# - If InstrumentEntry not checked, set checked and readonly
247
# - If InstrumentEntry checked, set as not readonly
248
# See browser/js/bika.lims.analysisservice.edit.js
249
ManualEntryOfResults = BooleanField(
250
    'ManualEntryOfResults',
251
    schemata="Method",
252
    default=True,
253
    widget=BooleanWidget(
254
        label=_("Instrument assignment is not required"),
255
        description=_(
256
            "Select if the results for tests of this type of analysis can be "
257
            "set manually. If selected, the user will be able to set a result "
258
            "for a test of this type of analysis in manage results view "
259
            "without the need of selecting an instrument, even though the "
260
            "method selected for the test has instruments assigned."),
261
    )
262
)
263
264
# Allow/Disallow instrument entry of results
265
# Behavior controlled by javascript depending on Instruments field:
266
# - If no instruments available, hide and uncheck
267
# - If at least one instrument selected, checked, but not readonly
268
# See browser/js/bika.lims.analysisservice.edit.js
269
InstrumentEntryOfResults = BooleanField(
270
    'InstrumentEntryOfResults',
271
    schemata="Method",
272
    default=False,
273
    widget=BooleanWidget(
274
        label=_("Instrument assignment is allowed"),
275
        description=_(
276
            "Select if the results for tests of this type of analysis can be "
277
            "set by using an instrument. If disabled, no instruments will be "
278
            "available for tests of this type of analysis in manage results "
279
            "view, even though the method selected for the test has "
280
            "instruments assigned."),
281
    )
282
)
283
284
# Default instrument to be used.
285
# Gets populated with the instruments selected in the Instruments field.
286
# Behavior of AnalysisServices controlled by js depending on
287
# ManualEntry/Instruments:
288
# - Populate dynamically with selected Instruments
289
# - If InstrumentEntry checked, set first selected instrument
290
# - If InstrumentEntry not checked, hide and set None
291
# See browser/js/bika.lims.analysisservice.edit.js
292
Instrument = UIDReferenceField(
293
    "Instrument",
294
    read_permission=View,
295
    write_permission=FieldEditAnalysisResult,
296
    schemata="Method",
297
    searchable=True,
298
    required=0,
299
    vocabulary="_getAvailableInstrumentsDisplayList",
300
    allowed_types=("Instrument",),
301
    accessor="getInstrumentUID",
302
    widget=SelectionWidget(
303
        format="select",
304
        label=_("Default Instrument"),
305
        description=_(
306
            "This is the instrument that is assigned to  tests from this type "
307
            "of analysis in manage results view. The method associated to "
308
            "this instrument will be assigned as the default method too.Note "
309
            "the instrument's method will prevail over any of the methods "
310
            "choosen if the 'Instrument assignment is not required' option is "
311
            "enabled.")
312
    )
313
)
314
315
# Default method to be used. This field is used in Analysis Service
316
# Edit view, use getMethod() to retrieve the Method to be used in
317
# this Analysis Service.
318
# Gets populated with the methods selected in the multiselection
319
# box above or with the default instrument's method.
320
# Behavior controlled by js depending on ManualEntry/Instrument/Methods:
321
# - If InstrumentEntry checked, set instrument's default method, and readonly
322
# - If InstrumentEntry not checked, populate dynamically with
323
#   selected Methods, set the first method selected and non-readonly
324
# See browser/js/bika.lims.analysisservice.edit.js
325
Method = UIDReferenceField(
326
    "Method",
327
    read_permission=View,
328
    write_permission=FieldEditAnalysisResult,
329
    schemata="Method",
330
    required=0,
331
    searchable=True,
332
    allowed_types=("Method",),
333
    vocabulary="_getAvailableMethodsDisplayList",
334
    accessor="getMethodUID",
335
    widget=SelectionWidget(
336
        format="select",
337
        label=_("Default Method"),
338
        description=_(
339
            "If 'Allow instrument entry of results' is selected, the method "
340
            "from the default instrument will be used. Otherwise, only the "
341
            "methods selected above will be displayed.")
342
    )
343
)
344
345
# Maximum time (from sample reception) allowed for the analysis to be performed.
346
# After this amount of time, a late alert is printed, and the analysis will be
347
# flagged in turnaround time report.
348
MaxTimeAllowed = DurationField(
349
    'MaxTimeAllowed',
350
    schemata="Analysis",
351
    widget=DurationWidget(
352
        label=_("Maximum turn-around time"),
353
        description=_(
354
            "Maximum time allowed for completion of the analysis. A late "
355
            "analysis alert is raised when this period elapses"),
356
    )
357
)
358
359
# The amount of difference allowed between this analysis, and any duplicates.
360
DuplicateVariation = FixedPointField(
361
    'DuplicateVariation',
362
    default='0.00',
363
    schemata="Method",
364
    widget=DecimalWidget(
365
        label=_("Duplicate Variation %"),
366
        description=_(
367
            "When the results of duplicate analyses on worksheets, carried "
368
            "out on the same sample, differ with more than this percentage, "
369
            "an alert is raised"),
370
    )
371
)
372
373
# True if the accreditation body has approved this lab's method for
374
# accreditation.
375
Accredited = BooleanField(
376
    'Accredited',
377
    schemata="Method",
378
    default=False,
379
    widget=BooleanWidget(
380
        label=_("Accredited"),
381
        description=_(
382
            "Check this box if the analysis service is included in the "
383
            "laboratory's schedule of accredited analyses"),
384
    )
385
)
386
387
# The physical location that the analysis is tested; for some analyses,
388
# the sampler may capture results at the point where the sample is taken,
389
# and these results can be captured using different rules.  For example,
390
# the results may be entered before the sample is received.
391
PointOfCapture = StringField(
392
    'PointOfCapture',
393
    schemata="Description",
394
    required=1,
395
    default='lab',
396
    vocabulary=SERVICE_POINT_OF_CAPTURE,
397
    widget=SelectionWidget(
398
        format='flex',
399
        label=_("Point of Capture"),
400
        description=_(
401
            "The results of field analyses are captured during sampling at "
402
            "the sample point, e.g. the temperature of a water sample in the "
403
            "river where it is sampled. Lab analyses are done in the "
404
            "laboratory"),
405
    )
406
)
407
408
# The category of the analysis service, used for filtering, collapsing and
409
# reporting on analyses.
410
Category = UIDReferenceField(
411
    'Category',
412
    schemata="Description",
413
    required=1,
414
    allowed_types=('AnalysisCategory',),
415
    vocabulary='getAnalysisCategories',
416
    widget=ReferenceWidget(
417
        checkbox_bound=0,
418
        label=_("Analysis Category"),
419
        description=_("The category the analysis service belongs to"),
420
        catalog_name='bika_setup_catalog',
421
        base_query={'is_active': True},
422
    )
423
)
424
425
# The base price for this analysis
426
Price = FixedPointField(
427
    'Price',
428
    schemata="Description",
429
    default='0.00',
430
    widget=DecimalWidget(
431
        label=_("Price (excluding VAT)"),
432
    )
433
)
434
435
# Some clients qualify for bulk discounts.
436
BulkPrice = FixedPointField(
437
    'BulkPrice',
438
    schemata="Description",
439
    default='0.00',
440
    widget=DecimalWidget(
441
        label=_("Bulk price (excluding VAT)"),
442
        description=_(
443
            "The price charged per analysis for clients who qualify for bulk "
444
            "discounts"),
445
    )
446
)
447
448
# If VAT is charged, a different VAT value can be entered for each
449
# service.  The default value is taken from BikaSetup
450
VAT = FixedPointField(
451
    'VAT',
452
    schemata="Description",
453
    default_method='getDefaultVAT',
454
    widget=DecimalWidget(
455
        label=_("VAT %"),
456
        description=_("Enter percentage value eg. 14.0"),
457
    )
458
)
459
460
# The analysis service's Department.  This is used to filter analyses,
461
# and for indicating the responsibile lab manager in reports.
462
Department = UIDReferenceField(
463
    'Department',
464
    schemata="Description",
465
    required=0,
466
    allowed_types=('Department',),
467
    vocabulary='getDepartments',
468
    widget=ReferenceWidget(
469
        checkbox_bound=0,
470
        label=_("Department"),
471
        description=_("The laboratory department"),
472
        catalog_name='bika_setup_catalog',
473
        base_query={'is_active': True},
474
    )
475
)
476
477
# Uncertainty percentages in results can change depending on the results
478
# themselves.
479
Uncertainties = RecordsField(
480
    'Uncertainties',
481
    schemata="Uncertainties",
482
    type='uncertainties',
483
    subfields=('intercept_min', 'intercept_max', 'errorvalue'),
484
    required_subfields=(
485
        'intercept_min', 'intercept_max', 'errorvalue'),
486
    subfield_sizes={'intercept_min': 10,
487
                    'intercept_max': 10,
488
                    'errorvalue': 10,
489
                    },
490
    subfield_labels={'intercept_min': _('Range min'),
491
                     'intercept_max': _('Range max'),
492
                     'errorvalue': _('Uncertainty value'),
493
                     },
494
    subfield_validators={'intercept_min': 'uncertainties_validator',
495
                         'intercept_max': 'uncertainties_validator',
496
                         'errorvalue': 'uncertainties_validator',
497
                         },
498
    widget=RecordsWidget(
499
        label=_("Uncertainty"),
500
        description=_(
501
            "Specify the uncertainty value for a given range, e.g. for "
502
            "results in a range with minimum of 0 and maximum of 10, "
503
            "where the uncertainty value is 0.5 - a result of 6.67 will be "
504
            "reported as 6.67 +- 0.5. You can also specify the uncertainty "
505
            "value as a percentage of the result value, by adding a '%' to "
506
            "the value entered in the 'Uncertainty Value' column, e.g. for "
507
            "results in a range with minimum of 10.01 and a maximum of 100, "
508
            "where the uncertainty value is 2% - a result of 100 will be "
509
            "reported as 100 +- 2. Please ensure successive ranges are "
510
            "continuous, e.g. 0.00 - 10.00 is followed by 10.01 - 20.00, "
511
            "20.01 - 30 .00 etc."),
512
    )
513
)
514
515
# Calculate the precision from Uncertainty value
516
# Behavior controlled by javascript
517
# - If checked, Precision and ExponentialFormatPrecision are not displayed.
518
#   The precision will be calculated according to the uncertainty.
519
# - If checked, Precision and ExponentialFormatPrecision will be displayed.
520
# See browser/js/bika.lims.analysisservice.edit.js
521
PrecisionFromUncertainty = BooleanField(
522
    'PrecisionFromUncertainty',
523
    schemata="Uncertainties",
524
    default=False,
525
    widget=BooleanWidget(
526
        label=_("Calculate Precision from Uncertainties"),
527
        description=_(
528
            "Precision as the number of significant digits according to the "
529
            "uncertainty. The decimal position will be given by the first "
530
            "number different from zero in the uncertainty, at that position "
531
            "the system will round up the uncertainty and results. For "
532
            "example, with a result of 5.243 and an uncertainty of 0.22, "
533
            "the system will display correctly as 5.2+-0.2. If no uncertainty "
534
            "range is set for the result, the system will use the fixed "
535
            "precision set."),
536
    )
537
)
538
539
# If checked, an additional input with the default uncertainty will
540
# be displayed in the manage results view. The value set by the user
541
# in this field will override the default uncertainty for the analysis
542
# result
543
AllowManualUncertainty = BooleanField(
544
    'AllowManualUncertainty',
545
    schemata="Uncertainties",
546
    default=False,
547
    widget=BooleanWidget(
548
        label=_("Allow manual uncertainty value input"),
549
        description=_(
550
            "Allow the analyst to manually replace the default uncertainty "
551
            "value."),
552
    )
553
)
554
555
# Results can be selected from a dropdown list.  This prevents the analyst
556
# from entering arbitrary values.  Each result must have a ResultValue, which
557
# must be a number - it is this number which is interpreted as the actual
558
# "Result" when applying calculations.
559
ResultOptions = RecordsField(
560
    'ResultOptions',
561
    schemata="Result Options",
562
    type='resultsoptions',
563
    subfields=('ResultValue', 'ResultText'),
564
    required_subfields=('ResultValue', 'ResultText'),
565
    subfield_labels={'ResultValue': _('Result Value'),
566
                     'ResultText': _('Display Value'), },
567
    subfield_validators={'ResultValue': 'resultoptionsvalidator',
568
                         'ResultText': 'resultoptionsvalidator'},
569
    subfield_sizes={'ResultValue': 5,
570
                    'ResultText': 25,},
571
    subfield_maxlength={'ResultValue': 5,
572
                        'ResultText': 255,},
573
    widget=RecordsWidget(
574
        label=_("Result Options"),
575
        description=_(
576
            "Please list all options for the analysis result if you want to "
577
            "restrict it to specific options only, e.g. 'Positive', "
578
            "'Negative' and 'Indeterminable'.  The option's result value must "
579
            "be a number"),
580
    )
581
)
582
583
# If the service is meant for providing an interim result to another analysis,
584
# or if the result is only used for internal processes, then it can be hidden
585
# from the client in the final report (and in manage_results view)
586
Hidden = BooleanField(
587
    'Hidden',
588
    schemata="Analysis",
589
    default=False,
590
    read_permission=View,
591
    write_permission=FieldEditAnalysisHidden,
592
    widget=BooleanWidget(
593
        label=_("Hidden"),
594
        description=_(
595
            "If enabled, this analysis and its results will not be displayed "
596
            "by default in reports. This setting can be overrided in Analysis "
597
            "Profile and/or Sample"),
598
    )
599
)
600
601
# Permit a user to verify their own results.  This could invalidate the
602
# accreditation for the results of this analysis!
603
SelfVerification = IntegerField(
604
    'SelfVerification',
605
    schemata="Analysis",
606
    default=-1,
607
    vocabulary="_getSelfVerificationVocabulary",
608
    widget=SelectionWidget(
609
        label=_("Self-verification of results"),
610
        description=_(
611
            "If enabled, a user who submitted a result for this analysis "
612
            "will also be able to verify it. This setting take effect for "
613
            "those users with a role assigned that allows them to verify "
614
            "results (by default, managers, labmanagers and verifiers). "
615
            "The option set here has priority over the option set in Bika "
616
            "Setup"),
617
        format="select",
618
    )
619
)
620
621
# Require more than one verification by separate Verifier or LabManager users.
622
NumberOfRequiredVerifications = IntegerField(
623
    'NumberOfRequiredVerifications',
624
    schemata="Analysis",
625
    default=-1,
626
    vocabulary="_getNumberOfRequiredVerificationsVocabulary",
627
    widget=SelectionWidget(
628
        label=_("Number of required verifications"),
629
        description=_(
630
            "Number of required verifications from different users with "
631
            "enough privileges before a given result for this analysis "
632
            "being considered as 'verified'. The option set here has "
633
            "priority over the option set in Bika Setup"),
634
        format="select",
635
    )
636
)
637
638
# Just a string displayed on various views
639
CommercialID = StringField(
640
    'CommercialID',
641
    searchable=1,
642
    schemata='Description',
643
    required=0,
644
    widget=StringWidget(
645
        label=_("Commercial ID"),
646
        description=_("The service's commercial ID for accounting purposes")
647
    )
648
)
649
650
# Just a string displayed on various views
651
ProtocolID = StringField(
652
    'ProtocolID',
653
    searchable=1,
654
    schemata='Description',
655
    required=0,
656
    widget=StringWidget(
657
        label=_("Protocol ID"),
658
        description=_("The service's analytical protocol ID")
659
    )
660
)
661
662
# Remarks are used in various ways by almost all objects in the system.
663
Remarks = TextField(
664
    'Remarks',
665
    read_permission=View,
666
    write_permission=FieldEditAnalysisRemarks,
667
    schemata='Description'
668
)
669
670
schema = BikaSchema.copy() + Schema((
671
    ShortTitle,
672
    SortKey,
673
    CommercialID,
674
    ProtocolID,
675
    ScientificName,
676
    Unit,
677
    Precision,
678
    ExponentialFormatPrecision,
679
    LowerDetectionLimit,
680
    UpperDetectionLimit,
681
    DetectionLimitSelector,
682
    AllowManualDetectionLimit,
683
    AttachmentOption,
684
    Keyword,
685
    ManualEntryOfResults,
686
    InstrumentEntryOfResults,
687
    Instrument,
688
    Method,
689
    MaxTimeAllowed,
690
    DuplicateVariation,
691
    Accredited,
692
    PointOfCapture,
693
    Category,
694
    Price,
695
    BulkPrice,
696
    VAT,
697
    Department,
698
    Uncertainties,
699
    PrecisionFromUncertainty,
700
    AllowManualUncertainty,
701
    ResultOptions,
702
    Hidden,
703
    SelfVerification,
704
    NumberOfRequiredVerifications,
705
    Remarks
706
))
707
708
schema['id'].widget.visible = False
709
schema['description'].schemata = 'Description'
710
schema['description'].widget.visible = True
711
schema['title'].required = True
712
schema['title'].widget.visible = True
713
schema['title'].schemata = 'Description'
714
schema['title'].validators = ()
715
# Update the validation layer after change the validator in runtime
716
schema['title']._validationLayer()
717
718
719
class AbstractBaseAnalysis(BaseContent):  # TODO BaseContent?  is really needed?
720
    implements(IBaseAnalysis)
721
    security = ClassSecurityInfo()
722
    schema = schema
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable schema does not seem to be defined.
Loading history...
723
    displayContentsTab = False
724
725
    @security.public
726
    def _getCatalogTool(self):
727
        from bika.lims.catalog import getCatalog
728
        return getCatalog(self)
729
730
    @security.public
731
    def Title(self):
732
        return _c(self.title)
733
734
    @security.public
735
    def getDefaultVAT(self):
736
        """Return default VAT from bika_setup
737
        """
738
        try:
739
            vat = self.bika_setup.getVAT()
740
            return vat
741
        except ValueError:
742
            return "0.00"
743
744
    @security.public
745
    def getVATAmount(self):
746
        """Compute VAT Amount from the Price and system configured VAT
747
        """
748
        price, vat = self.getPrice(), self.getVAT()
749
        return float(price) * (float(vat) / 100)
750
751
    @security.public
752
    def getDiscountedPrice(self):
753
        """Compute discounted price excl. VAT
754
        """
755
        price = self.getPrice()
756
        price = price and price or 0
757
        discount = self.bika_setup.getMemberDiscount()
758
        discount = discount and discount or 0
759
        return float(price) - (float(price) * float(discount)) / 100
760
761
    @security.public
762
    def getDiscountedBulkPrice(self):
763
        """Compute discounted bulk discount excl. VAT
764
        """
765
        price = self.getBulkPrice()
766
        price = price and price or 0
767
        discount = self.bika_setup.getMemberDiscount()
768
        discount = discount and discount or 0
769
        return float(price) - (float(price) * float(discount)) / 100
770
771
    @security.public
772
    def getTotalPrice(self):
773
        """Compute total price including VAT
774
        """
775
        price = self.getPrice()
776
        vat = self.getVAT()
777
        price = price and price or 0
778
        vat = vat and vat or 0
779
        return float(price) + (float(price) * float(vat)) / 100
780
781
    @security.public
782
    def getTotalBulkPrice(self):
783
        """Compute total bulk price
784
        """
785
        price = self.getBulkPrice()
786
        vat = self.getVAT()
787
        price = price and price or 0
788
        vat = vat and vat or 0
789
        return float(price) + (float(price) * float(vat)) / 100
790
791
    @security.public
792
    def getTotalDiscountedPrice(self):
793
        """Compute total discounted price
794
        """
795
        price = self.getDiscountedPrice()
796
        vat = self.getVAT()
797
        price = price and price or 0
798
        vat = vat and vat or 0
799
        return float(price) + (float(price) * float(vat)) / 100
800
801
    @security.public
802
    def getTotalDiscountedBulkPrice(self):
803
        """Compute total discounted corporate bulk price
804
        """
805
        price = self.getDiscountedCorporatePrice()
806
        vat = self.getVAT()
807
        price = price and price or 0
808
        vat = vat and vat or 0
809
        return float(price) + (float(price) * float(vat)) / 100
810
811
    @security.public
812
    def getAnalysisCategories(self):
813
        """A vocabulary listing available (and activated) categories.
814
        """
815
        bsc = getToolByName(self, 'bika_setup_catalog')
816
        cats = bsc(portal_type='AnalysisCategory', is_active=True)
817
        items = [(o.UID, o.Title) for o in cats]
818
        o = self.getCategory()
819
        if o and o.UID() not in [i[0] for i in items]:
820
            items.append((o.UID(), o.Title()))
821
        items.sort(lambda x, y: cmp(x[1], y[1]))
822
        return DisplayList(list(items))
823
824 View Code Duplication
    @security.public
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
825
    def getDepartments(self):
826
        """A vocabulary listing available (and activated) departments.
827
        """
828
        bsc = getToolByName(self, 'bika_setup_catalog')
829
        items = [('', '')] + [(o.UID, o.Title) for o in
830
                              bsc(portal_type='Department',
831
                                  is_active=True)]
832
        o = self.getDepartment()
833
        if o and o.UID() not in [i[0] for i in items]:
834
            items.append((o.UID(), o.Title()))
835
        items.sort(lambda x, y: cmp(x[1], y[1]))
836
        return DisplayList(list(items))
837
838
    @security.public
839
    def getLowerDetectionLimit(self):
840
        """Returns the Lower Detection Limit for this service as a floatable
841
        """
842
        ldl = self.getField('LowerDetectionLimit').get(self)
843
        try:
844
            return float(ldl)
845
        except ValueError:
846
            return 0
847
848
    @security.public
849
    def getUpperDetectionLimit(self):
850
        """Returns the Upper Detection Limit for this service as a floatable
851
        """
852
        udl = self.getField('UpperDetectionLimit').get(self)
853
        try:
854
            return float(udl)
855
        except ValueError:
856
            return 0
857
858
    @security.public
859
    def isSelfVerificationEnabled(self):
860
        """Returns if the user that submitted a result for this analysis must
861
        also be able to verify the result
862
        :returns: true or false
863
        """
864
        bsve = self.bika_setup.getSelfVerificationEnabled()
865
        vs = self.getSelfVerification()
866
        return bsve if vs == -1 else vs == 1
867
868
    @security.public
869
    def _getSelfVerificationVocabulary(self):
870
        """Returns a DisplayList with the available options for the
871
        self-verification list: 'system default', 'true', 'false'
872
        :returns: DisplayList with the available options for the
873
        self-verification list
874
        """
875
        bsve = self.bika_setup.getSelfVerificationEnabled()
876
        bsve = _('Yes') if bsve else _('No')
877
        bsval = "%s (%s)" % (_("System default"), bsve)
878
        items = [(-1, bsval), (0, _('No')), (1, _('Yes'))]
879
        return IntDisplayList(list(items))
880
881
    @security.public
882
    def getNumberOfRequiredVerifications(self):
883
        """Returns the number of required verifications a test for this
884
        analysis requires before being transitioned to 'verified' state
885
        :returns: number of required verifications
886
        """
887
        num = self.getField('NumberOfRequiredVerifications').get(self)
888
        if num < 1:
889
            return self.bika_setup.getNumberOfRequiredVerifications()
890
        return num
891
892
    @security.public
893
    def _getNumberOfRequiredVerificationsVocabulary(self):
894
        """Returns a DisplayList with the available options for the
895
        multi-verification list: 'system default', '1', '2', '3', '4'
896
        :returns: DisplayList with the available options for the
897
        multi-verification list
898
        """
899
        bsve = self.bika_setup.getNumberOfRequiredVerifications()
900
        bsval = "%s (%s)" % (_("System default"), str(bsve))
901
        items = [(-1, bsval), (1, '1'), (2, '2'), (3, '3'), (4, '4')]
902
        return IntDisplayList(list(items))
903
904
    @security.public
905
    def getMethodTitle(self):
906
        """This is used to populate catalog values
907
        """
908
        method = self.getMethod()
909
        if method:
910
            return method.Title()
911
912
    @security.public
913
    def getMethod(self):
914
        """Returns the assigned method
915
916
        :returns: Method object
917
        """
918
        return self.getField("Method").get(self)
919
920
    @security.public
921
    def getMethodUID(self):
922
        """Returns the UID of the assigned method
923
924
        NOTE: This is the default accessor of the `Method` schema field
925
        and needed for the selection widget to render the selected value
926
        properly in _view_ mode.
927
928
        :returns: Method UID
929
        """
930
        method = self.getMethod()
931
        if not method:
932
            return None
933
        return api.get_uid(method)
934
935
    @security.public
936
    def getMethodURL(self):
937
        """This is used to populate catalog values
938
        """
939
        method = self.getMethod()
940
        if method:
941
            return method.absolute_url_path()
942
943
    @security.public
944
    def getInstrumentTitle(self):
945
        """Used to populate catalog values
946
        """
947
        instrument = self.getInstrument()
948
        if instrument:
949
            return instrument.Title()
950
951
    @security.public
952
    def getInstrument(self):
953
        """Returns the assigned instrument
954
955
        :returns: Instrument object
956
        """
957
        return self.getField("Instrument").get(self)
958
959
    @security.public
960
    def getInstrumentUID(self):
961
        """Returns the UID of the assigned instrument
962
963
        NOTE: This is the default accessor of the `Instrument` schema field
964
        and needed for the selection widget to render the selected value
965
        properly in _view_ mode.
966
967
        :returns: Method UID
968
        """
969
        instrument = self.getInstrument()
970
        if not instrument:
971
            return None
972
        return api.get_uid(instrument)
973
974
    @security.public
975
    def getInstrumentURL(self):
976
        """Used to populate catalog values
977
        """
978
        instrument = self.getInstrument()
979
        if instrument:
980
            return instrument.absolute_url_path()
981
982
    @security.public
983
    def getCategoryTitle(self):
984
        """Used to populate catalog values
985
        """
986
        category = self.getCategory()
987
        if category:
988
            return category.Title()
989
990
    @security.public
991
    def getCategoryUID(self):
992
        """Used to populate catalog values
993
        """
994
        category = self.getCategory()
995
        if category:
996
            return category.UID()
997
998
    @security.public
999
    def getDepartmentTitle(self):
1000
        """Used to populate catalog values
1001
        """
1002
        department = self.getDepartment()
1003
        if department:
1004
            return department.Title()
1005
1006
    @security.public
1007
    def getMaxTimeAllowed(self):
1008
        """Returns the maximum turnaround time for this analysis. If no TAT is
1009
        set for this particular analysis, it returns the value set at setup
1010
        return: a dictionary with the keys "days", "hours" and "minutes"
1011
        """
1012
        tat = self.Schema().getField("MaxTimeAllowed").get(self)
1013
        return tat or self.bika_setup.getDefaultTurnaroundTime()
1014
1015
    @security.public
1016
    def isOpen(self):
1017
        """Checks if the Analysis is either in "assigned" or "unassigned" state
1018
1019
        return: True if the WF state is either "assigned" or "unassigned"
1020
        """
1021
        return api.get_workflow_status_of(self) in ["assigned", "unassigned"]
1022
1023
    @security.public
1024
    def isRegistered(self):
1025
        """Checks if the Analysis is in "registered" state
1026
1027
        return: True if the WF state is "registered"
1028
        """
1029
        return api.get_workflow_status_of(self) in ["registered"]
1030