Passed
Push — 2.x ( b4f47e...fa5b48 )
by Jordi
05:44
created

BikaSetup.setEnableGlobalAuditlog()   A

Complexity

Conditions 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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