Passed
Push — master ( 5f146e...28e0c7 )
by Jordi
04:40
created

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