Passed
Push — 2.x ( 353ef7...3874f5 )
by Jordi
05:23
created

BikaSetup.getEmailBodySamplePublication()   A

Complexity

Conditions 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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