Passed
Push — master ( 354e15...69376c )
by Jordi
04:34
created

AnalysisRequest.getProvince()   A

Complexity

Conditions 1

Size

Total Lines 3
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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