Passed
Push — master ( 6d6a8c...6374c8 )
by Ramon
05:17
created

  A

Complexity

Conditions 2

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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