Passed
Push — master ( d135c8...c0d002 )
by Ramon
05:03
created

AnalysisRequest.setDateReceived()   A

Complexity

Conditions 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 5
dl 0
loc 8
rs 10
c 0
b 0
f 0
cc 2
nop 2
1
# -*- coding: utf-8 -*-
2
#
3
# This file is part of SENAITE.CORE
4
#
5
# 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 user_email
74
from bika.lims.utils import user_fullname
75
from bika.lims.workflow import getTransitionDate
76
from bika.lims.workflow import getTransitionUsers
77
from DateTime import DateTime
78
from Products.Archetypes.atapi import BaseFolder
79
from Products.Archetypes.atapi import BooleanField
80
from Products.Archetypes.atapi import BooleanWidget
81
from Products.Archetypes.atapi import ComputedField
82
from Products.Archetypes.atapi import ComputedWidget
83
from Products.Archetypes.atapi import FileField
84
from Products.Archetypes.atapi import FileWidget
85
from Products.Archetypes.atapi import FixedPointField
86
from Products.Archetypes.atapi import ReferenceField
87
from Products.Archetypes.atapi import StringField
88
from Products.Archetypes.atapi import StringWidget
89
from Products.Archetypes.atapi import TextField
90
from Products.Archetypes.atapi import registerType
91
from Products.Archetypes.public import Schema
92
from Products.Archetypes.references import HoldingReference
93
from Products.Archetypes.Widget import RichWidget
94
from Products.ATExtensions.field import RecordsField
95
from Products.CMFCore.permissions import ModifyPortalContent
96
from Products.CMFCore.permissions import View
97
from Products.CMFCore.utils import getToolByName
98
from Products.CMFPlone.utils import _createObjectByType
99
from Products.CMFPlone.utils import safe_unicode
100
from zope.interface import alsoProvides
101
from zope.interface import implements
102
from zope.interface import noLongerProvides
103
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': 'invisible',
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.declareProtected(View, 'getBillableItems')
1550
1551
    def getBillableItems(self):
1552
        """Returns the items to be billed
1553
        """
1554
        def get_keywords_set(profiles):
1555
            keys = list()
1556
            for profile in profiles:
1557
                keys += map(lambda s: s.getKeyword(), profile.getService())
1558
            return set(keys)
1559
1560
        # Profiles with a fixed price, regardless of their analyses
1561
        profiles = self.getProfiles()
1562
        billable_items = filter(lambda pr: pr.getUseAnalysisProfilePrice(),
1563
                                 profiles)
1564
        # Profiles w/o a fixed price. The price is the sum of the individual
1565
        # price for each analysis
1566
        non_billable = filter(lambda p: p not in billable_items, profiles)
1567
        billable_keys = get_keywords_set(non_billable) - \
1568
                        get_keywords_set(billable_items)
1569
1570
        # Get the analyses to be billed
1571
        exclude_rs = ['retracted', 'rejected']
1572
        for analysis in self.getAnalyses(is_active=True):
1573
            if analysis.review_state in exclude_rs:
1574
                continue
1575
            if analysis.getKeyword not in billable_keys:
1576
                continue
1577
            billable_items.append(api.get_object(analysis))
1578
1579
        # Return the analyses that need to be billed individually, together with
1580
        # the profiles with a fixed price
1581
        return billable_items
1582
1583
    # TODO Cleanup - Remove this function, only used in invoice and too complex
1584
    def getServicesAndProfiles(self):
1585
        """This function gets all analysis services and all profiles and removes
1586
        the services belonging to a profile.
1587
1588
        :returns: a tuple of three lists, where the first list contains the
1589
        analyses and the second list the profiles.
1590
        The third contains the analyses objects used by the profiles.
1591
        """
1592
        # profile_analyses contains the profile's analyses (analysis !=
1593
        # service") objects to obtain
1594
        # the correct price later
1595
        exclude_rs = ['retracted', 'rejected']
1596
        analyses = filter(lambda an: an.review_state not in exclude_rs,
1597
                          self.getAnalyses(is_active=True))
1598
        analyses = map(api.get_object, analyses)
1599
        profiles = self.getProfiles()
1600
1601
        # Get the service keys from all profiles
1602
        profiles_keys = list()
1603
        for profile in profiles:
1604
            profile_keys = map(lambda s: s.getKeyword(), profile.getService())
1605
            profiles_keys.extend(profile_keys)
1606
1607
        # Extract the analyses which service is present in at least one profile
1608
        # and those not present (orphan)
1609
        profile_analyses = list()
1610
        orphan_analyses = list()
1611
        for an in analyses:
1612
            if an.getKeyword() in profiles_keys:
1613
                profile_analyses.append(an)
1614
            else:
1615
                orphan_analyses.append(an)
1616
        return analyses, profiles, profile_analyses
1617
1618
    security.declareProtected(View, 'getSubtotal')
1619
1620
    def getSubtotal(self):
1621
        """Compute Subtotal (without member discount and without vat)
1622
        """
1623
        return sum([Decimal(obj.getPrice()) for obj in self.getBillableItems()])
1624
1625
    security.declareProtected(View, 'getSubtotalVATAmount')
1626
1627
    def getSubtotalVATAmount(self):
1628
        """Compute VAT amount without member discount
1629
        """
1630
        return sum([Decimal(o.getVATAmount()) for o in self.getBillableItems()])
1631
1632
    security.declareProtected(View, 'getSubtotalTotalPrice')
1633
1634
    def getSubtotalTotalPrice(self):
1635
        """Compute the price with VAT but no member discount
1636
        """
1637
        return self.getSubtotal() + self.getSubtotalVATAmount()
1638
1639
    security.declareProtected(View, 'getDiscountAmount')
1640
1641
    def getDiscountAmount(self):
1642
        """It computes and returns the analysis service's discount amount
1643
        without VAT
1644
        """
1645
        has_client_discount = self.aq_parent.getMemberDiscountApplies()
1646
        if has_client_discount:
1647
            discount = Decimal(self.getDefaultMemberDiscount())
1648
            return Decimal(self.getSubtotal() * discount / 100)
1649
        else:
1650
            return 0
1651
1652
    def getVATAmount(self):
1653
        """It computes the VAT amount from (subtotal-discount.)*VAT/100, but
1654
        each analysis has its own VAT!
1655
1656
        :returns: the analysis request VAT amount with the discount
1657
        """
1658
        has_client_discount = self.aq_parent.getMemberDiscountApplies()
1659
        VATAmount = self.getSubtotalVATAmount()
1660
        if has_client_discount:
1661
            discount = Decimal(self.getDefaultMemberDiscount())
1662
            return Decimal((1 - discount / 100) * VATAmount)
1663
        else:
1664
            return VATAmount
1665
1666
    security.declareProtected(View, 'getTotalPrice')
1667
1668
    def getTotalPrice(self):
1669
        """It gets the discounted price from analyses and profiles to obtain the
1670
        total value with the VAT and the discount applied
1671
1672
        :returns: analysis request's total price including VATs and discounts
1673
        """
1674
        price = (self.getSubtotal() - self.getDiscountAmount() +
1675
                 self.getVATAmount())
1676
        return price
1677
1678
    getTotal = getTotalPrice
1679
1680
    security.declareProtected(ManageInvoices, 'issueInvoice')
1681
1682
    # noinspection PyUnusedLocal
1683
    def issueInvoice(self, REQUEST=None, RESPONSE=None):
1684
        """Issue invoice
1685
        """
1686
        # check for an adhoc invoice batch for this month
1687
        # noinspection PyCallingNonCallable
1688
        now = DateTime()
1689
        batch_month = now.strftime('%b %Y')
1690
        batch_title = '%s - %s' % (batch_month, 'ad hoc')
1691
        invoice_batch = None
1692
        for b_proxy in self.portal_catalog(portal_type='InvoiceBatch',
1693
                                           Title=batch_title):
1694
            invoice_batch = b_proxy.getObject()
1695
        if not invoice_batch:
1696
            # noinspection PyCallingNonCallable
1697
            first_day = DateTime(now.year(), now.month(), 1)
1698
            start_of_month = first_day.earliestTime()
1699
            last_day = first_day + 31
1700
            # noinspection PyUnresolvedReferences
1701
            while last_day.month() != now.month():
1702
                last_day -= 1
1703
            # noinspection PyUnresolvedReferences
1704
            end_of_month = last_day.latestTime()
1705
1706
            invoices = self.invoices
1707
            batch_id = invoices.generateUniqueId('InvoiceBatch')
1708
            invoice_batch = _createObjectByType("InvoiceBatch", invoices,
1709
                                                batch_id)
1710
            invoice_batch.edit(
1711
                title=batch_title,
1712
                BatchStartDate=start_of_month,
1713
                BatchEndDate=end_of_month,
1714
            )
1715
            invoice_batch.processForm()
1716
1717
        client_uid = self.getClientUID()
1718
        # Get the created invoice
1719
        invoice = invoice_batch.createInvoice(client_uid, [self, ])
1720
        invoice.setAnalysisRequest(self)
1721
        # Set the created invoice in the schema
1722
        self.Schema()['Invoice'].set(self, invoice)
1723
1724
    security.declarePublic('printInvoice')
1725
1726
    # noinspection PyUnusedLocal
1727
    def printInvoice(self, REQUEST=None, RESPONSE=None):
1728
        """Print invoice
1729
        """
1730
        invoice = self.getInvoice()
1731
        invoice_url = invoice.absolute_url()
1732
        RESPONSE.redirect('{}/invoice_print'.format(invoice_url))
1733
1734
    security.declarePublic('getVerifier')
1735
1736
    @deprecated("Use getVerifiers instead")
1737
    def getVerifier(self):
1738
        """Returns the user that verified the whole Analysis Request. Since the
1739
        verification is done automatically as soon as all the analyses it
1740
        contains are verified, this function returns the user that verified the
1741
        last analysis pending.
1742
        """
1743
        wtool = getToolByName(self, 'portal_workflow')
1744
        mtool = getToolByName(self, 'portal_membership')
1745
1746
        verifier = None
1747
        # noinspection PyBroadException
1748
        try:
1749
            review_history = wtool.getInfoFor(self, 'review_history')
1750
        except:  # noqa FIXME: remove blind except!
1751
            return 'access denied'
1752
1753
        if not review_history:
1754
            return 'no history'
1755
        for items in review_history:
1756
            action = items.get('action')
1757
            if action != 'verify':
1758
                continue
1759
            actor = items.get('actor')
1760
            member = mtool.getMemberById(actor)
1761
            verifier = member.getProperty('fullname')
1762
            if verifier is None or verifier == '':
1763
                verifier = actor
1764
        return verifier
1765
1766
    @security.public
1767
    def getVerifiersIDs(self):
1768
        """Returns the ids from users that have verified at least one analysis
1769
        from this Analysis Request
1770
        """
1771
        verifiers_ids = list()
1772
        for brain in self.getAnalyses():
1773
            verifiers_ids += brain.getVerificators
1774
        return list(set(verifiers_ids))
1775
1776
    @security.public
1777
    def getVerifiers(self):
1778
        """Returns the list of lab contacts that have verified at least one
1779
        analysis from this Analysis Request
1780
        """
1781
        contacts = list()
1782
        for verifier in self.getVerifiersIDs():
1783
            user = api.get_user(verifier)
1784
            contact = api.get_user_contact(user, ["LabContact"])
1785
            if contact:
1786
                contacts.append(contact)
1787
        return contacts
1788
1789
    security.declarePublic('getContactUIDForUser')
1790
1791
    def getContactUIDForUser(self):
1792
        """get the UID of the contact associated with the authenticated user
1793
        """
1794
        mt = getToolByName(self, 'portal_membership')
1795
        user = mt.getAuthenticatedMember()
1796
        user_id = user.getUserName()
1797
        pc = getToolByName(self, 'portal_catalog')
1798
        r = pc(portal_type='Contact',
1799
               getUsername=user_id)
1800
        if len(r) == 1:
1801
            return r[0].UID
1802
1803
    security.declarePublic('current_date')
1804
1805
    def current_date(self):
1806
        """return current date
1807
        """
1808
        # noinspection PyCallingNonCallable
1809
        return DateTime()
1810
1811
    def getQCAnalyses(self, qctype=None, review_state=None):
1812
        """return the QC analyses performed in the worksheet in which, at
1813
        least, one sample of this AR is present.
1814
1815
        Depending on qctype value, returns the analyses of:
1816
1817
            - 'b': all Blank Reference Samples used in related worksheet/s
1818
            - 'c': all Control Reference Samples used in related worksheet/s
1819
            - 'd': duplicates only for samples contained in this AR
1820
1821
        If qctype==None, returns all type of qc analyses mentioned above
1822
        """
1823
        qcanalyses = []
1824
        suids = []
1825
        ans = self.getAnalyses()
1826
        wf = getToolByName(self, 'portal_workflow')
1827
        for an in ans:
1828
            an = an.getObject()
1829
            if an.getServiceUID() not in suids:
1830
                suids.append(an.getServiceUID())
1831
1832
        def valid_dup(wan):
1833
            if wan.portal_type == 'ReferenceAnalysis':
1834
                return False
1835
            an_state = wf.getInfoFor(wan, 'review_state')
1836
            return \
1837
                wan.portal_type == 'DuplicateAnalysis' \
1838
                and wan.getRequestID() == self.id \
1839
                and (review_state is None or an_state in review_state)
1840
1841
        def valid_ref(wan):
1842
            if wan.portal_type != 'ReferenceAnalysis':
1843
                return False
1844
            an_state = wf.getInfoFor(wan, 'review_state')
1845
            an_reftype = wan.getReferenceType()
1846
            return wan.getServiceUID() in suids \
1847
                and wan not in qcanalyses \
1848
                and (qctype is None or an_reftype == qctype) \
1849
                and (review_state is None or an_state in review_state)
1850
1851
        for an in ans:
1852
            an = an.getObject()
1853
            ws = an.getWorksheet()
1854
            if not ws:
1855
                continue
1856
            was = ws.getAnalyses()
1857
            for wa in was:
1858
                if valid_dup(wa):
1859
                    qcanalyses.append(wa)
1860
                elif valid_ref(wa):
1861
                    qcanalyses.append(wa)
1862
1863
        return qcanalyses
1864
1865
    def isInvalid(self):
1866
        """return if the Analysis Request has been invalidated
1867
        """
1868
        workflow = getToolByName(self, 'portal_workflow')
1869
        return workflow.getInfoFor(self, 'review_state') == 'invalid'
1870
1871
    def getSamplingRoundUID(self):
1872
        """Obtains the sampling round UID
1873
        :returns: UID
1874
        """
1875
        sr = self.getSamplingRound()
1876
        if sr:
1877
            return sr.UID()
1878
        else:
1879
            return ''
1880
1881
    def getStorageLocationTitle(self):
1882
        """ A method for AR listing catalog metadata
1883
        :return: Title of Storage Location
1884
        """
1885
        sl = self.getStorageLocation()
1886
        if sl:
1887
            return sl.Title()
1888
        return ''
1889
1890
    @security.public
1891
    def getResultsRange(self):
1892
        """Returns the valid result ranges for the analyses this Analysis
1893
        Request contains.
1894
1895
        By default uses the result ranges defined in the Analysis Specification
1896
        set in "Specification" field if any. Values manually set through
1897
        `ResultsRange` field for any given analysis keyword have priority over
1898
        the result ranges defined in "Specification" field.
1899
1900
        :return: A list of dictionaries, where each dictionary defines the
1901
            result range to use for any analysis contained in this Analysis
1902
            Request for the keyword specified. Each dictionary has, at least,
1903
                the following keys: "keyword", "min", "max"
1904
        :rtype: dict
1905
        """
1906
        specs_range = []
1907
        specification = self.getSpecification()
1908
        if specification:
1909
            specs_range = specification.getResultsRange()
1910
            specs_range = specs_range and specs_range or []
1911
1912
        # Override with AR's custom ranges
1913
        ar_range = self.Schema().getField("ResultsRange").get(self)
1914
        if not ar_range:
1915
            return specs_range
1916
1917
        # Remove those analysis ranges that neither min nor max are floatable
1918
        an_specs = [an for an in ar_range if
1919
                    api.is_floatable(an.get('min', None)) or
1920
                    api.is_floatable(an.get('max', None))]
1921
        # Want to know which are the analyses that needs to be overriden
1922
        keywords = map(lambda item: item.get('keyword'), an_specs)
1923
        # Get rid of those analyses to be overriden
1924
        out_specs = [sp for sp in specs_range if sp['keyword'] not in keywords]
1925
        # Add manually set ranges
1926
        out_specs.extend(an_specs)
1927
        return map(lambda spec: ResultsRangeDict(spec), out_specs)
1928
1929
    def getDatePublished(self):
1930
        """
1931
        Returns the transition date from the Analysis Request object
1932
        """
1933
        return getTransitionDate(self, 'publish', return_as_datetime=True)
1934
1935
    security.declarePublic('getSamplingDeviationTitle')
1936
1937
    def getSamplingDeviationTitle(self):
1938
        """
1939
        It works as a metacolumn.
1940
        """
1941
        sd = self.getSamplingDeviation()
1942
        if sd:
1943
            return sd.Title()
1944
        return ''
1945
1946
    security.declarePublic('getHazardous')
1947
1948
    def getHazardous(self):
1949
        """
1950
        It works as a metacolumn.
1951
        """
1952
        sample_type = self.getSampleType()
1953
        if sample_type:
1954
            return sample_type.getHazardous()
1955
        return False
1956
1957
    security.declarePublic('getContactURL')
1958
1959
    def getContactURL(self):
1960
        """
1961
        It works as a metacolumn.
1962
        """
1963
        contact = self.getContact()
1964
        if contact:
1965
            return contact.absolute_url_path()
1966
        return ''
1967
1968
    security.declarePublic('getSamplingWorkflowEnabled')
1969
1970
    def getSamplingWorkflowEnabled(self):
1971
        """Returns True if the sample of this Analysis Request has to be
1972
        collected by the laboratory personnel
1973
        """
1974
        template = self.getTemplate()
1975
        if template:
1976
            return template.getSamplingRequired()
1977
        return self.bika_setup.getSamplingWorkflowEnabled()
1978
1979
    def getSamplers(self):
1980
        return getUsers(self, ['Sampler', ])
1981
1982
    def getPreservers(self):
1983
        return getUsers(self, ['Preserver', 'Sampler'])
1984
1985
    def getDepartments(self):
1986
        """Returns a list of the departments assigned to the Analyses
1987
        from this Analysis Request
1988
        """
1989
        departments = list()
1990
        for analysis in self.getAnalyses(full_objects=True):
1991
            department = analysis.getDepartment()
1992
            if department and department not in departments:
1993
                departments.append(department)
1994
        return departments
1995
1996
    def getResultsInterpretationByDepartment(self, department=None):
1997
        """Returns the results interpretation for this Analysis Request
1998
           and department. If department not set, returns the results
1999
           interpretation tagged as 'General'.
2000
2001
        :returns: a dict with the following keys:
2002
            {'uid': <department_uid> or 'general', 'richtext': <text/plain>}
2003
        """
2004
        uid = department.UID() if department else 'general'
2005
        rows = self.Schema()['ResultsInterpretationDepts'].get(self)
2006
        row = [row for row in rows if row.get('uid') == uid]
2007
        if len(row) > 0:
2008
            row = row[0]
2009
        elif uid == 'general' \
2010
                and hasattr(self, 'getResultsInterpretation') \
2011
                and self.getResultsInterpretation():
2012
            row = {'uid': uid, 'richtext': self.getResultsInterpretation()}
2013
        else:
2014
            row = {'uid': uid, 'richtext': ''}
2015
        return row
2016
2017
    def getAnalysisServiceSettings(self, uid):
2018
        """Returns a dictionary with the settings for the analysis service that
2019
        match with the uid provided.
2020
2021
        If there are no settings for the analysis service and
2022
        analysis requests:
2023
2024
        1. looks for settings in AR's ARTemplate. If found, returns the
2025
           settings for the AnalysisService set in the Template
2026
        2. If no settings found, looks in AR's ARProfile. If found, returns the
2027
           settings for the AnalysisService from the AR Profile. Otherwise,
2028
           returns a one entry dictionary with only the key 'uid'
2029
        """
2030
        sets = [s for s in self.getAnalysisServicesSettings()
2031
                if s.get('uid', '') == uid]
2032
2033
        # Created by using an ARTemplate?
2034
        if not sets and self.getTemplate():
2035
            adv = self.getTemplate().getAnalysisServiceSettings(uid)
2036
            sets = [adv] if 'hidden' in adv else []
2037
2038
        # Created by using an AR Profile?
2039
        if not sets and self.getProfiles():
2040
            adv = []
2041
            adv += [profile.getAnalysisServiceSettings(uid) for profile in
2042
                    self.getProfiles()]
2043
            sets = adv if 'hidden' in adv[0] else []
2044
2045
        return sets[0] if sets else {'uid': uid}
2046
2047
    # TODO Sample Cleanup - Remove this function
2048
    def getPartitions(self):
2049
        """This functions returns the partitions from the analysis request's
2050
        analyses.
2051
2052
        :returns: a list with the full partition objects
2053
        """
2054
        partitions = []
2055
        for analysis in self.getAnalyses(full_objects=True):
2056
            if analysis.getSamplePartition() not in partitions:
2057
                partitions.append(analysis.getSamplePartition())
2058
        return partitions
2059
2060
    # TODO Sample Cleanup - Remove (Use getContainer instead)
2061
    def getContainers(self):
2062
        """This functions returns the containers from the analysis request's
2063
        analyses
2064
2065
        :returns: a list with the full partition objects
2066
        """
2067
        return self.getContainer() and [self.getContainer] or []
2068
2069 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...
2070
        """Checks if the analysis service that match with the uid provided must
2071
        be hidden in results. If no hidden assignment has been set for the
2072
        analysis in this request, returns the visibility set to the analysis
2073
        itself.
2074
2075
        Raise a TypeError if the uid is empty or None
2076
2077
        Raise a ValueError if there is no hidden assignment in this request or
2078
        no analysis service found for this uid.
2079
        """
2080
        if not uid:
2081
            raise TypeError('None type or empty uid')
2082
        sets = self.getAnalysisServiceSettings(uid)
2083
        if 'hidden' not in sets:
2084
            uc = getToolByName(self, 'uid_catalog')
2085
            serv = uc(UID=uid)
2086
            if serv and len(serv) == 1:
2087
                return serv[0].getObject().getRawHidden()
2088
            else:
2089
                raise ValueError('{} is not valid'.format(uid))
2090
        return sets.get('hidden', False)
2091
2092
    def getRejecter(self):
2093
        """If the Analysis Request has been rejected, returns the user who did the
2094
        rejection. If it was not rejected or the current user has not enough
2095
        privileges to access to this information, returns None.
2096
        """
2097
        wtool = getToolByName(self, 'portal_workflow')
2098
        mtool = getToolByName(self, 'portal_membership')
2099
        # noinspection PyBroadException
2100
        try:
2101
            review_history = wtool.getInfoFor(self, 'review_history')
2102
        except:  # noqa FIXME: remove blind except!
2103
            return None
2104
        for items in review_history:
2105
            action = items.get('action')
2106
            if action != 'reject':
2107
                continue
2108
            actor = items.get('actor')
2109
            return mtool.getMemberById(actor)
2110
        return None
2111
2112
    def getReceivedBy(self):
2113
        """
2114
        Returns the User who received the analysis request.
2115
        :returns: the user id
2116
        """
2117
        user = getTransitionUsers(self, 'receive', last_user=True)
2118
        return user[0] if user else ''
2119
2120
    def getDateVerified(self):
2121
        """
2122
        Returns the date of verification as a DateTime object.
2123
        """
2124
        return getTransitionDate(self, 'verify', return_as_datetime=True)
2125
2126
    @security.public
2127
    def getPrioritySortkey(self):
2128
        """Returns the key that will be used to sort the current Analysis
2129
        Request based on both its priority and creation date. On ASC sorting,
2130
        the oldest item with highest priority will be displayed.
2131
        :return: string used for sorting
2132
        """
2133
        priority = self.getPriority()
2134
        created_date = self.created().ISO8601()
2135
        return '%s.%s' % (priority, created_date)
2136
2137
    @security.public
2138
    def setPriority(self, value):
2139
        if not value:
2140
            value = self.Schema().getField('Priority').getDefault(self)
2141
        original_value = self.Schema().getField('Priority').get(self)
2142
        if original_value != value:
2143
            self.Schema().getField('Priority').set(self, value)
2144
            self._reindexAnalyses(['getPrioritySortkey'], True)
2145
2146
    @security.private
2147
    def _reindexAnalyses(self, idxs=None, update_metadata=False):
2148
        if not idxs and not update_metadata:
2149
            return
2150
        if not idxs:
2151
            idxs = []
2152
        analyses = self.getAnalyses()
2153
        catalog = getToolByName(self, CATALOG_ANALYSIS_LISTING)
2154
        for analysis in analyses:
2155
            analysis_obj = analysis.getObject()
2156
            catalog.reindexObject(analysis_obj, idxs=idxs, update_metadata=1)
2157
2158
    def _getCreatorFullName(self):
2159
        """
2160
        Returns the full name of this analysis request's creator.
2161
        """
2162
        return user_fullname(self, self.Creator())
2163
2164
    def _getCreatorEmail(self):
2165
        """
2166
        Returns the email of this analysis request's creator.
2167
        """
2168
        return user_email(self, self.Creator())
2169
2170
    def _getSamplerFullName(self):
2171
        """
2172
        Returns the full name's defined sampler.
2173
        """
2174
        return user_fullname(self, self.getSampler())
2175
2176
    def _getSamplerEmail(self):
2177
        """
2178
        Returns the email of this analysis request's sampler.
2179
        """
2180
        return user_email(self, self.Creator())
2181
2182 View Code Duplication
    def getObjectWorkflowStates(self):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
2183
        """
2184
        This method is used as a metacolumn.
2185
        Returns a dictionary with the workflow id as key and workflow state as
2186
        value.
2187
        :returns: {'review_state':'active',...}
2188
        """
2189
        workflow = getToolByName(self, 'portal_workflow')
2190
        states = {}
2191
        for w in workflow.getWorkflowsFor(self):
2192
            state = w._getWorkflowStateOf(self).id
2193
            states[w.state_var] = state
2194
        return states
2195
2196
    def SearchableText(self):
2197
        """
2198
        Override searchable text logic based on the requirements.
2199
2200
        This method constructs a text blob which contains all full-text
2201
        searchable text for this content item.
2202
        https://docs.plone.org/develop/plone/searching_and_indexing/indexing.html#full-text-searching
2203
        """
2204
2205
        # Speed up string concatenation ops by using a buffer
2206
        entries = []
2207
2208
        # plain text fields we index from ourself,
2209
        # a list of accessor methods of the class
2210
        plain_text_fields = ("getId", )
2211
2212
        def read(acc):
2213
            """
2214
            Call a class accessor method to give a value for certain Archetypes
2215
            field.
2216
            """
2217
            try:
2218
                val = acc()
2219
            except Exception as e:
2220
                message = "Error getting the accessor parameter in " \
2221
                          "SearchableText from the Analysis Request Object " \
2222
                          "{}: {}".format(self.getId(), e.message)
2223
                logger.error(message)
2224
                val = ""
2225
2226
            if val is None:
2227
                val = ""
2228
2229
            return val
2230
2231
        # Concatenate plain text fields as they are
2232
        for f in plain_text_fields:
2233
            accessor = getattr(self, f)
2234
            value = read(accessor)
2235
            entries.append(value)
2236
2237
        # Adding HTML Fields to SearchableText can be uncommented if necessary
2238
        # transforms = getToolByName(self, 'portal_transforms')
2239
        #
2240
        # # Run HTML valued fields through text/plain conversion
2241
        # for f in html_fields:
2242
        #     accessor = getattr(self, f)
2243
        #     value = read(accessor)
2244
        #
2245
        #     if value != "":
2246
        #         stream = transforms.convertTo('text/plain', value,
2247
        #                                       mimetype='text/html')
2248
        #         value = stream.getData()
2249
        #
2250
        #     entries.append(value)
2251
2252
        # Plone accessor methods assume utf-8
2253
        def convertToUTF8(text):
2254
            if type(text) == unicode:
2255
                return text.encode("utf-8")
2256
            return text
2257
2258
        entries = [convertToUTF8(entry) for entry in entries]
2259
2260
        # Concatenate all strings to one text blob
2261
        return " ".join(entries)
2262
2263
    def getPriorityText(self):
2264
        """
2265
        This function looks up the priority text from priorities vocab
2266
        :returns: the priority text or ''
2267
        """
2268
        if self.getPriority():
2269
            return PRIORITIES.getValue(self.getPriority())
2270
        return ''
2271
2272
    def get_ARAttachment(self):
2273
        logger.warn("_ARAttachment is a virtual field used in AR Add. "
2274
                    "It can not hold an own value!")
2275
        return None
2276
2277
    def set_ARAttachment(self, value):
2278
        logger.warn("_ARAttachment is a virtual field used in AR Add. "
2279
                    "It can not hold an own value!")
2280
        return None
2281
2282
    def get_retest(self):
2283
        """Returns the Analysis Request automatically generated because of the
2284
        retraction of the current analysis request
2285
        """
2286
        relationship = "AnalysisRequestRetracted"
2287
        retest = self.getBackReferences(relationship=relationship)
2288
        if retest and len(retest) > 1:
2289
            logger.warn("More than one retest for {0}".format(self.getId()))
2290
        return retest and retest[0] or None
2291
2292
    def getAncestors(self, all_ancestors=True):
2293
        """Returns the ancestor(s) of this Analysis Request
2294
        param all_ancestors: include all ancestors, not only the parent
2295
        """
2296
        parent = self.getParentAnalysisRequest()
2297
        if not parent:
2298
            return list()
2299
        if not all_ancestors:
2300
            return [parent]
2301
        return [parent] + parent.getAncestors(all_ancestors=True)
2302
2303
    def isRootAncestor(self):
2304
        """Returns True if the AR is the root ancestor
2305
2306
        :returns: True if the AR has no more parents
2307
        """
2308
        parent = self.getParentAnalysisRequest()
2309
        if parent:
2310
            return False
2311
        return True
2312
2313
    def getDescendants(self, all_descendants=False):
2314
        """Returns the descendant Analysis Requests
2315
2316
        :param all_descendants: recursively include all descendants
2317
        """
2318
2319
        # N.B. full objects returned here from
2320
        #      `Products.Archetypes.Referenceable.getBRefs`
2321
        #      -> don't add this method into Metadata
2322
        children = self.getBackReferences(
2323
            "AnalysisRequestParentAnalysisRequest")
2324
2325
        descendants = []
2326
2327
        # recursively include all children
2328
        if all_descendants:
2329
            for child in children:
2330
                descendants.append(child)
2331
                descendants += child.getDescendants(all_descendants=True)
2332
        else:
2333
            descendants = children
2334
2335
        return descendants
2336
2337
    def getDescendantsUIDs(self, all_descendants=False):
2338
        """Returns the UIDs of the descendant Analysis Requests
2339
2340
        This method is used as metadata
2341
        """
2342
        descendants = self.getDescendants(all_descendants=all_descendants)
2343
        return map(api.get_uid, descendants)
2344
2345
    def isPartition(self):
2346
        """Returns true if this Analysis Request is a partition
2347
        """
2348
        return not self.isRootAncestor()
2349
2350
    # TODO Remove in favour of getSamplingWorkflowEnabled
2351
    def getSamplingRequired(self):
2352
        """Returns True if the sample of this Analysis Request has to be
2353
        collected by the laboratory personnel
2354
        """
2355
        return self.getSamplingWorkflowEnabled()
2356
2357
    def isOpen(self):
2358
        """Returns whether all analyses from this Analysis Request are open
2359
        (their status is either "assigned" or "unassigned")
2360
        """
2361
        for analysis in self.getAnalyses():
2362
            if not api.get_object(analysis).isOpen():
2363
                return False
2364
        return True
2365
2366
    def setParentAnalysisRequest(self, value):
2367
        """Sets a parent analysis request, making the current a partition
2368
        """
2369
        self.Schema().getField("ParentAnalysisRequest").set(self, value)
2370
        if not value:
2371
            noLongerProvides(self, IAnalysisRequestPartition)
2372
        else:
2373
            alsoProvides(self, IAnalysisRequestPartition)
2374
2375
    def getSecondaryAnalysisRequests(self):
2376
        """Returns the secondary analysis requests from this analysis request
2377
        """
2378
        relationship = "AnalysisRequestPrimaryAnalysisRequest"
2379
        return self.getBackReferences(relationship=relationship)
2380
2381
    def setDateReceived(self, value):
2382
        """Sets the date received to this analysis request and to secondary
2383
        analysis requests
2384
        """
2385
        self.Schema().getField('DateReceived').set(self, value)
2386
        for secondary in self.getSecondaryAnalysisRequests():
2387
            secondary.setDateReceived(value)
2388
            secondary.reindexObject(idxs=["getDateReceived", "is_received"])
2389
2390
    def setDateSampled(self, value):
2391
        """Sets the date sampled to this analysis request and to secondary
2392
        analysis requests
2393
        """
2394
        self.Schema().getField('DateSampled').set(self, value)
2395
        for secondary in self.getSecondaryAnalysisRequests():
2396
            secondary.setDateSampled(value)
2397
            secondary.reindexObject(idxs="getDateSampled")
2398
2399
    def setSamplingDate(self, value):
2400
        """Sets the sampling date to this analysis request and to secondary
2401
        analysis requests
2402
        """
2403
        self.Schema().getField('SamplingDate').set(self, value)
2404
        for secondary in self.getSecondaryAnalysisRequests():
2405
            secondary.setSamplingDate(value)
2406
            secondary.reindexObject(idxs="getSamplingDate")
2407
2408
2409
registerType(AnalysisRequest, PROJECTNAME)
2410