Passed
Push — master ( c72550...021814 )
by Ramon
05:39
created

BikaSetup.getRejectionReasonsItems()   A

Complexity

Conditions 4

Size

Total Lines 9
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

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