Passed
Push — 2.x ( 00f734...bf6542 )
by Jordi
07:33
created

bika.lims.content.bikasetup   B

Complexity

Total Complexity 44

Size/Duplication

Total Lines 1131
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 44
eloc 743
dl 0
loc 1131
rs 8.737
c 0
b 0
f 0

22 Methods

Rating   Name   Duplication   Size   Complexity  
A IDFormattingField.getSequenceTypes() 0 5 1
A IDFormattingField.getCounterTypes() 0 5 1
A BikaSetup.setAutoLogOff() 0 11 3
A BikaSetup.getPrefixFor() 0 9 2
A BikaSetup.isRejectionWorkflowEnabled() 0 11 3
A BikaSetup.getRejectionReasonsItems() 0 9 4
A BikaSetup.getCategorizeSampleAnalyses() 0 8 2
A BikaSetup.getStickerTemplates() 0 5 1
A BikaSetup.getAutoLogOff() 0 8 2
A BikaSetup.getIDServerValuesHTML() 0 8 2
A BikaSetup.setEnableGlobalAuditlog() 0 7 2
A BikaSetup.getSampleAnalysesRequired() 0 8 2
A BikaSetup.getImmediateResultsEntry() 0 8 2
A BikaSetup.getAnalysisServicesVocabulary() 0 11 2
A BikaSetup.getCountries() 0 4 2
A BikaSetup.setSampleAnalysesRequired() 0 7 2
A BikaSetup.getEmailBodySamplePublication() 0 7 2
A BikaSetup.setCategorizeSampleAnalyses() 0 7 2
A BikaSetup.setImmediateResultsEntry() 0 7 2
A BikaSetup.setEmailBodySamplePublication() 0 7 2
A BikaSetup.getEnableGlobalAuditlog() 0 8 2
A BikaSetup._getNumberOfRequiredVerificationsVocabulary() 0 9 1

How to fix   Complexity   

Complexity

Complex classes like bika.lims.content.bikasetup often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
# -*- coding: utf-8 -*-
2
#
3
# This file is part of SENAITE.CORE.
4
#
5
# SENAITE.CORE is free software: you can redistribute it and/or modify it under
6
# the terms of the GNU General Public License as published by the Free Software
7
# Foundation, version 2.
8
#
9
# This program is distributed in the hope that it will be useful, but WITHOUT
10
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
12
# details.
13
#
14
# You should have received a copy of the GNU General Public License along with
15
# this program; if not, write to the Free Software Foundation, Inc., 51
16
# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17
#
18
# Copyright 2018-2021 by it's authors.
19
# Some rights reserved, see README and LICENSE.
20
21
from AccessControl import ClassSecurityInfo
22
from bika.lims import api
23
from bika.lims import bikaMessageFactory as _
24
from bika.lims.browser.fields import DurationField
25
from bika.lims.browser.fields import UIDReferenceField
26
from bika.lims.browser.widgets import DurationWidget
27
from bika.lims.browser.widgets import RecordsWidget
28
from bika.lims.browser.widgets import ReferenceWidget
29
from bika.lims.browser.widgets import RejectionSetupWidget
30
from bika.lims.browser.worksheet.tools import getWorksheetLayouts
31
from bika.lims.config import CURRENCIES
32
from bika.lims.config import DECIMAL_MARKS
33
from bika.lims.config import DEFAULT_WORKSHEET_LAYOUT
34
from bika.lims.config import MULTI_VERIFICATION_TYPE
35
from bika.lims.config import PROJECTNAME
36
from bika.lims.config import SCINOTATION_OPTIONS
37
from bika.lims.config import WEEKDAYS
38
from bika.lims.content.bikaschema import BikaFolderSchema
39
from bika.lims.interfaces import IBikaSetup
40
from bika.lims.numbergenerator import INumberGenerator
41
from bika.lims.vocabularies import getStickerTemplates as _getStickerTemplates
42
from plone.app.folder import folder
43
from Products.Archetypes.atapi import BooleanField
44
from Products.Archetypes.atapi import BooleanWidget
45
from Products.Archetypes.atapi import DecimalWidget
46
from Products.Archetypes.atapi import FixedPointField
47
from Products.Archetypes.atapi import InAndOutWidget
48
from Products.Archetypes.atapi import IntegerField
49
from Products.Archetypes.atapi import IntegerWidget
50
from Products.Archetypes.atapi import LinesField
51
from Products.Archetypes.atapi import Schema
52
from Products.Archetypes.atapi import SelectionWidget
53
from Products.Archetypes.atapi import StringField
54
from Products.Archetypes.atapi import TextAreaWidget
55
from Products.Archetypes.atapi import registerType
56
from Products.Archetypes.Field import TextField
57
from Products.Archetypes.utils import DisplayList
58
from Products.Archetypes.utils import IntDisplayList
59
from Products.Archetypes.Widget import RichWidget
60
from Products.CMFCore.utils import getToolByName
61
from senaite.core.api import geo
62
from senaite.core.browser.fields.records import RecordsField
63
from senaite.core.interfaces import IHideActionsMenu
64
from senaite.core.p3compat import cmp
65
from zope.component import getUtility
66
from zope.interface import implements
67
68
69
class IDFormattingField(RecordsField):
70
    """A list of prefixes per portal_type
71
    """
72
    _properties = RecordsField._properties.copy()
73
    _properties.update({
74
        'type': 'prefixes',
75
        'subfields': (
76
            'portal_type',
77
            'form',
78
            'sequence_type',
79
            'context',
80
            'counter_type',
81
            'counter_reference',
82
            'prefix',
83
            'split_length'
84
        ),
85
        'subfield_labels': {
86
            'portal_type': 'Portal Type',
87
            'form': 'Format',
88
            'sequence_type': 'Seq Type',
89
            'context': 'Context',
90
            'counter_type': 'Counter Type',
91
            'counter_reference': 'Counter Ref',
92
            'prefix': 'Prefix',
93
            'split_length': 'Split Length',
94
        },
95
        'subfield_readonly': {
96
            'portal_type': False,
97
            'form': False,
98
            'sequence_type': False,
99
            'context': False,
100
            'counter_type': False,
101
            'counter_reference': False,
102
            'prefix': False,
103
            'split_length': False,
104
        },
105
        'subfield_sizes': {
106
            'portal_type': 20,
107
            'form': 30,
108
            'sequence_type': 1,
109
            'context': 12,
110
            'counter_type': 1,
111
            'counter_reference': 12,
112
            'prefix': 12,
113
            'split_length': 5,
114
        },
115
        'subfield_types': {
116
            'sequence_type': 'selection',
117
            'counter_type': 'selection',
118
            'split_length': 'int',
119
        },
120
        'subfield_vocabularies': {
121
            'sequence_type': 'getSequenceTypes',
122
            'counter_type': 'getCounterTypes',
123
        },
124
        'subfield_maxlength': {
125
            'form': 256,
126
        },
127
    })
128
129
    security = ClassSecurityInfo()
130
131
    def getSequenceTypes(self, instance=None):
132
        return DisplayList([
133
            ('', ''),
134
            ('counter', 'Counter'),
135
            ('generated', 'Generated')
136
        ])
137
138
    def getCounterTypes(self, instance=None):
139
        return DisplayList([
140
            ('', ''),
141
            ('backreference', 'Backreference'),
142
            ('contained', 'Contained')
143
        ])
144
145
146
STICKER_AUTO_OPTIONS = DisplayList((
147
    ('None', _('None')),
148
    ('register', _('Register')),
149
    ('receive', _('Receive')),
150
))
151
152
153
schema = BikaFolderSchema.copy() + Schema((
154
    IntegerField(
155
        'AutoLogOff',
156
        schemata="Security",
157
        required=1,
158
        default=0,
159
        widget=IntegerWidget(
160
            label=_("Automatic log-off"),
161
            description=_(
162
                "The number of minutes before a user is automatically logged off. "
163
                "0 disables automatic log-off"),
164
        )
165
    ),
166
    BooleanField(
167
        'RestrictWorksheetUsersAccess',
168
        schemata="Security",
169
        default=True,
170
        widget=BooleanWidget(
171
            label=_("Allow access to worksheets only to assigned analysts"),
172
            description=_("If unchecked, analysts will have access to all worksheets.")
173
        )
174
    ),
175
    BooleanField(
176
        'AllowToSubmitNotAssigned',
177
        schemata="Security",
178
        default=True,
179
        widget=BooleanWidget(
180
            label=_("Allow to submit results for unassigned analyses or for "
181
                    "analyses assigned to others"),
182
            description=_(
183
                "If unchecked, users will only be able to submit results "
184
                "for the analyses they are assigned to, and the submission of "
185
                "results for unassigned analyses won't be permitted. This "
186
                "setting does not apply to users with role Lab Manager")
187
        )
188
    ),
189
    BooleanField(
190
        'RestrictWorksheetManagement',
191
        schemata="Security",
192
        default=True,
193
        widget=BooleanWidget(
194
            label=_("Only lab managers can create and manage worksheets"),
195
            description=_("If unchecked, analysts and lab clerks will "
196
                          "be able to manage Worksheets, too. If the "
197
                          "users have restricted access only to those "
198
                          "worksheets for which they are assigned, "
199
                          "this option will be checked and readonly.")
200
        )
201
    ),
202
    # NOTE: This is a Proxy Field which delegates to the SENAITE Registry!
203
    BooleanField(
204
        "EnableGlobalAuditlog",
205
        schemata="Security",
206
        default=False,
207
        widget=BooleanWidget(
208
            label=_("Enable global Audit Log"),
209
            description=_(
210
                "The global Auditlog shows all modifications of the system. "
211
                "When enabled, all entities will be indexed in a separate "
212
                "catalog. This will increase the time when objects are "
213
                "created or modified."
214
            )
215
        )
216
    ),
217
    BooleanField(
218
        'ShowPrices',
219
        schemata="Accounting",
220
        default=True,
221
        widget=BooleanWidget(
222
            label=_("Include and display pricing information"),
223
        )
224
    ),
225
    StringField(
226
        'Currency',
227
        schemata="Accounting",
228
        required=1,
229
        vocabulary=CURRENCIES,
230
        default='EUR',
231
        widget=SelectionWidget(
232
            label=_("Currency"),
233
            description=_("Select the currency the site will use to display prices."),
234
            format='select',
235
        )
236
    ),
237
    StringField(
238
        'DefaultCountry',
239
        schemata="Accounting",
240
        required=1,
241
        vocabulary='getCountries',
242
        default='',
243
        widget=SelectionWidget(
244
            label=_("Country"),
245
            description=_("Select the country the site will show by default"),
246
            format='select',
247
        )
248
    ),
249
    FixedPointField(
250
        'MemberDiscount',
251
        schemata="Accounting",
252
        default='33.33',
253
        widget=DecimalWidget(
254
            label=_("Member discount %"),
255
            description=_(
256
                "The discount percentage entered here, is applied to the prices for clients "
257
                "flagged as 'members', normally co-operative members or associates deserving "
258
                "of this discount"),
259
        )
260
    ),
261
    FixedPointField(
262
        'VAT',
263
        schemata="Accounting",
264
        default='19.00',
265
        widget=DecimalWidget(
266
            label=_("VAT %"),
267
            description=_(
268
                "Enter percentage value eg. 14.0. This percentage is applied system wide "
269
                "but can be overwrittem on individual items"),
270
        )
271
    ),
272
    StringField(
273
        'DecimalMark',
274
        schemata="Results Reports",
275
        vocabulary=DECIMAL_MARKS,
276
        default=".",
277
        widget=SelectionWidget(
278
            label=_("Default decimal mark"),
279
            description=_("Preferred decimal mark for reports."),
280
            format='select',
281
        )
282
    ),
283
    StringField(
284
        'ScientificNotationReport',
285
        schemata="Results Reports",
286
        default='1',
287
        vocabulary=SCINOTATION_OPTIONS,
288
        widget=SelectionWidget(
289
            label=_("Default scientific notation format for reports"),
290
            description=_("Preferred scientific notation format for reports"),
291
            format='select',
292
        )
293
    ),
294
    IntegerField(
295
        'MinimumResults',
296
        schemata="Results Reports",
297
        required=1,
298
        default=5,
299
        widget=IntegerWidget(
300
            label=_("Minimum number of results for QC stats calculations"),
301
            description=_(
302
                "Using too few data points does not make statistical sense. "
303
                "Set an acceptable minimum number of results before QC statistics "
304
                "will be calculated and plotted"),
305
        )
306
    ),
307
    BooleanField(
308
        'CategoriseAnalysisServices',
309
        schemata="Analyses",
310
        default=False,
311
        widget=BooleanWidget(
312
            label=_("Categorise analysis services"),
313
            description=_("Group analysis services by category in the LIMS tables, helpful when the list is long")
314
        ),
315
    ),
316
    BooleanField(
317
        "CategorizeSampleAnalyses",
318
        schemata="Analyses",
319
        default=False,
320
        widget=BooleanWidget(
321
            label=_("label_bikasetup_categorizesampleanalyses",
322
                    default="Categorize sample analyses"),
323
            description=_("description_bikasetup_categorizesampleanalyses",
324
                          "Group analyses by category for samples")
325
        ),
326
    ),
327
    BooleanField(
328
        "SampleAnalysesRequired",
329
        schemata="Analyses",
330
        default=True,
331
        widget=BooleanWidget(
332
            label=_("label_bikasetup_sampleanalysesrequired",
333
                    default="Require sample analyses"),
334
            description=_("description_bikasetup_sampleanalysesrequired",
335
                          "Analyses are required for sample registration")
336
        ),
337
    ),
338
    BooleanField(
339
        'EnableARSpecs',
340
        schemata="Analyses",
341
        default=False,
342
        widget=BooleanWidget(
343
            label=_("Enable Sample Specifications"),
344
            description=_(
345
                "Analysis specifications which are edited directly on the "
346
                "Sample."),
347
        ),
348
    ),
349
    IntegerField(
350
        'ExponentialFormatThreshold',
351
        schemata="Analyses",
352
        required=1,
353
        default=7,
354
        widget=IntegerWidget(
355
            label=_("Exponential format threshold"),
356
            description=_(
357
                "Result values with at least this number of significant "
358
                "digits are displayed in scientific notation using the "
359
                "letter 'e' to indicate the exponent.  The precision can be "
360
                "configured in individual Analysis Services."),
361
        )
362
    ),
363
    BooleanField(
364
        "ImmediateResultsEntry",
365
        schemata="Analyses",
366
        default=False,
367
        widget=BooleanWidget(
368
            label=_("label_bikasetup_immediateresultsentry",
369
                    default=u"Immediate results entry"),
370
            description=_(
371
                "description_bikasetup_immediateresultsentry",
372
                default=u"Allow the user to directly enter results after "
373
                "sample creation, e.g. to enter field results immediately, or "
374
                "lab results, when the automatic sample reception is "
375
                "activated."
376
            ),
377
        ),
378
    ),
379
    BooleanField(
380
        'EnableAnalysisRemarks',
381
        schemata="Analyses",
382
        default=False,
383
        widget=BooleanWidget(
384
            label=_("Add a remarks field to all analyses"),
385
            description=_(
386
                "If enabled, a free text field will be displayed close to "
387
                "each analysis in results entry view"
388
            )
389
        ),
390
    ),
391
    BooleanField(
392
        "AutoVerifySamples",
393
        schemata="Analyses",
394
        default=True,
395
        widget=BooleanWidget(
396
            label=_("Automatic verification of samples"),
397
            description=_(
398
                "When enabled, the sample is automatically verified as soon as "
399
                "all results are verified. Otherwise, users with enough "
400
                "privileges have to manually verify the sample afterwards. "
401
                "Default: enabled"
402
            )
403
        )
404
    ),
405
    BooleanField(
406
        'SelfVerificationEnabled',
407
        schemata="Analyses",
408
        default=False,
409
        widget=BooleanWidget(
410
            label=_("Allow self-verification of results"),
411
            description=_(
412
                "If enabled, a user who submitted a result will also be able "
413
                "to verify it. This setting only take effect for those users "
414
                "with a role assigned that allows them to verify results "
415
                "(by default, managers, labmanagers and verifiers)."
416
                "This setting can be overrided for a given Analysis in "
417
                "Analysis Service edit view. By default, disabled."),
418
        ),
419
    ),
420
    IntegerField(
421
        'NumberOfRequiredVerifications',
422
        schemata="Analyses",
423
        default=1,
424
        vocabulary="_getNumberOfRequiredVerificationsVocabulary",
425
        widget=SelectionWidget(
426
            format="select",
427
            label=_("Number of required verifications"),
428
            description=_(
429
                "Number of required verifications before a given result being "
430
                "considered as 'verified'. This setting can be overrided for "
431
                "any Analysis in Analysis Service edit view. By default, 1"),
432
        ),
433
    ),
434
    StringField(
435
        'TypeOfmultiVerification',
436
        schemata="Analyses",
437
        default='self_multi_enabled',
438
        vocabulary=MULTI_VERIFICATION_TYPE,
439
        widget=SelectionWidget(
440
            label=_("Multi Verification type"),
441
            description=_(
442
                "Choose type of multiple verification for the same user."
443
                "This setting can enable/disable verifying/consecutively verifying"
444
                "more than once for the same user."),
445
            format='select',
446
        )
447
    ),
448
    StringField(
449
        'ResultsDecimalMark',
450
        schemata="Analyses",
451
        vocabulary=DECIMAL_MARKS,
452
        default=".",
453
        widget=SelectionWidget(
454
            label=_("Default decimal mark"),
455
            description=_("Preferred decimal mark for results"),
456
            format='select',
457
        )
458
    ),
459
    StringField(
460
        'ScientificNotationResults',
461
        schemata="Analyses",
462
        default='1',
463
        vocabulary=SCINOTATION_OPTIONS,
464
        widget=SelectionWidget(
465
            label=_("Default scientific notation format for results"),
466
            description=_("Preferred scientific notation format for results"),
467
            format='select',
468
        )
469
    ),
470
    StringField(
471
        'WorksheetLayout',
472
        schemata="Appearance",
473
        default=DEFAULT_WORKSHEET_LAYOUT,
474
        vocabulary=getWorksheetLayouts(),
475
        widget=SelectionWidget(
476
            label=_("Default layout in worksheet view"),
477
            description=_("Preferred layout of the results entry table "
478
                          "in the Worksheet view. Classic layout displays "
479
                          "the Samples in rows and the analyses "
480
                          "in columns. Transposed layout displays the "
481
                          "Samples in columns and the analyses "
482
                          "in rows."),
483
            format='select',
484
        )
485
    ),
486
    BooleanField(
487
        'DashboardByDefault',
488
        schemata="Appearance",
489
        default=True,
490
        widget=BooleanWidget(
491
            label=_("Use Dashboard as default front page"),
492
            description=_("Select this to activate the dashboard as a default front page.")
493
        ),
494
    ),
495
    UIDReferenceField(
496
        "LandingPage",
497
        schemata="Appearance",
498
        required=0,
499
        allowed_types=(
500
            "Document",
501
            "Client",
502
            "ClientFolder",
503
            "Samples",
504
            "WorksheetFolder",
505
        ),
506
        mode="rw",
507
        multiValued=0,
508
        relationship='SetupLandingPage',
509
        widget=ReferenceWidget(
510
            label=_("Landing Page"),
511
            description=_(
512
                "The landing page is shown for non-authenticated users "
513
                "if the Dashboard is not selected as the default front page. "
514
                "If no landing page is selected, the default frontpage is displayed."),
515
            catalog_name="portal_catalog",
516
            base_query={"review_State": "published"},
517
            showOn=True,
518
        ),
519
    ),
520
    BooleanField(
521
        'PrintingWorkflowEnabled',
522
        schemata="Sampling",
523
        default=False,
524
        widget=BooleanWidget(
525
            label=_("Enable the Results Report Printing workflow"),
526
            description=_("Select this to allow the user to set an "
527
                          "additional 'Printed' status to those Analysis "
528
                          "Requests tha have been Published. "
529
                          "Disabled by default.")
530
        ),
531
    ),
532
    BooleanField(
533
        'SamplingWorkflowEnabled',
534
        schemata="Sampling",
535
        default=False,
536
        widget=BooleanWidget(
537
            label=_("Enable Sampling"),
538
            description=_("Select this to activate the sample collection workflow steps.")
539
        ),
540
    ),
541
    BooleanField(
542
        'ScheduleSamplingEnabled',
543
        schemata="Sampling",
544
        default=False,
545
        widget=BooleanWidget(
546
            label=_("Enable Sampling Scheduling"),
547
            description=_(
548
                "Select this to allow a Sampling Coordinator to" +
549
                " schedule a sampling. This functionality only takes effect" +
550
                " when 'Sampling workflow' is active")
551
        ),
552
    ),
553
    BooleanField(
554
        "AutoreceiveSamples",
555
        schemata="Sampling",
556
        default=False,
557
        widget=BooleanWidget(
558
            label=_("Auto-receive samples"),
559
            description=_(
560
                "Select to receive the samples automatically when created by "
561
                "lab personnel and sampling workflow is disabled. Samples "
562
                "created by client contacts won't be received automatically"
563
            ),
564
        ),
565
    ),
566
    BooleanField(
567
        'ShowPartitions',
568
        schemata="Appearance",
569
        default=False,
570
        widget=BooleanWidget(
571
            label=_("Display sample partitions to clients"),
572
            description=_(
573
                "Select to show sample partitions to client contacts. "
574
                "If deactivated, partitions won't be included in listings "
575
                "and no info message with links to the primary sample will "
576
                "be displayed to client contacts.")
577
        ),
578
    ),
579
    BooleanField(
580
        'SamplePreservationEnabled',
581
        schemata="Sampling",
582
        default=False,
583
        widget=BooleanWidget(
584
            label=_("Enable Sample Preservation"),
585
            description=_("")
586
        ),
587
    ),
588
    LinesField(
589
        "Workdays",
590
        schemata="Sampling",
591
        vocabulary=WEEKDAYS,
592
        default=tuple(map(str, range(7))),
593
        required=1,
594
        widget=InAndOutWidget(
595
            visible=True,
596
            label=_("Laboratory Workdays"),
597
            description=_("Only laboratory workdays are considered for the "
598
                          "analysis turnaround time calculation. "),
599
            format="checkbox",
600
        )
601
    ),
602
    DurationField(
603
        'DefaultTurnaroundTime',
604
        schemata="Sampling",
605
        required=1,
606
        default={"days": 5, "hours": 0, "minutes": 0},
607
        widget=DurationWidget(
608
            label=_("Default turnaround time for analyses."),
609
            description=_(
610
                "This is the default maximum time allowed for performing "
611
                "analyses.  It is only used for analyses where the analysis "
612
                "service does not specify a turnaround time. "
613
                "Only laboratory workdays are considered."
614
            ),
615
        )
616
    ),
617
    DurationField(
618
        'DefaultSampleLifetime',
619
        schemata="Sampling",
620
        required=1,
621
        default={"days": 30, "hours": 0, "minutes": 0},
622
        widget=DurationWidget(
623
            label=_("Default sample retention period"),
624
            description=_(
625
                "The number of days before a sample expires and cannot be analysed "
626
                "any more. This setting can be overwritten per individual sample type "
627
                "in the sample types setup"),
628
        )
629
    ),
630
    # NOTE: This is a Proxy Field which delegates to the SENAITE Registry!
631
    TextField(
632
        "EmailBodySamplePublication",
633
        default_content_type="text/html",
634
        default_output_type="text/x-html-safe",
635
        schemata="Notifications",
636
        # Needed to fetch the default value from the registry
637
        edit_accessor="getEmailBodySamplePublication",
638
        widget=RichWidget(
639
            label=_(
640
                "label_bikasetup_email_body_sample_publication",
641
                "Email body for Sample publication notifications"),
642
            description=_(
643
                "description_bikasetup_email_body_sample_publication",
644
                default="Set the email body text to be used by default when "
645
                "sending out result reports to the selected recipients. "
646
                "You can use reserved keywords: "
647
                "$client_name, $recipients, $lab_name, $lab_address"),
648
            default_mime_type="text/x-html",
649
            output_mime_type="text/x-html",
650
            allow_file_upload=False,
651
            rows=15,
652
        ),
653
    ),
654
    BooleanField(
655
        'NotifyOnSampleRejection',
656
        schemata="Notifications",
657
        default=False,
658
        widget=BooleanWidget(
659
            label=_("Email notification on Sample rejection"),
660
            description=_("Select this to activate automatic notifications "
661
                          "via email to the Client when a Sample is rejected.")
662
        ),
663
    ),
664
    TextField(
665
        "EmailBodySampleRejection",
666
        default_content_type='text/html',
667
        default_output_type='text/x-html-safe',
668
        schemata="Notifications",
669
        label=_("Email body for Sample Rejection notifications"),
670
        default="The sample $sample_link has been rejected because of the "
671
                "following reasons:"
672
                "<br/><br/>$reasons<br/><br/>"
673
                "For further information, please contact us under the "
674
                "following address.<br/><br/>"
675
                "$lab_address",
676
        widget=RichWidget(
677
            label=_("Email body for Sample Rejection notifications"),
678
            description=_(
679
                "Set the text for the body of the email to be sent to the "
680
                "Sample's client contact if the option 'Email notification on "
681
                "Sample rejection' is enabled. You can use reserved keywords: "
682
                "$sample_id, $sample_link, $reasons, $lab_address"),
683
            default_mime_type='text/x-rst',
684
            output_mime_type='text/x-html',
685
            allow_file_upload=False,
686
            rows=15,
687
        ),
688
    ),
689
    BooleanField(
690
        'NotifyOnSampleInvalidation',
691
        schemata="Notifications",
692
        default=True,
693
        widget=BooleanWidget(
694
            label=_("Email notification on Sample invalidation"),
695
            description=_("Select this to activate automatic notifications "
696
                          "via email to the Client and Lab Managers when a "
697
                          "Sample is invalidated.")
698
        ),
699
    ),
700
    TextField(
701
        "EmailBodySampleInvalidation",
702
        default_content_type='text/html',
703
        default_output_type='text/x-html-safe',
704
        schemata="Notifications",
705
        label=_("Email body for Sample Invalidation notifications"),
706
        default=
707
            "Some non-conformities have been detected in the results report "
708
            "published for Sample $sample_link. "
709
            "<br/><br/> "
710
            "A new Sample $retest_link has been created automatically, and the "
711
            "previous request has been invalidated. "
712
            "<br/><br/> "
713
            "The root cause is under investigation and corrective "
714
            "action has been initiated. "
715
            "<br/><br/> "
716
            "$lab_address",
717
        widget=RichWidget(
718
            label=_("Email body for Sample Invalidation notifications"),
719
            description=_("Set the text for the body of the email to be sent, "
720
                          ", if option 'Email notification on Sample "
721
                          "'invalidation' enabled,  to the Sample's client "
722
                          "contact. You can use reserved keywords: $sample_id, "
723
                          "$sample_link, $retest_id, $retest_link, "
724
                          "$lab_address"),
725
            default_mime_type='text/x-rst',
726
            output_mime_type='text/x-html',
727
            allow_file_upload=False,
728
            rows=15,
729
        ),
730
    ),
731
    StringField(
732
        'AutoPrintStickers',
733
        schemata="Sticker",
734
        vocabulary=STICKER_AUTO_OPTIONS,
735
        widget=SelectionWidget(
736
            format='select',
737
            label=_("Automatic sticker printing"),
738
            description=_(
739
                "Select 'Register' if you want stickers to be automatically printed when "
740
                "new Samples or sample records are created. Select 'Receive' to print stickers "
741
                "when Samples or Samples are received. Select 'None' to disable automatic printing"),
742
        )
743
    ),
744
    StringField(
745
        'AutoStickerTemplate',
746
        schemata="Sticker",
747
        vocabulary="getStickerTemplates",
748
        widget=SelectionWidget(
749
            format='select',
750
            label=_("Sticker templates"),
751
            description=_("Select which sticker to print when automatic sticker printing is enabled"),
752
        )
753
    ),
754
    StringField(
755
        'SmallStickerTemplate',
756
        schemata="Sticker",
757
        vocabulary="getStickerTemplates",
758
        default="Code_128_1x48mm.pt",
759
        widget=SelectionWidget(
760
            format='select',
761
            label=_("Small sticker"),
762
            description=_("Select which sticker should be used as the 'small' sticker by default")
763
        )
764
    ),
765
    StringField(
766
        'LargeStickerTemplate',
767
        schemata="Sticker",
768
        vocabulary="getStickerTemplates",
769
        default="Code_128_1x72mm.pt",
770
        widget=SelectionWidget(
771
            format='select',
772
            label=_("Large sticker"),
773
            description=_("Select which sticker should be used as the 'large' sticker by default")
774
        )
775
    ),
776
    IntegerField(
777
        'DefaultNumberOfCopies',
778
        schemata="Sticker",
779
        required="1",
780
        default="1",
781
        widget=IntegerWidget(
782
            label=_("Number of copies"),
783
            description=_("Set the default number of copies to be printed for each sticker")
784
        )
785
    ),
786
    IDFormattingField(
787
        'IDFormatting',
788
        schemata="ID Server",
789
        default=[
790
            {
791
                'form': 'B-{seq:03d}',
792
                'portal_type': 'Batch',
793
                'prefix': 'batch',
794
                'sequence_type': 'generated',
795
                'split_length': 1
796
            }, {
797
                'form': 'D-{seq:03d}',
798
                'portal_type': 'DuplicateAnalysis',
799
                'prefix': 'duplicate',
800
                'sequence_type': 'generated',
801
                'split_length': 1
802
            }, {
803
                'form': 'I-{seq:03d}',
804
                'portal_type': 'Invoice',
805
                'prefix': 'invoice',
806
                'sequence_type': 'generated',
807
                'split_length': 1
808
            }, {
809
                'form': 'QC-{seq:03d}',
810
                'portal_type': 'ReferenceSample',
811
                'prefix': 'refsample',
812
                'sequence_type': 'generated',
813
                'split_length': 1
814
            }, {
815
                'form': 'SA-{seq:03d}',
816
                'portal_type': 'ReferenceAnalysis',
817
                'prefix': 'refanalysis',
818
                'sequence_type': 'generated',
819
                'split_length': 1
820
            }, {
821
                'form': 'WS-{seq:03d}',
822
                'portal_type': 'Worksheet',
823
                'prefix': 'worksheet',
824
                'sequence_type': 'generated',
825
                'split_length': 1
826
            }, {
827
                'form': '{sampleType}-{seq:04d}',
828
                'portal_type': 'AnalysisRequest',
829
                'prefix': 'analysisrequest',
830
                'sequence_type': 'generated',
831
                'split_length': 1
832
            }, {
833
                'form': '{parent_ar_id}-P{partition_count:02d}',
834
                'portal_type': 'AnalysisRequestPartition',
835
                'prefix': 'analysisrequestpartition',
836
                'sequence_type': '',
837
                'split-length': 1
838
            }, {
839
                'form': '{parent_base_id}-R{retest_count:02d}',
840
                'portal_type': 'AnalysisRequestRetest',
841
                'prefix': 'analysisrequestretest',
842
                'sequence_type': '',
843
                'split-length': 1
844
            }, {
845
                'form': '{parent_ar_id}-S{secondary_count:02d}',
846
                'portal_type': 'AnalysisRequestSecondary',
847
                'prefix': 'analysisrequestsecondary',
848
                'sequence_type': '',
849
                'split-length': 1
850
            },
851
        ],
852
        widget=RecordsWidget(
853
            label=_("Formatting Configuration"),
854
            allowDelete=True,
855
            description=_(
856
                " <p>The ID Server provides unique sequential IDs "
857
                "for objects such as Samples and Worksheets etc, based on a "
858
                "format specified for each content type.</p>"
859
                "<p>The format is constructed similarly to the Python format"
860
                " syntax, using predefined variables per content type, and"
861
                " advancing the IDs through a sequence number, 'seq' and its"
862
                " padding as a number of digits, e.g. '03d' for a sequence of"
863
                " IDs from 001 to 999.</p>"
864
                "<p>Alphanumeric prefixes for IDs are included as is in the"
865
                " formats, e.g. WS for Worksheet in WS-{seq:03d} produces"
866
                " sequential Worksheet IDs: WS-001, WS-002, WS-003 etc.</p>"
867
                "<p>For dynamic generation of alphanumeric and sequential IDs,"
868
                " the wildcard {alpha} can be used. E.g WS-{alpha:2a3d}"
869
                " produces WS-AA001, WS-AA002, WS-AB034, etc.</p>"
870
                "<p>Variables that can be used include:"
871
                "<table>"
872
                "<tr>"
873
                "<th style='width:150px'>Content Type</th><th>Variables</th>"
874
                "</tr>"
875
                "<tr><td>Client ID</td><td>{clientId}</td></tr>"
876
                "<tr><td>Year</td><td>{year}</td></tr>"
877
                "<tr><td>Sample ID</td><td>{sampleId}</td></tr>"
878
                "<tr><td>Sample Type</td><td>{sampleType}</td></tr>"
879
                "<tr><td>Sampling Date</td><td>{samplingDate}</td></tr>"
880
                "<tr><td>Date Sampled</td><td>{dateSampled}</td></tr>"
881
                "</table>"
882
                "</p>"
883
                "<p>Configuration Settings:"
884
                "<ul>"
885
                "<li>format:"
886
                "<ul><li>a python format string constructed from predefined"
887
                " variables like sampleId, clientId, sampleType.</li>"
888
                "<li>special variable 'seq' must be positioned last in the"
889
                "format string</li></ul></li>"
890
                "<li>sequence type: [generated|counter]</li>"
891
                "<li>context: if type counter, provides context the counting"
892
                " function</li>"
893
                "<li>counter type: [backreference|contained]</li>"
894
                "<li>counter reference: a parameter to the counting"
895
                " function</li>"
896
                "<li>prefix: default prefix if none provided in format"
897
                " string</li>"
898
                "<li>split length: the number of parts to be included in the"
899
                " prefix</li>"
900
                "</ul></p>")
901
        )
902
    ),
903
    StringField(
904
        'IDServerValues',
905
        schemata="ID Server",
906
        accessor="getIDServerValuesHTML",
907
        readonly=True,
908
        widget=TextAreaWidget(
909
            label=_("ID Server Values"),
910
            cols=30,
911
            rows=30,
912
        ),
913
    ),
914
    RecordsField(
915
        'RejectionReasons',
916
        schemata="Analyses",
917
        widget=RejectionSetupWidget(
918
            label=_("Enable the rejection workflow"),
919
            description=_("Select this to activate the rejection workflow "
920
                          "for Samples. A 'Reject' option will be displayed in "
921
                          "the actions menu.")
922
        ),
923
    ),
924
    IntegerField(
925
        'DefaultNumberOfARsToAdd',
926
        schemata="Analyses",
927
        required=0,
928
        default=4,
929
        widget=IntegerWidget(
930
            label=_("Default count of Sample to add."),
931
            description=_("Default value of the 'Sample count' when users click 'ADD' button to create new Samples"),
932
        )
933
    ),
934
))
935
936
schema['title'].validators = ()
937
schema['title'].widget.visible = False
938
# Update the validation layer after change the validator in runtime
939
schema['title']._validationLayer()
940
941
942
class BikaSetup(folder.ATFolder):
943
    """LIMS Setup
944
    """
945
    implements(IBikaSetup, IHideActionsMenu)
946
947
    schema = schema
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable schema does not seem to be defined.
Loading history...
948
    security = ClassSecurityInfo()
949
950
    def setAutoLogOff(self, value):
951
        """set session lifetime
952
        """
953
        value = int(value)
954
        if value < 0:
955
            value = 0
956
        value = value * 60
957
        acl = api.get_tool("acl_users")
958
        session = acl.get("session")
959
        if session:
960
            session.timeout = value
961
962
    def getAutoLogOff(self):
963
        """get session lifetime
964
        """
965
        acl = api.get_tool("acl_users")
966
        session = acl.get("session")
967
        if not session:
968
            return 0
969
        return session.timeout // 60
970
971
    def getStickerTemplates(self):
972
        """Get the sticker templates
973
        """
974
        out = [[t['id'], t['title']] for t in _getStickerTemplates()]
975
        return DisplayList(out)
976
977
    def getAnalysisServicesVocabulary(self):
978
        """
979
        Get all active Analysis Services from Bika Setup and return them as Display List.
980
        """
981
        bsc = getToolByName(self, 'senaite_catalog_setup')
982
        brains = bsc(portal_type='AnalysisService',
983
                     is_active=True)
984
        items = [(b.UID, b.Title) for b in brains]
985
        items.insert(0, ("", ""))
986
        items.sort(lambda x, y: cmp(x[1], y[1]))
987
        return DisplayList(list(items))
988
989
    def getPrefixFor(self, portal_type):
990
        """Return the prefix for a portal_type.
991
           If not found, simply uses the portal_type itself
992
        """
993
        prefix = [p for p in self.getIDFormatting() if p['portal_type'] == portal_type]
994
        if prefix:
995
            return prefix[0]['prefix']
996
        else:
997
            return portal_type
998
999
    def getCountries(self):
1000
        items = geo.get_countries()
1001
        items = map(lambda country: (country.alpha_2, country.name), items)
1002
        return items
1003
1004
    def isRejectionWorkflowEnabled(self):
1005
        """Return true if the rejection workflow is enabled (its checkbox is set)
1006
        """
1007
        widget = self.getRejectionReasons()
1008
        # widget will be something like:
1009
        # [{'checkbox': u'on', 'textfield-2': u'b', 'textfield-1': u'c', 'textfield-0': u'a'}]
1010
        if len(widget) > 0:
1011
            checkbox = widget[0].get('checkbox', False)
1012
            return True if checkbox == 'on' and len(widget[0]) > 1 else False
1013
        else:
1014
            return False
1015
1016
    def getRejectionReasonsItems(self):
1017
        """Return the list of predefined rejection reasons
1018
        """
1019
        reasons = self.getRejectionReasons()
1020
        if not reasons:
1021
            return []
1022
        reasons = reasons[0]
1023
        keys = filter(lambda key: key != "checkbox", reasons.keys())
1024
        return map(lambda key: reasons[key], sorted(keys)) or []
1025
1026
    def _getNumberOfRequiredVerificationsVocabulary(self):
1027
        """
1028
        Returns a DisplayList with the available options for the
1029
        multi-verification list: '1', '2', '3', '4'
1030
        :returns: DisplayList with the available options for the
1031
            multi-verification list
1032
        """
1033
        items = [(1, '1'), (2, '2'), (3, '3'), (4, '4')]
1034
        return IntDisplayList(list(items))
1035
1036
    def getIDServerValuesHTML(self):
1037
        number_generator = getUtility(INumberGenerator)
1038
        keys = number_generator.keys()
1039
        values = number_generator.values()
1040
        results = []
1041
        for i in range(len(keys)):
1042
            results.append('%s: %s' % (keys[i], values[i]))
1043
        return "\n".join(results)
1044
1045
    def getEmailBodySamplePublication(self):
1046
        """Get the value from the senaite setup
1047
        """
1048
        setup = api.get_senaite_setup()
1049
        # setup is `None` during initial site content structure installation
1050
        if setup:
1051
            return setup.getEmailBodySamplePublication()
1052
1053
    def setEmailBodySamplePublication(self, value):
1054
        """Set the value in the senaite setup
1055
        """
1056
        setup = api.get_senaite_setup()
1057
        # setup is `None` during initial site content structure installation
1058
        if setup:
1059
            setup.setEmailBodySamplePublication(value)
1060
1061
    def getEnableGlobalAuditlog(self):
1062
        """Get the value from the senaite setup
1063
        """
1064
        setup = api.get_senaite_setup()
1065
        # setup is `None` during initial site content structure installation
1066
        if setup:
1067
            return setup.getEnableGlobalAuditlog()
1068
        return False
1069
1070
    def setEnableGlobalAuditlog(self, value):
1071
        """Set the value in the senaite setup
1072
        """
1073
        setup = api.get_senaite_setup()
1074
        # setup is `None` during initial site content structure installation
1075
        if setup:
1076
            setup.setEnableGlobalAuditlog(value)
1077
1078
    def getImmediateResultsEntry(self):
1079
        """Get the value from the senaite setup
1080
        """
1081
        setup = api.get_senaite_setup()
1082
        # setup is `None` during initial site content structure installation
1083
        if setup:
1084
            return setup.getImmediateResultsEntry()
1085
        return False
1086
1087
    def setImmediateResultsEntry(self, value):
1088
        """Set the value in the senaite setup
1089
        """
1090
        setup = api.get_senaite_setup()
1091
        # setup is `None` during initial site content structure installation
1092
        if setup:
1093
            setup.setImmediateResultsEntry(value)
1094
1095
    def getCategorizeSampleAnalyses(self):
1096
        """Get the value from the senaite setup
1097
        """
1098
        setup = api.get_senaite_setup()
1099
        # setup is `None` during initial site content structure installation
1100
        if setup:
1101
            return setup.getCategorizeSampleAnalyses()
1102
        return False
1103
1104
    def setCategorizeSampleAnalyses(self, value):
1105
        """Set the value in the senaite setup
1106
        """
1107
        setup = api.get_senaite_setup()
1108
        # setup is `None` during initial site content structure installation
1109
        if setup:
1110
            setup.setCategorizeSampleAnalyses(value)
1111
1112
    def getSampleAnalysesRequired(self):
1113
        """Get the value from the senaite setup
1114
        """
1115
        setup = api.get_senaite_setup()
1116
        # setup is `None` during initial site content structure installation
1117
        if setup:
1118
            return setup.getSampleAnalysesRequired()
1119
        return False
1120
1121
    def setSampleAnalysesRequired(self, value):
1122
        """Set the value in the senaite setup
1123
        """
1124
        setup = api.get_senaite_setup()
1125
        # setup is `None` during initial site content structure installation
1126
        if setup:
1127
            setup.setSampleAnalysesRequired(value)
1128
1129
1130
registerType(BikaSetup, PROJECTNAME)
1131