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

AnalysisRequest.getOtherRejectionReasons()   A

Complexity

Conditions 2

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 5
dl 0
loc 7
rs 10
c 0
b 0
f 0
cc 2
nop 1
1
# -*- coding: utf-8 -*-
2
#
3
# This file is part of SENAITE.CORE
4
#
5
# Copyright 2018 by it's authors.
6
# Some rights reserved. See LICENSE.rst, CONTRIBUTORS.rst.
7
8
import sys
9
from decimal import Decimal
10
11
from AccessControl import ClassSecurityInfo
12
from bika.lims import api
13
from bika.lims import bikaMessageFactory as _
14
from bika.lims import deprecated
15
from bika.lims import logger
16
from bika.lims.browser.fields import ARAnalysesField
17
from bika.lims.browser.fields import DateTimeField
18
from bika.lims.browser.fields import DurationField
19
from bika.lims.browser.fields import UIDReferenceField
20
from bika.lims.browser.fields.remarksfield import RemarksField
21
from bika.lims.browser.widgets import DateTimeWidget
22
from bika.lims.browser.widgets import DecimalWidget
23
from bika.lims.browser.widgets import PrioritySelectionWidget
24
from bika.lims.browser.widgets import ReferenceWidget
25
from bika.lims.browser.widgets import RejectionWidget
26
from bika.lims.browser.widgets import RemarksWidget
27
from bika.lims.browser.widgets import SelectionWidget as BikaSelectionWidget
28
from bika.lims.browser.widgets.durationwidget import DurationWidget
29
from bika.lims.catalog import CATALOG_ANALYSIS_LISTING
30
from bika.lims.catalog import CATALOG_ANALYSIS_REQUEST_LISTING
31
from bika.lims.config import PRIORITIES
32
from bika.lims.config import PROJECTNAME
33
from bika.lims.content.analysisspec import ResultsRangeDict
34
from bika.lims.content.bikaschema import BikaSchema
35
from bika.lims.interfaces import IAnalysisRequest
36
from bika.lims.interfaces import IAnalysisRequestPartition
37
from bika.lims.interfaces import ICancellable
38
from bika.lims.permissions import FieldEditBatch
39
from bika.lims.permissions import FieldEditClient
40
from bika.lims.permissions import FieldEditClientOrderNumber
41
from bika.lims.permissions import FieldEditClientReference
42
from bika.lims.permissions import FieldEditClientSampleID
43
from bika.lims.permissions import FieldEditComposite
44
from bika.lims.permissions import FieldEditContact
45
from bika.lims.permissions import FieldEditContainer
46
from bika.lims.permissions import FieldEditDatePreserved
47
from bika.lims.permissions import FieldEditDateReceived
48
from bika.lims.permissions import FieldEditDateSampled
49
from bika.lims.permissions import FieldEditEnvironmentalConditions
50
from bika.lims.permissions import FieldEditInvoiceExclude
51
from bika.lims.permissions import FieldEditMemberDiscount
52
from bika.lims.permissions import FieldEditPreservation
53
from bika.lims.permissions import FieldEditPreserver
54
from bika.lims.permissions import FieldEditPriority
55
from bika.lims.permissions import FieldEditProfiles
56
from bika.lims.permissions import FieldEditPublicationSpecifications
57
from bika.lims.permissions import FieldEditRejectionReasons
58
from bika.lims.permissions import FieldEditRemarks
59
from bika.lims.permissions import FieldEditResultsInterpretation
60
from bika.lims.permissions import FieldEditSampleCondition
61
from bika.lims.permissions import FieldEditSamplePoint
62
from bika.lims.permissions import FieldEditSampler
63
from bika.lims.permissions import FieldEditSampleType
64
from bika.lims.permissions import FieldEditSamplingDate
65
from bika.lims.permissions import FieldEditSamplingDeviation
66
from bika.lims.permissions import FieldEditSamplingRound
67
from bika.lims.permissions import FieldEditScheduledSampler
68
from bika.lims.permissions import FieldEditSpecification
69
from bika.lims.permissions import FieldEditStorageLocation
70
from bika.lims.permissions import FieldEditTemplate
71
from bika.lims.permissions import ManageInvoices
72
from bika.lims.utils import getUsers
73
from bika.lims.utils import tmpID
74
from bika.lims.utils import user_email
75
from bika.lims.utils import user_fullname
76
from bika.lims.workflow import getTransitionDate
77
from bika.lims.workflow import getTransitionUsers
78
from DateTime import DateTime
79
from Products.Archetypes.atapi import BaseFolder
80
from Products.Archetypes.atapi import BooleanField
81
from Products.Archetypes.atapi import BooleanWidget
82
from Products.Archetypes.atapi import ComputedField
83
from Products.Archetypes.atapi import ComputedWidget
84
from Products.Archetypes.atapi import FileField
85
from Products.Archetypes.atapi import FileWidget
86
from Products.Archetypes.atapi import FixedPointField
87
from Products.Archetypes.atapi import ReferenceField
88
from Products.Archetypes.atapi import StringField
89
from Products.Archetypes.atapi import StringWidget
90
from Products.Archetypes.atapi import TextField
91
from Products.Archetypes.atapi import registerType
92
from Products.Archetypes.public import Schema
93
from Products.Archetypes.references import HoldingReference
94
from Products.Archetypes.Widget import RichWidget
95
from Products.ATExtensions.field import RecordsField
96
from Products.CMFCore.permissions import ModifyPortalContent
97
from Products.CMFCore.permissions import View
98
from Products.CMFCore.utils import getToolByName
99
from Products.CMFPlone.utils import _createObjectByType
100
from Products.CMFPlone.utils import safe_unicode
101
from zope.interface import alsoProvides
102
from zope.interface import implements
103
from zope.interface import noLongerProvides
104
105
# SCHEMA DEFINITION
106
schema = BikaSchema.copy() + Schema((
107
108
    UIDReferenceField(
109
        'Contact',
110
        required=1,
111
        default_method='getContactUIDForUser',
112
        allowed_types=('Contact',),
113
        mode="rw",
114
        read_permission=View,
115
        write_permission=FieldEditContact,
116
        widget=ReferenceWidget(
117
            label=_("Contact"),
118
            render_own_label=True,
119
            size=20,
120
            helper_js=("bika_widgets/referencewidget.js",
121
                       "++resource++bika.lims.js/contact.js"),
122
            description=_("The primary contact of this sample, "
123
                          "who will receive notifications and publications "
124
                          "via email"),
125
            visible={
126
                'add': 'edit',
127
                'header_table': 'prominent',
128
            },
129
            base_query={'is_active': True},
130
            showOn=True,
131
            popup_width='400px',
132
            colModel=[
133
                {'columnName': 'UID', 'hidden': True},
134
                {'columnName': 'Fullname', 'width': '50',
135
                 'label': _('Name')},
136
                {'columnName': 'EmailAddress', 'width': '50',
137
                 'label': _('Email Address')},
138
            ],
139
        ),
140
    ),
141
142
    ReferenceField(
143
        'CCContact',
144
        multiValued=1,
145
        vocabulary_display_path_bound=sys.maxsize,
146
        allowed_types=('Contact',),
147
        referenceClass=HoldingReference,
148
        relationship='AnalysisRequestCCContact',
149
        mode="rw",
150
        read_permission=View,
151
        write_permission=FieldEditContact,
152
        widget=ReferenceWidget(
153
            label=_("CC Contacts"),
154
            description=_("The contacts used in CC for email notifications"),
155
            render_own_label=True,
156
            size=20,
157
            visible={
158
                'add': 'edit',
159
                'header_table': 'prominent',
160
            },
161
            base_query={'is_active': True},
162
            showOn=True,
163
            popup_width='400px',
164
            colModel=[
165
                {'columnName': 'UID', 'hidden': True},
166
                {'columnName': 'Fullname', 'width': '50',
167
                 'label': _('Name')},
168
                {'columnName': 'EmailAddress', 'width': '50',
169
                 'label': _('Email Address')},
170
            ],
171
        ),
172
    ),
173
174
    StringField(
175
        'CCEmails',
176
        mode="rw",
177
        read_permission=View,
178
        write_permission=FieldEditContact,
179
        acquire=True,
180
        acquire_fieldname="CCEmails",
181
        widget=StringWidget(
182
            label=_("CC Emails"),
183
            description=_("Additional email addresses to be notified"),
184
            visible={
185
                'add': 'edit',
186
                'header_table': 'prominent',
187
            },
188
            render_own_label=True,
189
            size=20,
190
        ),
191
    ),
192
193
    ReferenceField(
194
        'Client',
195
        required=1,
196
        allowed_types=('Client',),
197
        relationship='AnalysisRequestClient',
198
        mode="rw",
199
        read_permission=View,
200
        write_permission=FieldEditClient,
201
        widget=ReferenceWidget(
202
            label=_("Client"),
203
            description=_("The assigned client of this request"),
204
            size=20,
205
            render_own_label=True,
206
            visible={
207
                'add': 'edit',
208
                'header_table': 'prominent',
209
            },
210
            base_query={'review_state': 'active'},
211
            showOn=True,
212
            add_button={
213
                    'visible': True,
214
                    'url': 'clients/createObject?type_name=Client',
215
                    'return_fields': ['Title'],
216
                    'js_controllers': ['#client-base-edit'],
217
                    'overlay_handler': 'ClientOverlayHandler',
218
                }
219
        ),
220
    ),
221
    # TODO Remove in >v1.3.0 - This is kept for upgrade and backwards-compat.
222
    UIDReferenceField(
223
        'Sample',
224
        allowed_types=('Sample',),
225
        mode="rw",
226
        read_permission=View,
227
        write_permission=ModifyPortalContent,
228
        widget=ReferenceWidget(
229
            label=_("Sample"),
230
            description=_("Select a sample to create a secondary Sample"),
231
            size=20,
232
            render_own_label=True,
233
            visible=False,
234
            catalog_name='bika_catalog',
235
            base_query={'is_active': True,
236
                        'review_state': ['sample_due', 'sample_received', ]},
237
            showOn=True,
238
        ),
239
    ),
240
241
    # Field for the creation of Secondary Analysis Requests.
242
    # This field is meant to be displayed in AR Add form only. A viewlet exists
243
    # to inform the user this Analysis Request is secondary
244
    ReferenceField(
245
        "PrimaryAnalysisRequest",
246
        allowed_types=("AnalysisRequest",),
247
        referenceClass=HoldingReference,
248
        relationship='AnalysisRequestPrimaryAnalysisRequest',
249
        mode="rw",
250
        read_permission=View,
251
        write_permission=FieldEditClient,
252
        widget=ReferenceWidget(
253
            label=_("Primary Sample"),
254
            description=_("Select a sample to create a secondary Sample"),
255
            size=20,
256
            render_own_label=True,
257
            visible={
258
                'add': 'edit',
259
                'header_table': 'prominent',
260
            },
261
            catalog_name=CATALOG_ANALYSIS_REQUEST_LISTING,
262
            colModel=[
263
                {'columnName': 'getId', 'width': '20',
264
                 'label': _('Sample ID'), 'align': 'left'},
265
                {'columnName': 'getClientSampleID', 'width': '20',
266
                 'label': _('Client SID'), 'align': 'left'},
267
                {'columnName': 'getSampleTypeTitle', 'width': '30',
268
                 'label': _('Sample Type'), 'align': 'left'},
269
                {'columnName': 'getClientTitle', 'width': '30',
270
                 'label': _('Client'), 'align': 'left'},
271
                {'columnName': 'UID', 'hidden': True},
272
            ],
273
            base_query={'is_active': True, 'is_received': True},
274
            sidx='getId',
275
            sord='desc',
276
            showOn=True,
277
        )
278
    ),
279
280
    ReferenceField(
281
        'Batch',
282
        allowed_types=('Batch',),
283
        relationship='AnalysisRequestBatch',
284
        mode="rw",
285
        read_permission=View,
286
        write_permission=FieldEditBatch,
287
        widget=ReferenceWidget(
288
            label=_("Batch"),
289
            size=20,
290
            description=_("The assigned batch of this request"),
291
            render_own_label=True,
292
            visible={
293
                'add': 'edit',
294
            },
295
            catalog_name='bika_catalog',
296
            base_query={'review_state': 'open'},
297
            showOn=True,
298
        ),
299
    ),
300
301
    ReferenceField(
302
        'SamplingRound',
303
        allowed_types=('SamplingRound',),
304
        relationship='AnalysisRequestSamplingRound',
305
        mode="rw",
306
        read_permission=View,
307
        write_permission=FieldEditSamplingRound,
308
        widget=ReferenceWidget(
309
            label=_("Sampling Round"),
310
            description=_("The assigned sampling round of this request"),
311
            size=20,
312
            render_own_label=True,
313
            visible={
314
                'add': 'invisible',
315
            },
316
            catalog_name='portal_catalog',
317
            base_query={},
318
            showOn=True,
319
        ),
320
    ),
321
322
    ReferenceField(
323
        'SubGroup',
324
        required=False,
325
        allowed_types=('SubGroup',),
326
        referenceClass=HoldingReference,
327
        relationship='AnalysisRequestSubGroup',
328
        mode="rw",
329
        read_permission=View,
330
        write_permission=FieldEditBatch,
331
        widget=ReferenceWidget(
332
            label=_("Batch Sub-group"),
333
            description=_("The assigned batch sub group of this request"),
334
            size=20,
335
            render_own_label=True,
336
            visible={
337
                'add': 'edit',
338
            },
339
            catalog_name='bika_setup_catalog',
340
            colModel=[
341
                {'columnName': 'Title', 'width': '30',
342
                 'label': _('Title'), 'align': 'left'},
343
                {'columnName': 'Description', 'width': '70',
344
                 'label': _('Description'), 'align': 'left'},
345
                {'columnName': 'SortKey', 'hidden': True},
346
                {'columnName': 'UID', 'hidden': True},
347
            ],
348
            base_query={'is_active': True},
349
            sidx='SortKey',
350
            sord='asc',
351
            showOn=True,
352
        ),
353
    ),
354
355
    ReferenceField(
356
        'Template',
357
        allowed_types=('ARTemplate',),
358
        referenceClass=HoldingReference,
359
        relationship='AnalysisRequestARTemplate',
360
        mode="rw",
361
        read_permission=View,
362
        write_permission=FieldEditTemplate,
363
        widget=ReferenceWidget(
364
            label=_("Sample Template"),
365
            description=_("The predefined values of the Sample template are set "
366
                          "in the request"),
367
            size=20,
368
            render_own_label=True,
369
            visible={
370
                'add': 'edit',
371
                'secondary': 'disabled',
372
            },
373
            catalog_name='bika_setup_catalog',
374
            base_query={'is_active': True},
375
            showOn=True,
376
        ),
377
    ),
378
379
    # TODO Remove Profile field (in singular)
380
    ReferenceField(
381
        'Profile',
382
        allowed_types=('AnalysisProfile',),
383
        referenceClass=HoldingReference,
384
        relationship='AnalysisRequestAnalysisProfile',
385
        mode="rw",
386
        read_permission=View,
387
        write_permission=ModifyPortalContent,
388
        widget=ReferenceWidget(
389
            label=_("Analysis Profile"),
390
            description=_("Analysis profiles apply a certain set of analyses"),
391
            size=20,
392
            render_own_label=True,
393
            visible=False,
394
            catalog_name='bika_setup_catalog',
395
            base_query={'is_active': True},
396
            showOn=False,
397
        ),
398
    ),
399
400
    ReferenceField(
401
        'Profiles',
402
        multiValued=1,
403
        allowed_types=('AnalysisProfile',),
404
        referenceClass=HoldingReference,
405
        vocabulary_display_path_bound=sys.maxsize,
406
        relationship='AnalysisRequestAnalysisProfiles',
407
        mode="rw",
408
        read_permission=View,
409
        write_permission=FieldEditProfiles,
410
        widget=ReferenceWidget(
411
            label=_("Analysis Profiles"),
412
            description=_("Analysis profiles apply a certain set of analyses"),
413
            size=20,
414
            render_own_label=True,
415
            visible={
416
                'add': 'edit',
417
            },
418
            catalog_name='bika_setup_catalog',
419
            base_query={'is_active': True},
420
            showOn=True,
421
        ),
422
    ),
423
    # TODO Workflow - Request - Fix DateSampled inconsistencies. At the moment,
424
    # one can create an AR (using code) with DateSampled set when sampling_wf at
425
    # the same time sampling workflow is active. This might cause
426
    # inconsistencies: AR still in `to_be_sampled`, but getDateSampled returns
427
    # a valid date!
428
    DateTimeField(
429
        'DateSampled',
430
        mode="rw",
431
        read_permission=View,
432
        write_permission=FieldEditDateSampled,
433
        widget=DateTimeWidget(
434
            label=_("Date Sampled"),
435
            description=_("The date when the sample was taken"),
436
            size=20,
437
            show_time=True,
438
            datepicker_nofuture=1,
439
            visible={
440
                'add': 'edit',
441
                'secondary': 'disabled',
442
                'header_table': 'prominent',
443
            },
444
            render_own_label=True,
445
        ),
446
    ),
447
    StringField(
448
        'Sampler',
449
        mode="rw",
450
        read_permission=View,
451
        write_permission=FieldEditSampler,
452
        vocabulary='getSamplers',
453
        widget=BikaSelectionWidget(
454
            format='select',
455
            label=_("Sampler"),
456
            description=_("The person who took the sample"),
457
            # see SamplingWOrkflowWidgetVisibility
458
            visible={
459
                'add': 'edit',
460
                'header_table': 'prominent',
461
            },
462
            render_own_label=True,
463
        ),
464
    ),
465
466
    StringField(
467
        'ScheduledSamplingSampler',
468
        mode="rw",
469
        read_permission=View,
470
        write_permission=FieldEditScheduledSampler,
471
        vocabulary='getSamplers',
472
        widget=BikaSelectionWidget(
473
            description=_("Define the sampler supposed to do the sample in "
474
                          "the scheduled date"),
475
            format='select',
476
            label=_("Sampler for scheduled sampling"),
477
            visible={
478
                'add': 'edit',
479
            },
480
            render_own_label=True,
481
        ),
482
    ),
483
484
    DateTimeField(
485
        'SamplingDate',
486
        mode="rw",
487
        read_permission=View,
488
        write_permission=FieldEditSamplingDate,
489
        widget=DateTimeWidget(
490
            label=_("Expected Sampling Date"),
491
            description=_("The date when the sample will be taken"),
492
            size=20,
493
            show_time=True,
494
            datepicker_nopast=1,
495
            render_own_label=True,
496
            visible={
497
                'add': 'edit',
498
                'secondary': 'disabled',
499
            },
500
        ),
501
    ),
502
503
    UIDReferenceField(
504
        'SampleType',
505
        required=1,
506
        allowed_types='SampleType',
507
        mode="rw",
508
        read_permission=View,
509
        write_permission=FieldEditSampleType,
510
        widget=ReferenceWidget(
511
            label=_("Sample Type"),
512
            render_own_label=True,
513
            visible={
514
                'add': 'edit',
515
                'secondary': 'disabled',
516
            },
517
            catalog_name='bika_setup_catalog',
518
            base_query={'is_active': True},
519
            showOn=True,
520
        ),
521
    ),
522
523
    UIDReferenceField(
524
        'Container',
525
        required=0,
526
        allowed_types='Container',
527
        mode="rw",
528
        read_permission=View,
529
        write_permission=FieldEditContainer,
530
        widget=ReferenceWidget(
531
            label=_("Container"),
532
            render_own_label=True,
533
            visible={
534
                'add': 'edit',
535
            },
536
            catalog_name='bika_setup_catalog',
537
            base_query={'is_active': True},
538
            showOn=True,
539
        ),
540
    ),
541
542
    UIDReferenceField(
543
        'Preservation',
544
        required=0,
545
        allowed_types='Preservation',
546
        mode="rw",
547
        read_permission=View,
548
        write_permission=FieldEditPreservation,
549
        widget=ReferenceWidget(
550
            label=_("Preservation"),
551
            render_own_label=True,
552
            visible={
553
                'add': 'edit',
554
            },
555
            catalog_name='bika_setup_catalog',
556
            base_query={'is_active': True},
557
            showOn=True,
558
        ),
559
    ),
560
561
    DateTimeField('DatePreserved',
562
        mode="rw",
563
        read_permission=View,
564
        write_permission=FieldEditDatePreserved,
565
        widget=DateTimeWidget(
566
            label=_("Date Preserved"),
567
            description=_("The date when the sample was preserved"),
568
            size=20,
569
            show_time=True,
570
            render_own_label=True,
571
            visible={
572
                'add': 'edit',
573
                'header_table': 'prominent',
574
            },
575
        ),
576
    ),
577
    StringField('Preserver',
578
        required=0,
579
        mode="rw",
580
        read_permission=View,
581
        write_permission=FieldEditPreserver,
582
        vocabulary='getPreservers',
583
        widget=BikaSelectionWidget(
584
            format='select',
585
            label=_("Preserver"),
586
            description=_("The person who preserved the sample"),
587
            visible={
588
                'add': 'edit',
589
                'header_table': 'prominent',
590
            },
591
            render_own_label=True,
592
        ),
593
    ),
594
    # TODO Sample cleanup - This comes from partition
595
    DurationField('RetentionPeriod',
596
        required=0,
597
        mode="r",
598
        read_permission=View,
599
        widget=DurationWidget(
600
            label=_("Retention Period"),
601
            visible=False,
602
        ),
603
    ),
604
    RecordsField(
605
        'RejectionReasons',
606
        mode="rw",
607
        read_permission=View,
608
        write_permission=FieldEditRejectionReasons,
609
        widget=RejectionWidget(
610
            label=_("Sample Rejection"),
611
            description=_("Set the Sample Rejection workflow and the reasons"),
612
            render_own_label=False,
613
            visible={
614
                'add': 'edit',
615
                'secondary': 'disabled',
616
            },
617
        ),
618
    ),
619
620
    ReferenceField(
621
        'Specification',
622
        required=0,
623
        allowed_types='AnalysisSpec',
624
        relationship='AnalysisRequestAnalysisSpec',
625
        mode="rw",
626
        read_permission=View,
627
        write_permission=FieldEditSpecification,
628
        widget=ReferenceWidget(
629
            label=_("Analysis Specification"),
630
            description=_("Choose default Sample specification values"),
631
            size=20,
632
            render_own_label=True,
633
            visible={
634
                'add': 'edit',
635
            },
636
            catalog_name='bika_setup_catalog',
637
            colModel=[
638
                {'columnName': 'contextual_title',
639
                 'width': '30',
640
                 'label': _('Title'),
641
                 'align': 'left'},
642
                {'columnName': 'SampleTypeTitle',
643
                 'width': '70',
644
                 'label': _('SampleType'),
645
                 'align': 'left'},
646
                # UID is required in colModel
647
                {'columnName': 'UID', 'hidden': True},
648
            ],
649
            showOn=True,
650
        ),
651
    ),
652
653
    # see setResultsRange below.
654
    RecordsField(
655
        'ResultsRange',
656
        required=0,
657
        type='resultsrange',
658
        subfields=('keyword', 'min', 'max', 'warn_min', 'warn_max', 'hidemin',
659
                   'hidemax', 'rangecomment', 'min_operator', 'max_operator'),
660
        widget=ComputedWidget(visible=False),
661
    ),
662
663
    ReferenceField(
664
        'PublicationSpecification',
665
        required=0,
666
        allowed_types='AnalysisSpec',
667
        relationship='AnalysisRequestPublicationSpec',
668
        mode="rw",
669
        read_permission=View,
670
        write_permission=FieldEditPublicationSpecifications,
671
        widget=ReferenceWidget(
672
            label=_("Publication Specification"),
673
            description=_(
674
                "Set the specification to be used before publishing a Sample."),
675
            size=20,
676
            render_own_label=True,
677
            visible={
678
                "add": "invisible",
679
                'secondary': 'disabled',
680
            },
681
            catalog_name='bika_setup_catalog',
682
            base_query={'is_active': True},
683
            showOn=True,
684
        ),
685
    ),
686
687
    # Sample field
688
    UIDReferenceField(
689
        'SamplePoint',
690
        allowed_types='SamplePoint',
691
        mode="rw",
692
        read_permission=View,
693
        write_permission=FieldEditSamplePoint,
694
        widget=ReferenceWidget(
695
            label=_("Sample Point"),
696
            description=_("Location where sample was taken"),
697
            size=20,
698
            render_own_label=True,
699
            visible={
700
                'add': 'edit',
701
                'secondary': 'disabled',
702
            },
703
            catalog_name='bika_setup_catalog',
704
            base_query={'is_active': True},
705
            showOn=True,
706
        ),
707
    ),
708
709
    UIDReferenceField(
710
        'StorageLocation',
711
        allowed_types='StorageLocation',
712
        mode="rw",
713
        read_permission=View,
714
        write_permission=FieldEditStorageLocation,
715
        widget=ReferenceWidget(
716
            label=_("Storage Location"),
717
            description=_("Location where sample is kept"),
718
            size=20,
719
            render_own_label=True,
720
            visible={
721
                'add': 'edit',
722
                'secondary': 'disabled',
723
            },
724
            catalog_name='bika_setup_catalog',
725
            base_query={'is_active': True},
726
            showOn=True,
727
        ),
728
    ),
729
730
    StringField(
731
        'ClientOrderNumber',
732
        mode="rw",
733
        read_permission=View,
734
        write_permission=FieldEditClientOrderNumber,
735
        widget=StringWidget(
736
            label=_("Client Order Number"),
737
            description=_("The client side order number for this request"),
738
            size=20,
739
            render_own_label=True,
740
            visible={
741
                'add': 'edit',
742
                'secondary': 'disabled',
743
            },
744
        ),
745
    ),
746
747
    StringField(
748
        'ClientReference',
749
        mode="rw",
750
        read_permission=View,
751
        write_permission=FieldEditClientReference,
752
        widget=StringWidget(
753
            label=_("Client Reference"),
754
            description=_("The client side reference for this request"),
755
            render_own_label=True,
756
            visible={
757
                'add': 'edit',
758
                'secondary': 'disabled',
759
            },
760
        ),
761
    ),
762
763
    StringField(
764
        'ClientSampleID',
765
        mode="rw",
766
        read_permission=View,
767
        write_permission=FieldEditClientSampleID,
768
        widget=StringWidget(
769
            label=_("Client Sample ID"),
770
            description=_("The client side identifier of the sample"),
771
            size=20,
772
            render_own_label=True,
773
            visible={
774
                'add': 'edit',
775
                'secondary': 'disabled',
776
            },
777
        ),
778
    ),
779
780
    UIDReferenceField(
781
        'SamplingDeviation',
782
        allowed_types='SamplingDeviation',
783
        mode="rw",
784
        read_permission=View,
785
        write_permission=FieldEditSamplingDeviation,
786
        widget=ReferenceWidget(
787
            label=_("Sampling Deviation"),
788
            description=_("Deviation between the sample and how it "
789
                          "was sampled"),
790
            size=20,
791
            render_own_label=True,
792
            visible={
793
                'add': 'edit',
794
                'secondary': 'disabled',
795
            },
796
            catalog_name='bika_setup_catalog',
797
            base_query={'is_active': True},
798
            showOn=True,
799
        ),
800
    ),
801
802
    UIDReferenceField(
803
        'SampleCondition',
804
        allowed_types='SampleCondition',
805
        mode="rw",
806
        read_permission=View,
807
        write_permission=FieldEditSampleCondition,
808
        widget=ReferenceWidget(
809
            label=_("Sample condition"),
810
            description=_("The condition of the sample"),
811
            size=20,
812
            render_own_label=True,
813
            visible={
814
                'add': 'edit',
815
                'secondary': 'disabled',
816
            },
817
            catalog_name='bika_setup_catalog',
818
            base_query={'is_active': True},
819
            showOn=True,
820
        ),
821
    ),
822
823
    StringField(
824
        'Priority',
825
        default='3',
826
        vocabulary=PRIORITIES,
827
        mode='rw',
828
        read_permission=View,
829
        write_permission=FieldEditPriority,
830
        widget=PrioritySelectionWidget(
831
            label=_('Priority'),
832
            format='select',
833
            visible={
834
                'add': 'edit',
835
            },
836
        ),
837
    ),
838
    StringField(
839
        'EnvironmentalConditions',
840
        mode="rw",
841
        read_permission=View,
842
        write_permission=FieldEditEnvironmentalConditions,
843
        widget=StringWidget(
844
            label=_("Environmental conditions"),
845
            description=_("The environmental condition during sampling"),
846
            visible={
847
                'add': 'edit',
848
                'header_table': 'prominent',
849
            },
850
            render_own_label=True,
851
            size=20,
852
        ),
853
    ),
854
855
    # TODO Remove - Is this still necessary?
856
    ReferenceField(
857
        'DefaultContainerType',
858
        allowed_types=('ContainerType',),
859
        relationship='AnalysisRequestContainerType',
860
        referenceClass=HoldingReference,
861
        mode="rw",
862
        read_permission=View,
863
        write_permission=ModifyPortalContent,
864
        widget=ReferenceWidget(
865
            label=_("Default Container"),
866
            description=_("Default container for new sample partitions"),
867
            size=20,
868
            render_own_label=True,
869
            visible=False,
870
            catalog_name='bika_setup_catalog',
871
            base_query={'is_active': True},
872
            showOn=True,
873
        ),
874
    ),
875
876
    BooleanField(
877
        'Composite',
878
        default=False,
879
        mode="rw",
880
        read_permission=View,
881
        write_permission=FieldEditComposite,
882
        widget=BooleanWidget(
883
            label=_("Composite"),
884
            render_own_label=True,
885
            visible={
886
                'add': 'edit',
887
                'secondary': 'disabled',
888
            },
889
        ),
890
    ),
891
892
    BooleanField(
893
        'InvoiceExclude',
894
        default=False,
895
        mode="rw",
896
        read_permission=View,
897
        write_permission=FieldEditInvoiceExclude,
898
        widget=BooleanWidget(
899
            label=_("Invoice Exclude"),
900
            description=_("Should the analyses be excluded from the invoice?"),
901
            render_own_label=True,
902
            visible={
903
                'add': 'edit',
904
                'header_table': 'visible',
905
            },
906
        ),
907
    ),
908
909
    # TODO Review permission for this field Analyses
910
    ARAnalysesField(
911
        'Analyses',
912
        required=1,
913
        mode="rw",
914
        read_permission=View,
915
        write_permission=ModifyPortalContent,
916
        widget=ComputedWidget(
917
            visible={
918
                'edit': 'invisible',
919
                'view': 'invisible',
920
                'sample_registered': {
921
                    'view': 'visible', 'edit': 'visible', 'add': 'invisible'},
922
            }
923
        ),
924
    ),
925
926
    ReferenceField(
927
        'Attachment',
928
        multiValued=1,
929
        allowed_types=('Attachment',),
930
        referenceClass=HoldingReference,
931
        relationship='AnalysisRequestAttachment',
932
        mode="rw",
933
        read_permission=View,
934
        write_permission=ModifyPortalContent,
935
        widget=ComputedWidget(
936
            visible={
937
                'edit': 'invisible',
938
                'view': 'invisible',
939
            },
940
        )
941
    ),
942
943
    # This is a virtual field and handled only by AR Add View to allow multi
944
    # attachment upload in AR Add. It should never contain an own value!
945
    FileField(
946
        '_ARAttachment',
947
        widget=FileWidget(
948
            label=_("Attachment"),
949
            description=_("Add one or more attachments to describe the "
950
                          "sample in this sample, or to specify "
951
                          "your request."),
952
            render_own_label=True,
953
            visible={
954
                'view': 'invisible',
955
                'add': 'edit',
956
                'header_table': 'invisible',
957
            },
958
        )
959
    ),
960
961
    ReferenceField(
962
        'Invoice',
963
        vocabulary_display_path_bound=sys.maxsize,
964
        allowed_types=('Invoice',),
965
        referenceClass=HoldingReference,
966
        relationship='AnalysisRequestInvoice',
967
        mode="rw",
968
        read_permission=View,
969
        write_permission=ModifyPortalContent,
970
        widget=ComputedWidget(
971
            visible={
972
                'edit': 'invisible',
973
                'view': 'visible',
974
            },
975
        )
976
    ),
977
978
    DateTimeField(
979
        'DateReceived',
980
        mode="rw",
981
        read_permission=View,
982
        write_permission=FieldEditDateReceived,
983
        widget=DateTimeWidget(
984
            label=_("Date Sample Received"),
985
            show_time=True,
986
            datepicker_nofuture=1,
987
            description=_("The date when the sample was received"),
988
            render_own_label=True,
989
        ),
990
    ),
991
    ComputedField(
992
        'DatePublished',
993
        mode="r",
994
        read_permission=View,
995
        expression="here.getDatePublished().strftime('%Y-%m-%d %H:%M %p') if here.getDatePublished() else ''",
996
        widget=DateTimeWidget(
997
            label=_("Date Published"),
998
            visible={
999
                'edit': 'invisible',
1000
                'add': 'invisible',
1001
                'secondary': 'invisible',
1002
            },
1003
        ),
1004
    ),
1005
1006
    RemarksField(
1007
        'Remarks',
1008
        searchable=True,
1009
        read_permission=View,
1010
        write_permission=FieldEditRemarks,
1011
        widget=RemarksWidget(
1012
            label=_("Remarks"),
1013
            description=_("Remarks and comments for this request"),
1014
            render_own_label=True,
1015
            visible={
1016
                'add': 'edit',
1017
                'header_table': 'invisible',
1018
            },
1019
        ),
1020
    ),
1021
1022
    FixedPointField(
1023
        'MemberDiscount',
1024
        default_method='getDefaultMemberDiscount',
1025
        mode="rw",
1026
        read_permission=View,
1027
        write_permission=FieldEditMemberDiscount,
1028
        widget=DecimalWidget(
1029
            label=_("Member discount %"),
1030
            description=_("Enter percentage value eg. 33.0"),
1031
            render_own_label=True,
1032
            visible={
1033
                'add': 'invisible',
1034
            },
1035
        ),
1036
    ),
1037
    # TODO-catalog: move all these computed fields to methods
1038
    ComputedField(
1039
        'ClientUID',
1040
        expression='here.aq_parent.UID()',
1041
        widget=ComputedWidget(
1042
            visible=False,
1043
        ),
1044
    ),
1045
1046
    ComputedField(
1047
        'SampleTypeTitle',
1048
        expression="here.getSampleType().Title() if here.getSampleType() "
1049
                   "else ''",
1050
        widget=ComputedWidget(
1051
            visible=False,
1052
        ),
1053
    ),
1054
1055
    ComputedField(
1056
        'SamplePointTitle',
1057
        expression="here.getSamplePoint().Title() if here.getSamplePoint() "
1058
                   "else ''",
1059
        widget=ComputedWidget(
1060
            visible=False,
1061
        ),
1062
    ),
1063
1064
    ComputedField(
1065
        'ContactUID',
1066
        expression="here.getContact() and here.getContact().UID() or ''",
1067
        widget=ComputedWidget(
1068
            visible=False,
1069
        ),
1070
    ),
1071
1072
    ComputedField(
1073
        'ProfilesUID',
1074
        expression="[p.UID() for p in here.getProfiles()] " \
1075
                   "if here.getProfiles() else []",
1076
        widget=ComputedWidget(
1077
            visible=False,
1078
        ),
1079
    ),
1080
1081
    ComputedField(
1082
        'Invoiced',
1083
        expression='here.getInvoice() and True or False',
1084
        default=False,
1085
        widget=ComputedWidget(
1086
            visible=False,
1087
        ),
1088
    ),
1089
    ComputedField(
1090
        'ReceivedBy',
1091
        expression='here.getReceivedBy()',
1092
        default='',
1093
        widget=ComputedWidget(visible=False,),
1094
    ),
1095
    ComputedField(
1096
        'CreatorFullName',
1097
        expression="here._getCreatorFullName()",
1098
        widget=ComputedWidget(visible=False),
1099
    ),
1100
    ComputedField(
1101
        'CreatorEmail',
1102
        expression="here._getCreatorEmail()",
1103
        widget=ComputedWidget(visible=False),
1104
    ),
1105
    ComputedField(
1106
        'SamplingRoundUID',
1107
        expression="here.getSamplingRound().UID() " \
1108
                   "if here.getSamplingRound() else ''",
1109
        widget=ComputedWidget(visible=False),
1110
    ),
1111
    ComputedField(
1112
        'SamplerFullName',
1113
        expression="here._getSamplerFullName()",
1114
        widget=ComputedWidget(visible=False),
1115
    ),
1116
    ComputedField(
1117
        'SamplerEmail',
1118
        expression="here._getSamplerEmail()",
1119
        widget=ComputedWidget(visible=False),
1120
    ),
1121
    ComputedField(
1122
        'BatchID',
1123
        expression="here.getBatch().getId() if here.getBatch() else ''",
1124
        widget=ComputedWidget(visible=False),
1125
    ),
1126
    ComputedField(
1127
        'BatchURL',
1128
        expression="here.getBatch().absolute_url_path() " \
1129
                   "if here.getBatch() else ''",
1130
        widget=ComputedWidget(visible=False),
1131
    ),
1132
    ComputedField(
1133
        'ClientUID',
1134
        expression="here.getClient().UID() if here.getClient() else ''",
1135
        widget=ComputedWidget(visible=False),
1136
    ),
1137
    ComputedField(
1138
        'ClientID',
1139
        expression="here.getClient().getClientID() if here.getClient() else ''",
1140
        widget=ComputedWidget(visible=False),
1141
    ),
1142
    ComputedField(
1143
        'ClientTitle',
1144
        expression="here.getClient().Title() if here.getClient() else ''",
1145
        widget=ComputedWidget(visible=False),
1146
    ),
1147
    ComputedField(
1148
        'ClientURL',
1149
        expression="here.getClient().absolute_url_path() " \
1150
                   "if here.getClient() else ''",
1151
        widget=ComputedWidget(visible=False),
1152
    ),
1153
    ComputedField(
1154
        'ContactUsername',
1155
        expression="here.getContact().getUsername() " \
1156
                   "if here.getContact() else ''",
1157
        widget=ComputedWidget(visible=False),
1158
    ),
1159
    ComputedField(
1160
        'ContactFullName',
1161
        expression="here.getContact().getFullname() " \
1162
                   "if here.getContact() else ''",
1163
        widget=ComputedWidget(visible=False),
1164
    ),
1165
    ComputedField(
1166
        'ContactEmail',
1167
        expression="here.getContact().getEmailAddress() " \
1168
                   "if here.getContact() else ''",
1169
        widget=ComputedWidget(visible=False),
1170
    ),
1171
    ComputedField(
1172
        'SampleTypeUID',
1173
        expression="here.getSampleType().UID() " \
1174
                   "if here.getSampleType() else ''",
1175
        widget=ComputedWidget(visible=False),
1176
    ),
1177
    ComputedField(
1178
        'SamplePointUID',
1179
        expression="here.getSamplePoint().UID() " \
1180
                   "if here.getSamplePoint() else ''",
1181
        widget=ComputedWidget(visible=False),
1182
    ),
1183
    ComputedField(
1184
        'StorageLocationUID',
1185
        expression="here.getStorageLocation().UID() " \
1186
                   "if here.getStorageLocation() else ''",
1187
        widget=ComputedWidget(visible=False),
1188
    ),
1189
    ComputedField(
1190
        'ProfilesURL',
1191
        expression="[p.absolute_url_path() for p in here.getProfiles()] " \
1192
                   "if here.getProfiles() else []",
1193
        widget=ComputedWidget(visible=False),
1194
    ),
1195
    ComputedField(
1196
        'ProfilesTitle',
1197
        expression="[p.Title() for p in here.getProfiles()] " \
1198
                   "if here.getProfiles() else []",
1199
        widget=ComputedWidget(visible=False),
1200
    ),
1201
    ComputedField(
1202
        'ProfilesTitleStr',
1203
        expression="', '.join([p.Title() for p in here.getProfiles()]) " \
1204
                   "if here.getProfiles() else ''",
1205
        widget=ComputedWidget(visible=False),
1206
    ),
1207
    ComputedField(
1208
        'TemplateUID',
1209
        expression="here.getTemplate().UID() if here.getTemplate() else ''",
1210
        widget=ComputedWidget(visible=False),
1211
    ),
1212
    ComputedField(
1213
        'TemplateURL',
1214
        expression="here.getTemplate().absolute_url_path() " \
1215
                   "if here.getTemplate() else ''",
1216
        widget=ComputedWidget(visible=False),
1217
    ),
1218
    ComputedField(
1219
        'TemplateTitle',
1220
        expression="here.getTemplate().Title() if here.getTemplate() else ''",
1221
        widget=ComputedWidget(visible=False),
1222
    ),
1223
1224
    ReferenceField(
1225
        'ParentAnalysisRequest',
1226
        allowed_types=('AnalysisRequest',),
1227
        relationship='AnalysisRequestParentAnalysisRequest',
1228
        referenceClass=HoldingReference,
1229
        mode="rw",
1230
        read_permission=View,
1231
        write_permission=ModifyPortalContent,
1232
        widget=ReferenceWidget(
1233
            visible=False,
1234
        ),
1235
    ),
1236
1237
    # The Analysis Request the current Analysis Request comes from because of
1238
    # an invalidation of the former
1239
    ReferenceField(
1240
        'Invalidated',
1241
        allowed_types=('AnalysisRequest',),
1242
        relationship='AnalysisRequestRetracted',
1243
        referenceClass=HoldingReference,
1244
        mode="rw",
1245
        read_permission=View,
1246
        write_permission=ModifyPortalContent,
1247
        widget=ReferenceWidget(
1248
            visible=False,
1249
        ),
1250
    ),
1251
1252
    # The Analysis Request that was automatically generated due to the
1253
    # invalidation of the current Analysis Request
1254
    ComputedField(
1255
        'Retest',
1256
        expression="here.get_retest()",
1257
        widget=ComputedWidget(visible=False)
1258
    ),
1259
1260
    # For comments or results interpretation
1261
    # Old one, to be removed because of the incorporation of
1262
    # ResultsInterpretationDepts (due to LIMS-1628)
1263
    TextField(
1264
        'ResultsInterpretation',
1265
        mode="rw",
1266
        default_content_type='text/html',
1267
        # Input content type for the textfield
1268
        default_output_type='text/x-html-safe',
1269
        # getResultsInterpretation returns a str with html tags
1270
        # to conserve the txt format in the report.
1271
        read_permission=View,
1272
        write_permission=FieldEditResultsInterpretation,
1273
        widget=RichWidget(
1274
            description=_("Comments or results interpretation"),
1275
            label=_("Results Interpretation"),
1276
            size=10,
1277
            allow_file_upload=False,
1278
            default_mime_type='text/x-rst',
1279
            output_mime_type='text/x-html',
1280
            rows=3,
1281
            visible=False),
1282
    ),
1283
1284
    RecordsField(
1285
        'ResultsInterpretationDepts',
1286
        read_permission=View,
1287
        write_permission=FieldEditResultsInterpretation,
1288
        subfields=('uid', 'richtext'),
1289
        subfield_labels={
1290
            'uid': _('Department'),
1291
            'richtext': _('Results Interpretation')},
1292
        widget=RichWidget(visible=False),
1293
     ),
1294
    # Custom settings for the assigned analysis services
1295
    # https://jira.bikalabs.com/browse/LIMS-1324
1296
    # Fields:
1297
    #   - uid: Analysis Service UID
1298
    #   - hidden: True/False. Hide/Display in results reports
1299
    RecordsField('AnalysisServicesSettings',
1300
                 required=0,
1301
                 subfields=('uid', 'hidden',),
1302
                 widget=ComputedWidget(visible=False),
1303
                 ),
1304
    StringField(
1305
        'Printed',
1306
        mode="rw",
1307
        read_permission=View,
1308
        widget=StringWidget(
1309
            label=_("Printed"),
1310
            description=_("Indicates if the last SampleReport is printed,"),
1311
            visible=False,
1312
        ),
1313
    ),
1314
)
1315
)
1316
1317
1318
# Some schema rearrangement
1319
schema['title'].required = False
1320
schema['id'].widget.visible = False
1321
schema['title'].widget.visible = False
1322
schema.moveField('Client', before='Contact')
1323
schema.moveField('ResultsInterpretation', pos='bottom')
1324
schema.moveField('ResultsInterpretationDepts', pos='bottom')
1325
schema.moveField("PrimaryAnalysisRequest", before="Client")
1326
1327
1328
class AnalysisRequest(BaseFolder):
1329
    implements(IAnalysisRequest, ICancellable)
1330
    security = ClassSecurityInfo()
1331
    displayContentsTab = False
1332
    schema = schema
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable schema does not seem to be defined.
Loading history...
1333
1334
    _at_rename_after_creation = True
1335
1336
    def _renameAfterCreation(self, check_auto_id=False):
1337
        from bika.lims.idserver import renameAfterCreation
1338
        renameAfterCreation(self)
1339
1340
    def _getCatalogTool(self):
1341
        from bika.lims.catalog import getCatalog
1342
        return getCatalog(self)
1343
1344
    def Title(self):
1345
        """ Return the Request ID as title """
1346
        return self.getId()
1347
1348
    def sortable_title(self):
1349
        """
1350
        Some lists expects this index
1351
        """
1352
        return self.getId()
1353
1354
    def Description(self):
1355
        """Returns searchable data as Description"""
1356
        descr = " ".join((self.getId(), self.aq_parent.Title()))
1357
        return safe_unicode(descr).encode('utf-8')
1358
1359
    def getClient(self):
1360
        if self.aq_parent.portal_type == 'Client':
1361
            return self.aq_parent
1362
        if self.aq_parent.portal_type == 'Batch':
1363
            return self.aq_parent.getClient()
1364
        return ''
1365
1366
    def getClientPath(self):
1367
        return "/".join(self.aq_parent.getPhysicalPath())
1368
1369
    def getProfilesTitle(self):
1370
        return [profile.Title() for profile in self.getProfiles()]
1371
1372
    def setPublicationSpecification(self, value):
1373
        """Never contains a value; this field is here for the UI." \
1374
        """
1375
        return value
1376
1377
    def getAnalysisService(self):
1378
        proxies = self.getAnalyses(full_objects=False)
1379
        value = set()
1380
        for proxy in proxies:
1381
            value.add(proxy.Title)
1382
        return list(value)
1383
1384
    def getAnalysts(self):
1385
        proxies = self.getAnalyses(full_objects=True)
1386
        value = []
1387
        for proxy in proxies:
1388
            val = proxy.getAnalyst()
1389
            if val not in value:
1390
                value.append(val)
1391
        return value
1392
1393
    def getDistrict(self):
1394
        client = self.aq_parent
1395
        return client.getDistrict()
1396
1397
    def getProvince(self):
1398
        client = self.aq_parent
1399
        return client.getProvince()
1400
1401
    @security.public
1402
    def getBatch(self):
1403
        # The parent type may be "Batch" during ar_add.
1404
        # This function fills the hidden field in ar_add.pt
1405
        if self.aq_parent.portal_type == 'Batch':
1406
            return self.aq_parent
1407
        else:
1408
            return self.Schema()['Batch'].get(self)
1409
1410
    @security.public
1411
    def getBatchUID(self):
1412
        batch = self.getBatch()
1413
        if batch:
1414
            return batch.UID()
1415
1416
    @security.public
1417
    def setBatch(self, value=None):
1418
        original_value = self.Schema().getField('Batch').get(self)
1419
        if original_value != value:
1420
            self.Schema().getField('Batch').set(self, value)
1421
            self._reindexAnalyses(['getBatchUID'], False)
1422
1423
    def getDefaultMemberDiscount(self):
1424
        """Compute default member discount if it applies
1425
        """
1426
        if hasattr(self, 'getMemberDiscountApplies'):
1427
            if self.getMemberDiscountApplies():
1428
                settings = self.bika_setup
1429
                return settings.getMemberDiscount()
1430
            else:
1431
                return "0.00"
1432
1433
    @security.public
1434
    def getAnalysesNum(self):
1435
        """ Returns an array with the number of analyses for the current AR in
1436
            different statuses, like follows:
1437
                [verified, total, not_submitted, to_be_verified]
1438
        """
1439
        an_nums = [0, 0, 0, 0]
1440
        for analysis in self.getAnalyses():
1441
            review_state = analysis.review_state
1442
            if review_state in ['retracted', 'rejected', 'cancelled']:
1443
                continue
1444
            if review_state == 'to_be_verified':
1445
                an_nums[3] += 1
1446
            elif review_state in ['published', 'verified']:
1447
                an_nums[0] += 1
1448
            else:
1449
                an_nums[2] += 1
1450
            an_nums[1] += 1
1451
        return an_nums
1452
1453
    @security.public
1454
    def getResponsible(self):
1455
        """Return all manager info of responsible departments
1456
        """
1457
        managers = {}
1458
        for department in self.getDepartments():
1459
            manager = department.getManager()
1460
            if manager is None:
1461
                continue
1462
            manager_id = manager.getId()
1463
            if manager_id not in managers:
1464
                managers[manager_id] = {}
1465
                managers[manager_id]['salutation'] = safe_unicode(
1466
                    manager.getSalutation())
1467
                managers[manager_id]['name'] = safe_unicode(
1468
                    manager.getFullname())
1469
                managers[manager_id]['email'] = safe_unicode(
1470
                    manager.getEmailAddress())
1471
                managers[manager_id]['phone'] = safe_unicode(
1472
                    manager.getBusinessPhone())
1473
                managers[manager_id]['job_title'] = safe_unicode(
1474
                    manager.getJobTitle())
1475
                if manager.getSignature():
1476
                    managers[manager_id]['signature'] = \
1477
                        '{}/Signature'.format(manager.absolute_url())
1478
                else:
1479
                    managers[manager_id]['signature'] = False
1480
                managers[manager_id]['departments'] = ''
1481
            mngr_dept = managers[manager_id]['departments']
1482
            if mngr_dept:
1483
                mngr_dept += ', '
1484
            mngr_dept += safe_unicode(department.Title())
1485
            managers[manager_id]['departments'] = mngr_dept
1486
        mngr_keys = managers.keys()
1487
        mngr_info = {'ids': mngr_keys, 'dict': managers}
1488
1489
        return mngr_info
1490
1491
    @security.public
1492
    def getManagers(self):
1493
        """Return all managers of responsible departments
1494
        """
1495
        manager_ids = []
1496
        manager_list = []
1497
        for department in self.getDepartments():
1498
            manager = department.getManager()
1499
            if manager is None:
1500
                continue
1501
            manager_id = manager.getId()
1502
            if manager_id not in manager_ids:
1503
                manager_ids.append(manager_id)
1504
                manager_list.append(manager)
1505
        return manager_list
1506
1507
    def getDueDate(self):
1508
        """Returns the earliest due date of the analyses this Analysis Request
1509
        contains."""
1510
        due_dates = map(lambda an: an.getDueDate, self.getAnalyses())
1511
        return due_dates and min(due_dates) or None
1512
1513
    security.declareProtected(View, 'getLate')
1514
1515
    def getLate(self):
1516
        """Return True if there is at least one late analysis in this Request
1517
        """
1518
        for analysis in self.getAnalyses():
1519
            if analysis.review_state == "retracted":
1520
                continue
1521
            analysis_obj = api.get_object(analysis)
1522
            if analysis_obj.isLateAnalysis():
1523
                return True
1524
        return False
1525
1526
    def getPrinted(self):
1527
        """ returns "0", "1" or "2" to indicate Printed state.
1528
            0 -> Never printed.
1529
            1 -> Printed after last publish
1530
            2 -> Printed but republished afterwards.
1531
        """
1532
        workflow = getToolByName(self, 'portal_workflow')
1533
        review_state = workflow.getInfoFor(self, 'review_state', '')
1534
        if review_state not in ['published']:
1535
            return "0"
1536
        report_list = sorted(self.objectValues('ARReport'),
1537
                             key=lambda report: report.getDatePublished())
1538
        if not report_list:
1539
            return "0"
1540
        last_report = report_list[-1]
1541
        if last_report.getDatePrinted():
1542
            return "1"
1543
        else:
1544
            for report in report_list:
1545
                if report.getDatePrinted():
1546
                    return "2"
1547
        return "0"
1548
1549
    @security.protected(View)
1550
    def getBillableItems(self):
1551
        """Returns the items to be billed
1552
        """
1553
        # Assigned profiles
1554
        profiles = self.getProfiles()
1555
        # Billable profiles which have a fixed price set
1556
        billable_profiles = filter(
1557
            lambda pr: pr.getUseAnalysisProfilePrice(), profiles)
1558
        # All services contained in the billable profiles
1559
        billable_profile_services = reduce(lambda a, b: a+b, map(
0 ignored issues
show
Comprehensibility Best Practice introduced by
Undefined variable 'reduce'
Loading history...
1560
            lambda profile: profile.getService(), billable_profiles), [])
1561
        # Keywords of the contained services
1562
        billable_service_keys = map(
1563
            lambda s: s.getKeyword(), set(billable_profile_services))
1564
        # The billable items contain billable profiles and single selected analyses
1565
        billable_items = billable_profiles
1566
        # Get the analyses to be billed
1567
        exclude_rs = ["retracted", "rejected"]
1568
        for analysis in self.getAnalyses(is_active=True):
1569
            if analysis.review_state in exclude_rs:
1570
                continue
1571
            if analysis.getKeyword in billable_service_keys:
1572
                continue
1573
            billable_items.append(api.get_object(analysis))
1574
        return billable_items
1575
1576
    @security.protected(View)
1577
    def getSubtotal(self):
1578
        """Compute Subtotal (without member discount and without vat)
1579
        """
1580
        return sum([Decimal(obj.getPrice()) for obj in self.getBillableItems()])
1581
1582
    @security.protected(View)
1583
    def getSubtotalVATAmount(self):
1584
        """Compute VAT amount without member discount
1585
        """
1586
        return sum([Decimal(o.getVATAmount()) for o in self.getBillableItems()])
1587
1588
    @security.protected(View)
1589
    def getSubtotalTotalPrice(self):
1590
        """Compute the price with VAT but no member discount
1591
        """
1592
        return self.getSubtotal() + self.getSubtotalVATAmount()
1593
1594
    @security.protected(View)
1595
    def getDiscountAmount(self):
1596
        """It computes and returns the analysis service's discount amount
1597
        without VAT
1598
        """
1599
        has_client_discount = self.aq_parent.getMemberDiscountApplies()
1600
        if has_client_discount:
1601
            discount = Decimal(self.getDefaultMemberDiscount())
1602
            return Decimal(self.getSubtotal() * discount / 100)
1603
        else:
1604
            return 0
1605
1606
    @security.protected(View)
1607
    def getVATAmount(self):
1608
        """It computes the VAT amount from (subtotal-discount.)*VAT/100, but
1609
        each analysis has its own VAT!
1610
1611
        :returns: the analysis request VAT amount with the discount
1612
        """
1613
        has_client_discount = self.aq_parent.getMemberDiscountApplies()
1614
        VATAmount = self.getSubtotalVATAmount()
1615
        if has_client_discount:
1616
            discount = Decimal(self.getDefaultMemberDiscount())
1617
            return Decimal((1 - discount / 100) * VATAmount)
1618
        else:
1619
            return VATAmount
1620
1621
    @security.protected(View)
1622
    def getTotalPrice(self):
1623
        """It gets the discounted price from analyses and profiles to obtain the
1624
        total value with the VAT and the discount applied
1625
1626
        :returns: analysis request's total price including VATs and discounts
1627
        """
1628
        price = (self.getSubtotal() - self.getDiscountAmount() +
1629
                 self.getVATAmount())
1630
        return price
1631
1632
    getTotal = getTotalPrice
1633
1634
    @security.protected(ManageInvoices)
1635
    def createInvoice(self, pdf):
1636
        """Issue invoice
1637
        """
1638
        client = self.getClient()
1639
        invoice = self.getInvoice()
1640
        if not invoice:
1641
            invoice = _createObjectByType("Invoice", client, tmpID())
1642
        invoice.edit(
1643
            AnalysisRequest=self,
1644
            Client=client,
1645
            InvoiceDate=DateTime(),
1646
            InvoicePDF=pdf
1647
        )
1648
        invoice.processForm()
1649
        self.setInvoice(invoice)
1650
        return invoice
1651
1652
    @security.public
1653
    def printInvoice(self, REQUEST=None, RESPONSE=None):
1654
        """Print invoice
1655
        """
1656
        invoice = self.getInvoice()
1657
        invoice_url = invoice.absolute_url()
1658
        RESPONSE.redirect('{}/invoice_print'.format(invoice_url))
1659
1660
    @deprecated("Use getVerifiers instead")
1661
    @security.public
1662
    def getVerifier(self):
1663
        """Returns the user that verified the whole Analysis Request. Since the
1664
        verification is done automatically as soon as all the analyses it
1665
        contains are verified, this function returns the user that verified the
1666
        last analysis pending.
1667
        """
1668
        wtool = getToolByName(self, 'portal_workflow')
1669
        mtool = getToolByName(self, 'portal_membership')
1670
1671
        verifier = None
1672
        # noinspection PyBroadException
1673
        try:
1674
            review_history = wtool.getInfoFor(self, 'review_history')
1675
        except:  # noqa FIXME: remove blind except!
1676
            return 'access denied'
1677
1678
        if not review_history:
1679
            return 'no history'
1680
        for items in review_history:
1681
            action = items.get('action')
1682
            if action != 'verify':
1683
                continue
1684
            actor = items.get('actor')
1685
            member = mtool.getMemberById(actor)
1686
            verifier = member.getProperty('fullname')
1687
            if verifier is None or verifier == '':
1688
                verifier = actor
1689
        return verifier
1690
1691
    @security.public
1692
    def getVerifiersIDs(self):
1693
        """Returns the ids from users that have verified at least one analysis
1694
        from this Analysis Request
1695
        """
1696
        verifiers_ids = list()
1697
        for brain in self.getAnalyses():
1698
            verifiers_ids += brain.getVerificators
1699
        return list(set(verifiers_ids))
1700
1701
    @security.public
1702
    def getVerifiers(self):
1703
        """Returns the list of lab contacts that have verified at least one
1704
        analysis from this Analysis Request
1705
        """
1706
        contacts = list()
1707
        for verifier in self.getVerifiersIDs():
1708
            user = api.get_user(verifier)
1709
            contact = api.get_user_contact(user, ["LabContact"])
1710
            if contact:
1711
                contacts.append(contact)
1712
        return contacts
1713
1714
    security.declarePublic('getContactUIDForUser')
1715
1716
    def getContactUIDForUser(self):
1717
        """get the UID of the contact associated with the authenticated user
1718
        """
1719
        mt = getToolByName(self, 'portal_membership')
1720
        user = mt.getAuthenticatedMember()
1721
        user_id = user.getUserName()
1722
        pc = getToolByName(self, 'portal_catalog')
1723
        r = pc(portal_type='Contact',
1724
               getUsername=user_id)
1725
        if len(r) == 1:
1726
            return r[0].UID
1727
1728
    security.declarePublic('current_date')
1729
1730
    def current_date(self):
1731
        """return current date
1732
        """
1733
        # noinspection PyCallingNonCallable
1734
        return DateTime()
1735
1736
    def getQCAnalyses(self, qctype=None, review_state=None):
1737
        """return the QC analyses performed in the worksheet in which, at
1738
        least, one sample of this AR is present.
1739
1740
        Depending on qctype value, returns the analyses of:
1741
1742
            - 'b': all Blank Reference Samples used in related worksheet/s
1743
            - 'c': all Control Reference Samples used in related worksheet/s
1744
            - 'd': duplicates only for samples contained in this AR
1745
1746
        If qctype==None, returns all type of qc analyses mentioned above
1747
        """
1748
        qcanalyses = []
1749
        suids = []
1750
        ans = self.getAnalyses()
1751
        wf = getToolByName(self, 'portal_workflow')
1752
        for an in ans:
1753
            an = an.getObject()
1754
            if an.getServiceUID() not in suids:
1755
                suids.append(an.getServiceUID())
1756
1757
        def valid_dup(wan):
1758
            if wan.portal_type == 'ReferenceAnalysis':
1759
                return False
1760
            an_state = wf.getInfoFor(wan, 'review_state')
1761
            return \
1762
                wan.portal_type == 'DuplicateAnalysis' \
1763
                and wan.getRequestID() == self.id \
1764
                and (review_state is None or an_state in review_state)
1765
1766
        def valid_ref(wan):
1767
            if wan.portal_type != 'ReferenceAnalysis':
1768
                return False
1769
            an_state = wf.getInfoFor(wan, 'review_state')
1770
            an_reftype = wan.getReferenceType()
1771
            return wan.getServiceUID() in suids \
1772
                and wan not in qcanalyses \
1773
                and (qctype is None or an_reftype == qctype) \
1774
                and (review_state is None or an_state in review_state)
1775
1776
        for an in ans:
1777
            an = an.getObject()
1778
            ws = an.getWorksheet()
1779
            if not ws:
1780
                continue
1781
            was = ws.getAnalyses()
1782
            for wa in was:
1783
                if valid_dup(wa):
1784
                    qcanalyses.append(wa)
1785
                elif valid_ref(wa):
1786
                    qcanalyses.append(wa)
1787
1788
        return qcanalyses
1789
1790
    def isInvalid(self):
1791
        """return if the Analysis Request has been invalidated
1792
        """
1793
        workflow = getToolByName(self, 'portal_workflow')
1794
        return workflow.getInfoFor(self, 'review_state') == 'invalid'
1795
1796
    def getSamplingRoundUID(self):
1797
        """Obtains the sampling round UID
1798
        :returns: UID
1799
        """
1800
        sr = self.getSamplingRound()
1801
        if sr:
1802
            return sr.UID()
1803
        else:
1804
            return ''
1805
1806
    def getStorageLocationTitle(self):
1807
        """ A method for AR listing catalog metadata
1808
        :return: Title of Storage Location
1809
        """
1810
        sl = self.getStorageLocation()
1811
        if sl:
1812
            return sl.Title()
1813
        return ''
1814
1815
    @security.public
1816
    def getResultsRange(self):
1817
        """Returns the valid result ranges for the analyses this Analysis
1818
        Request contains.
1819
1820
        By default uses the result ranges defined in the Analysis Specification
1821
        set in "Specification" field if any. Values manually set through
1822
        `ResultsRange` field for any given analysis keyword have priority over
1823
        the result ranges defined in "Specification" field.
1824
1825
        :return: A list of dictionaries, where each dictionary defines the
1826
            result range to use for any analysis contained in this Analysis
1827
            Request for the keyword specified. Each dictionary has, at least,
1828
                the following keys: "keyword", "min", "max"
1829
        :rtype: dict
1830
        """
1831
        specs_range = []
1832
        specification = self.getSpecification()
1833
        if specification:
1834
            specs_range = specification.getResultsRange()
1835
            specs_range = specs_range and specs_range or []
1836
1837
        # Override with AR's custom ranges
1838
        ar_range = self.Schema().getField("ResultsRange").get(self)
1839
        if not ar_range:
1840
            return specs_range
1841
1842
        # Remove those analysis ranges that neither min nor max are floatable
1843
        an_specs = [an for an in ar_range if
1844
                    api.is_floatable(an.get('min', None)) or
1845
                    api.is_floatable(an.get('max', None))]
1846
        # Want to know which are the analyses that needs to be overriden
1847
        keywords = map(lambda item: item.get('keyword'), an_specs)
1848
        # Get rid of those analyses to be overriden
1849
        out_specs = [sp for sp in specs_range if sp['keyword'] not in keywords]
1850
        # Add manually set ranges
1851
        out_specs.extend(an_specs)
1852
        return map(lambda spec: ResultsRangeDict(spec), out_specs)
1853
1854
    def getDatePublished(self):
1855
        """
1856
        Returns the transition date from the Analysis Request object
1857
        """
1858
        return getTransitionDate(self, 'publish', return_as_datetime=True)
1859
1860
    @security.public
1861
    def getSamplingDeviationTitle(self):
1862
        """
1863
        It works as a metacolumn.
1864
        """
1865
        sd = self.getSamplingDeviation()
1866
        if sd:
1867
            return sd.Title()
1868
        return ''
1869
1870
    @security.public
1871
    def getHazardous(self):
1872
        """
1873
        It works as a metacolumn.
1874
        """
1875
        sample_type = self.getSampleType()
1876
        if sample_type:
1877
            return sample_type.getHazardous()
1878
        return False
1879
1880
    @security.public
1881
    def getContactURL(self):
1882
        """
1883
        It works as a metacolumn.
1884
        """
1885
        contact = self.getContact()
1886
        if contact:
1887
            return contact.absolute_url_path()
1888
        return ''
1889
1890
    @security.public
1891
    def getSamplingWorkflowEnabled(self):
1892
        """Returns True if the sample of this Analysis Request has to be
1893
        collected by the laboratory personnel
1894
        """
1895
        template = self.getTemplate()
1896
        if template:
1897
            return template.getSamplingRequired()
1898
        return self.bika_setup.getSamplingWorkflowEnabled()
1899
1900
    def getSamplers(self):
1901
        return getUsers(self, ['Sampler', ])
1902
1903
    def getPreservers(self):
1904
        return getUsers(self, ['Preserver', 'Sampler'])
1905
1906
    def getDepartments(self):
1907
        """Returns a list of the departments assigned to the Analyses
1908
        from this Analysis Request
1909
        """
1910
        departments = list()
1911
        for analysis in self.getAnalyses(full_objects=True):
1912
            department = analysis.getDepartment()
1913
            if department and department not in departments:
1914
                departments.append(department)
1915
        return departments
1916
1917
    def getResultsInterpretationByDepartment(self, department=None):
1918
        """Returns the results interpretation for this Analysis Request
1919
           and department. If department not set, returns the results
1920
           interpretation tagged as 'General'.
1921
1922
        :returns: a dict with the following keys:
1923
            {'uid': <department_uid> or 'general', 'richtext': <text/plain>}
1924
        """
1925
        uid = department.UID() if department else 'general'
1926
        rows = self.Schema()['ResultsInterpretationDepts'].get(self)
1927
        row = [row for row in rows if row.get('uid') == uid]
1928
        if len(row) > 0:
1929
            row = row[0]
1930
        elif uid == 'general' \
1931
                and hasattr(self, 'getResultsInterpretation') \
1932
                and self.getResultsInterpretation():
1933
            row = {'uid': uid, 'richtext': self.getResultsInterpretation()}
1934
        else:
1935
            row = {'uid': uid, 'richtext': ''}
1936
        return row
1937
1938
    def getAnalysisServiceSettings(self, uid):
1939
        """Returns a dictionary with the settings for the analysis service that
1940
        match with the uid provided.
1941
1942
        If there are no settings for the analysis service and
1943
        analysis requests:
1944
1945
        1. looks for settings in AR's ARTemplate. If found, returns the
1946
           settings for the AnalysisService set in the Template
1947
        2. If no settings found, looks in AR's ARProfile. If found, returns the
1948
           settings for the AnalysisService from the AR Profile. Otherwise,
1949
           returns a one entry dictionary with only the key 'uid'
1950
        """
1951
        sets = [s for s in self.getAnalysisServicesSettings()
1952
                if s.get('uid', '') == uid]
1953
1954
        # Created by using an ARTemplate?
1955
        if not sets and self.getTemplate():
1956
            adv = self.getTemplate().getAnalysisServiceSettings(uid)
1957
            sets = [adv] if 'hidden' in adv else []
1958
1959
        # Created by using an AR Profile?
1960
        if not sets and self.getProfiles():
1961
            adv = []
1962
            adv += [profile.getAnalysisServiceSettings(uid) for profile in
1963
                    self.getProfiles()]
1964
            sets = adv if 'hidden' in adv[0] else []
1965
1966
        return sets[0] if sets else {'uid': uid}
1967
1968
    # TODO Sample Cleanup - Remove this function
1969
    def getPartitions(self):
1970
        """This functions returns the partitions from the analysis request's
1971
        analyses.
1972
1973
        :returns: a list with the full partition objects
1974
        """
1975
        partitions = []
1976
        for analysis in self.getAnalyses(full_objects=True):
1977
            if analysis.getSamplePartition() not in partitions:
1978
                partitions.append(analysis.getSamplePartition())
1979
        return partitions
1980
1981
    # TODO Sample Cleanup - Remove (Use getContainer instead)
1982
    def getContainers(self):
1983
        """This functions returns the containers from the analysis request's
1984
        analyses
1985
1986
        :returns: a list with the full partition objects
1987
        """
1988
        return self.getContainer() and [self.getContainer] or []
1989
1990 View Code Duplication
    def isAnalysisServiceHidden(self, uid):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1991
        """Checks if the analysis service that match with the uid provided must
1992
        be hidden in results. If no hidden assignment has been set for the
1993
        analysis in this request, returns the visibility set to the analysis
1994
        itself.
1995
1996
        Raise a TypeError if the uid is empty or None
1997
1998
        Raise a ValueError if there is no hidden assignment in this request or
1999
        no analysis service found for this uid.
2000
        """
2001
        if not uid:
2002
            raise TypeError('None type or empty uid')
2003
        sets = self.getAnalysisServiceSettings(uid)
2004
        if 'hidden' not in sets:
2005
            uc = getToolByName(self, 'uid_catalog')
2006
            serv = uc(UID=uid)
2007
            if serv and len(serv) == 1:
2008
                return serv[0].getObject().getRawHidden()
2009
            else:
2010
                raise ValueError('{} is not valid'.format(uid))
2011
        return sets.get('hidden', False)
2012
2013
    def getRejecter(self):
2014
        """If the Analysis Request has been rejected, returns the user who did the
2015
        rejection. If it was not rejected or the current user has not enough
2016
        privileges to access to this information, returns None.
2017
        """
2018
        wtool = getToolByName(self, 'portal_workflow')
2019
        mtool = getToolByName(self, 'portal_membership')
2020
        # noinspection PyBroadException
2021
        try:
2022
            review_history = wtool.getInfoFor(self, 'review_history')
2023
        except:  # noqa FIXME: remove blind except!
2024
            return None
2025
        for items in review_history:
2026
            action = items.get('action')
2027
            if action != 'reject':
2028
                continue
2029
            actor = items.get('actor')
2030
            return mtool.getMemberById(actor)
2031
        return None
2032
2033
    def getReceivedBy(self):
2034
        """
2035
        Returns the User who received the analysis request.
2036
        :returns: the user id
2037
        """
2038
        user = getTransitionUsers(self, 'receive', last_user=True)
2039
        return user[0] if user else ''
2040
2041
    def getDateVerified(self):
2042
        """
2043
        Returns the date of verification as a DateTime object.
2044
        """
2045
        return getTransitionDate(self, 'verify', return_as_datetime=True)
2046
2047
    @security.public
2048
    def getPrioritySortkey(self):
2049
        """Returns the key that will be used to sort the current Analysis
2050
        Request based on both its priority and creation date. On ASC sorting,
2051
        the oldest item with highest priority will be displayed.
2052
        :return: string used for sorting
2053
        """
2054
        priority = self.getPriority()
2055
        created_date = self.created().ISO8601()
2056
        return '%s.%s' % (priority, created_date)
2057
2058
    @security.public
2059
    def setPriority(self, value):
2060
        if not value:
2061
            value = self.Schema().getField('Priority').getDefault(self)
2062
        original_value = self.Schema().getField('Priority').get(self)
2063
        if original_value != value:
2064
            self.Schema().getField('Priority').set(self, value)
2065
            self._reindexAnalyses(['getPrioritySortkey'], True)
2066
2067
    @security.private
2068
    def _reindexAnalyses(self, idxs=None, update_metadata=False):
2069
        if not idxs and not update_metadata:
2070
            return
2071
        if not idxs:
2072
            idxs = []
2073
        analyses = self.getAnalyses()
2074
        catalog = getToolByName(self, CATALOG_ANALYSIS_LISTING)
2075
        for analysis in analyses:
2076
            analysis_obj = analysis.getObject()
2077
            catalog.reindexObject(analysis_obj, idxs=idxs, update_metadata=1)
2078
2079
    def _getCreatorFullName(self):
2080
        """
2081
        Returns the full name of this analysis request's creator.
2082
        """
2083
        return user_fullname(self, self.Creator())
2084
2085
    def _getCreatorEmail(self):
2086
        """
2087
        Returns the email of this analysis request's creator.
2088
        """
2089
        return user_email(self, self.Creator())
2090
2091
    def _getSamplerFullName(self):
2092
        """
2093
        Returns the full name's defined sampler.
2094
        """
2095
        return user_fullname(self, self.getSampler())
2096
2097
    def _getSamplerEmail(self):
2098
        """
2099
        Returns the email of this analysis request's sampler.
2100
        """
2101
        return user_email(self, self.Creator())
2102
2103 View Code Duplication
    def getObjectWorkflowStates(self):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
2104
        """
2105
        This method is used as a metacolumn.
2106
        Returns a dictionary with the workflow id as key and workflow state as
2107
        value.
2108
        :returns: {'review_state':'active',...}
2109
        """
2110
        workflow = getToolByName(self, 'portal_workflow')
2111
        states = {}
2112
        for w in workflow.getWorkflowsFor(self):
2113
            state = w._getWorkflowStateOf(self).id
2114
            states[w.state_var] = state
2115
        return states
2116
2117
    def SearchableText(self):
2118
        """
2119
        Override searchable text logic based on the requirements.
2120
2121
        This method constructs a text blob which contains all full-text
2122
        searchable text for this content item.
2123
        https://docs.plone.org/develop/plone/searching_and_indexing/indexing.html#full-text-searching
2124
        """
2125
2126
        # Speed up string concatenation ops by using a buffer
2127
        entries = []
2128
2129
        # plain text fields we index from ourself,
2130
        # a list of accessor methods of the class
2131
        plain_text_fields = ("getId", )
2132
2133
        def read(acc):
2134
            """
2135
            Call a class accessor method to give a value for certain Archetypes
2136
            field.
2137
            """
2138
            try:
2139
                val = acc()
2140
            except Exception as e:
2141
                message = "Error getting the accessor parameter in " \
2142
                          "SearchableText from the Analysis Request Object " \
2143
                          "{}: {}".format(self.getId(), e.message)
2144
                logger.error(message)
2145
                val = ""
2146
2147
            if val is None:
2148
                val = ""
2149
2150
            return val
2151
2152
        # Concatenate plain text fields as they are
2153
        for f in plain_text_fields:
2154
            accessor = getattr(self, f)
2155
            value = read(accessor)
2156
            entries.append(value)
2157
2158
        # Adding HTML Fields to SearchableText can be uncommented if necessary
2159
        # transforms = getToolByName(self, 'portal_transforms')
2160
        #
2161
        # # Run HTML valued fields through text/plain conversion
2162
        # for f in html_fields:
2163
        #     accessor = getattr(self, f)
2164
        #     value = read(accessor)
2165
        #
2166
        #     if value != "":
2167
        #         stream = transforms.convertTo('text/plain', value,
2168
        #                                       mimetype='text/html')
2169
        #         value = stream.getData()
2170
        #
2171
        #     entries.append(value)
2172
2173
        # Plone accessor methods assume utf-8
2174
        def convertToUTF8(text):
2175
            if type(text) == unicode:
2176
                return text.encode("utf-8")
2177
            return text
2178
2179
        entries = [convertToUTF8(entry) for entry in entries]
2180
2181
        # Concatenate all strings to one text blob
2182
        return " ".join(entries)
2183
2184
    def getPriorityText(self):
2185
        """
2186
        This function looks up the priority text from priorities vocab
2187
        :returns: the priority text or ''
2188
        """
2189
        if self.getPriority():
2190
            return PRIORITIES.getValue(self.getPriority())
2191
        return ''
2192
2193
    def get_ARAttachment(self):
2194
        logger.warn("_ARAttachment is a virtual field used in AR Add. "
2195
                    "It can not hold an own value!")
2196
        return None
2197
2198
    def set_ARAttachment(self, value):
2199
        logger.warn("_ARAttachment is a virtual field used in AR Add. "
2200
                    "It can not hold an own value!")
2201
        return None
2202
2203
    def get_retest(self):
2204
        """Returns the Analysis Request automatically generated because of the
2205
        retraction of the current analysis request
2206
        """
2207
        relationship = "AnalysisRequestRetracted"
2208
        retest = self.getBackReferences(relationship=relationship)
2209
        if retest and len(retest) > 1:
2210
            logger.warn("More than one retest for {0}".format(self.getId()))
2211
        return retest and retest[0] or None
2212
2213
    def getAncestors(self, all_ancestors=True):
2214
        """Returns the ancestor(s) of this Analysis Request
2215
        param all_ancestors: include all ancestors, not only the parent
2216
        """
2217
        parent = self.getParentAnalysisRequest()
2218
        if not parent:
2219
            return list()
2220
        if not all_ancestors:
2221
            return [parent]
2222
        return [parent] + parent.getAncestors(all_ancestors=True)
2223
2224
    def isRootAncestor(self):
2225
        """Returns True if the AR is the root ancestor
2226
2227
        :returns: True if the AR has no more parents
2228
        """
2229
        parent = self.getParentAnalysisRequest()
2230
        if parent:
2231
            return False
2232
        return True
2233
2234
    def getDescendants(self, all_descendants=False):
2235
        """Returns the descendant Analysis Requests
2236
2237
        :param all_descendants: recursively include all descendants
2238
        """
2239
2240
        # N.B. full objects returned here from
2241
        #      `Products.Archetypes.Referenceable.getBRefs`
2242
        #      -> don't add this method into Metadata
2243
        children = self.getBackReferences(
2244
            "AnalysisRequestParentAnalysisRequest")
2245
2246
        descendants = []
2247
2248
        # recursively include all children
2249
        if all_descendants:
2250
            for child in children:
2251
                descendants.append(child)
2252
                descendants += child.getDescendants(all_descendants=True)
2253
        else:
2254
            descendants = children
2255
2256
        return descendants
2257
2258
    def getDescendantsUIDs(self, all_descendants=False):
2259
        """Returns the UIDs of the descendant Analysis Requests
2260
2261
        This method is used as metadata
2262
        """
2263
        descendants = self.getDescendants(all_descendants=all_descendants)
2264
        return map(api.get_uid, descendants)
2265
2266
    def isPartition(self):
2267
        """Returns true if this Analysis Request is a partition
2268
        """
2269
        return not self.isRootAncestor()
2270
2271
    # TODO Remove in favour of getSamplingWorkflowEnabled
2272
    def getSamplingRequired(self):
2273
        """Returns True if the sample of this Analysis Request has to be
2274
        collected by the laboratory personnel
2275
        """
2276
        return self.getSamplingWorkflowEnabled()
2277
2278
    def isOpen(self):
2279
        """Returns whether all analyses from this Analysis Request are open
2280
        (their status is either "assigned" or "unassigned")
2281
        """
2282
        for analysis in self.getAnalyses():
2283
            if not api.get_object(analysis).isOpen():
2284
                return False
2285
        return True
2286
2287
    def setParentAnalysisRequest(self, value):
2288
        """Sets a parent analysis request, making the current a partition
2289
        """
2290
        self.Schema().getField("ParentAnalysisRequest").set(self, value)
2291
        if not value:
2292
            noLongerProvides(self, IAnalysisRequestPartition)
2293
        else:
2294
            alsoProvides(self, IAnalysisRequestPartition)
2295
2296
    def getSecondaryAnalysisRequests(self):
2297
        """Returns the secondary analysis requests from this analysis request
2298
        """
2299
        relationship = "AnalysisRequestPrimaryAnalysisRequest"
2300
        return self.getBackReferences(relationship=relationship)
2301
2302
    def setDateReceived(self, value):
2303
        """Sets the date received to this analysis request and to secondary
2304
        analysis requests
2305
        """
2306
        self.Schema().getField('DateReceived').set(self, value)
2307
        for secondary in self.getSecondaryAnalysisRequests():
2308
            secondary.setDateReceived(value)
2309
            secondary.reindexObject(idxs=["getDateReceived", "is_received"])
2310
2311
    def setDateSampled(self, value):
2312
        """Sets the date sampled to this analysis request and to secondary
2313
        analysis requests
2314
        """
2315
        self.Schema().getField('DateSampled').set(self, value)
2316
        for secondary in self.getSecondaryAnalysisRequests():
2317
            secondary.setDateSampled(value)
2318
            secondary.reindexObject(idxs="getDateSampled")
2319
2320
    def setSamplingDate(self, value):
2321
        """Sets the sampling date to this analysis request and to secondary
2322
        analysis requests
2323
        """
2324
        self.Schema().getField('SamplingDate').set(self, value)
2325
        for secondary in self.getSecondaryAnalysisRequests():
2326
            secondary.setSamplingDate(value)
2327
            secondary.reindexObject(idxs="getSamplingDate")
2328
2329
    def getSelectedRejectionReasons(self):
2330
        """Returns a list with the selected rejection reasons, if any
2331
        """
2332
        reasons = self.getRejectionReasons()
2333
        if not reasons:
2334
            return []
2335
        return reasons[0].get("selected", [])
2336
2337
    def getOtherRejectionReasons(self):
2338
        """Returns other rejection reasons custom text, if any
2339
        """
2340
        reasons = self.getRejectionReasons()
2341
        if not reasons:
2342
            return ""
2343
        return reasons[0].get("other", "")
2344
2345
2346
registerType(AnalysisRequest, PROJECTNAME)
2347