Passed
Push — master ( caa188...9d0ad3 )
by Ramon
11:25 queued 07:12
created

AnalysisRequest.getLate()   A

Complexity

Conditions 3

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 5
dl 0
loc 7
rs 10
c 0
b 0
f 0
cc 3
nop 1
1
# -*- coding: utf-8 -*-
2
#
3
# This file is part of SENAITE.CORE
4
#
5
# Copyright 2018 by it's authors.
6
# Some rights reserved. See LICENSE.rst, CONTRIBUTORS.rst.
7
8
import sys
9
from decimal import Decimal
10
from operator import methodcaller
11
12
from AccessControl import ClassSecurityInfo
13
from bika.lims import api
14
from bika.lims import bikaMessageFactory as _
15
from bika.lims import deprecated
16
from bika.lims import logger
17
# Bika Fields
18
from bika.lims.browser.fields import ARAnalysesField
19
from bika.lims.browser.fields import DateTimeField
20
from bika.lims.browser.fields import ProxyField
21
from bika.lims.browser.fields import UIDReferenceField
22
# Bika Widgets
23
from bika.lims.browser.fields.remarksfield import RemarksField
24
from bika.lims.browser.widgets import DateTimeWidget
25
from bika.lims.browser.widgets import RemarksWidget
26
from bika.lims.browser.widgets import DecimalWidget
27
from bika.lims.browser.widgets import PrioritySelectionWidget
28
from bika.lims.browser.widgets import ReferenceWidget
29
from bika.lims.browser.widgets import RejectionWidget
30
from bika.lims.browser.widgets import SelectionWidget as BikaSelectionWidget
31
from bika.lims.catalog import CATALOG_ANALYSIS_LISTING
32
from bika.lims.config import PRIORITIES
33
from bika.lims.config import PROJECTNAME
34
from bika.lims.content.analysisspec import ResultsRangeDict
35
from bika.lims.content.bikaschema import BikaSchema
36
# Bika Interfaces
37
from bika.lims.interfaces import IAnalysisRequest
38
# Bika Permissions
39
from bika.lims.permissions import EditARContact
40
from bika.lims.permissions import ManageInvoices
41
from bika.lims.permissions import SampleSample
42
from bika.lims.permissions import ScheduleSampling
43
from bika.lims.permissions import Verify as VerifyPermission
44
# Bika Utils
45
from bika.lims.utils import getUsers
46
from bika.lims.utils import user_email
47
from bika.lims.utils import user_fullname
48
# Bika Workflow
49
from bika.lims.workflow import getReviewHistoryActionsList
50
from bika.lims.workflow import getTransitionDate
51
from bika.lims.workflow import getTransitionUsers
52
from bika.lims.workflow.analysisrequest import events
53
from bika.lims.workflow.analysisrequest import guards
54
from DateTime import DateTime
55
from plone import api as ploneapi
56
from Products.Archetypes.atapi import BaseFolder
57
from Products.Archetypes.atapi import BooleanField
58
from Products.Archetypes.atapi import BooleanWidget
59
from Products.Archetypes.atapi import ComputedField
60
from Products.Archetypes.atapi import ComputedWidget
61
from Products.Archetypes.atapi import DisplayList
62
from Products.Archetypes.atapi import FileField
63
from Products.Archetypes.atapi import FileWidget
64
from Products.Archetypes.atapi import FixedPointField
65
from Products.Archetypes.atapi import ReferenceField
66
from Products.Archetypes.atapi import StringField
67
from Products.Archetypes.atapi import StringWidget
68
from Products.Archetypes.atapi import TextAreaWidget
69
from Products.Archetypes.atapi import TextField
70
from Products.Archetypes.atapi import registerType
71
from Products.Archetypes.config import REFERENCE_CATALOG
72
from Products.Archetypes.public import Schema
73
from Products.Archetypes.references import HoldingReference
74
from Products.Archetypes.Widget import RichWidget
75
# AT Fields and AT Widgets
76
from Products.ATExtensions.field import RecordsField
77
from Products.CMFCore.permissions import ModifyPortalContent
78
from Products.CMFCore.permissions import View
79
from Products.CMFCore.utils import getToolByName
80
from Products.CMFPlone.utils import _createObjectByType
81
from Products.CMFPlone.utils import safe_unicode
82
from zope.interface import implements
83
84
85
# SCHEMA DEFINITION
86
schema = BikaSchema.copy() + Schema((
87
88
    UIDReferenceField(
89
        'Contact',
90
        required=1,
91
        default_method='getContactUIDForUser',
92
        allowed_types=('Contact',),
93
        mode="rw",
94
        read_permission=View,
95
        write_permission=EditARContact,
96
        widget=ReferenceWidget(
97
            label=_("Contact"),
98
            render_own_label=True,
99
            size=20,
100
            helper_js=("bika_widgets/referencewidget.js",
101
                       "++resource++bika.lims.js/contact.js"),
102
            description=_("The primary contact of this analysis request, "
103
                          "who will receive notifications and publications "
104
                          "via email"),
105
            visible={
106
                'edit': 'visible',
107
                'view': 'visible',
108
                'add': 'edit',
109
                'header_table': 'prominent',
110
                'sample_registered': {
111
                    'view': 'visible', 'edit': 'visible', 'add': 'edit'},
112
                'to_be_sampled': {'view': 'visible', 'edit': 'visible'},
113
                'scheduled_sampling': {'view': 'visible', 'edit': 'visible'},
114
                'sampled': {'view': 'visible', 'edit': 'visible'},
115
                'to_be_preserved': {'view': 'visible', 'edit': 'visible'},
116
                'sample_due': {'view': 'visible', 'edit': 'visible'},
117
                'sample_received': {'view': 'visible', 'edit': 'visible'},
118
                'attachment_due': {'view': 'visible', 'edit': 'visible'},
119
                'to_be_verified': {'view': 'visible', 'edit': 'visible'},
120
                'verified': {'view': 'visible', 'edit': 'invisible'},
121
                'published': {'view': 'visible', 'edit': 'invisible'},
122
                'invalid': {'view': 'visible', 'edit': 'invisible'},
123
                'rejected': {'view': 'visible', 'edit': 'invisible'},
124
            },
125
            base_query={'inactive_state': 'active'},
126
            showOn=True,
127
            popup_width='400px',
128
            colModel=[
129
                {'columnName': 'UID', 'hidden': True},
130
                {'columnName': 'Fullname', 'width': '50',
131
                 'label': _('Name')},
132
                {'columnName': 'EmailAddress', 'width': '50',
133
                 'label': _('Email Address')},
134
            ],
135
        ),
136
    ),
137
138
    ReferenceField(
139
        'CCContact',
140
        multiValued=1,
141
        vocabulary_display_path_bound=sys.maxsize,
142
        allowed_types=('Contact',),
143
        referenceClass=HoldingReference,
144
        relationship='AnalysisRequestCCContact',
145
        mode="rw",
146
        read_permission=View,
147
        write_permission=EditARContact,
148
        widget=ReferenceWidget(
149
            label=_("CC Contacts"),
150
            description=_("The contacts used in CC for email notifications"),
151
            render_own_label=True,
152
            size=20,
153
            visible={
154
                'edit': 'visible',
155
                'view': 'visible',
156
                'add': 'edit',
157
                'header_table': 'prominent',
158
                'sample_registered': {
159
                    'view': 'visible', 'edit': 'visible', 'add': 'edit'},
160
                'to_be_sampled': {'view': 'visible', 'edit': 'visible'},
161
                'scheduled_sampling': {'view': 'visible', 'edit': 'visible'},
162
                'sampled': {'view': 'visible', 'edit': 'visible'},
163
                'to_be_preserved': {'view': 'visible', 'edit': 'visible'},
164
                'sample_due': {'view': 'visible', 'edit': 'visible'},
165
                'sample_received': {'view': 'visible', 'edit': 'visible'},
166
                'attachment_due': {'view': 'visible', 'edit': 'visible'},
167
                'to_be_verified': {'view': 'visible', 'edit': 'visible'},
168
                'verified': {'view': 'visible', 'edit': 'invisible'},
169
                'published': {'view': 'visible', 'edit': 'invisible'},
170
                'invalid': {'view': 'visible', 'edit': 'invisible'},
171
                'rejected': {'view': 'visible', 'edit': 'invisible'},
172
            },
173
            base_query={'inactive_state': 'active'},
174
            showOn=True,
175
            popup_width='400px',
176
            colModel=[
177
                {'columnName': 'UID', 'hidden': True},
178
                {'columnName': 'Fullname', 'width': '50',
179
                 'label': _('Name')},
180
                {'columnName': 'EmailAddress', 'width': '50',
181
                 'label': _('Email Address')},
182
            ],
183
        ),
184
    ),
185
186
    StringField(
187
        'CCEmails',
188
        mode="rw",
189
        read_permission=View,
190
        write_permission=EditARContact,
191
        acquire=True,
192
        acquire_fieldname="CCEmails",
193
        widget=StringWidget(
194
            label=_("CC Emails"),
195
            description=_("Additional email addresses to be notified"),
196
            visible={
197
                'edit': 'visible',
198
                'view': 'visible',
199
                'add': 'edit',
200
                'header_table': 'prominent',
201
                'sample_registered': {
202
                    'view': 'visible', 'edit': 'visible', 'add': 'edit'},
203
                'to_be_sampled': {'view': 'visible', 'edit': 'visible'},
204
                'scheduled_sampling': {'view': 'visible', 'edit': 'visible'},
205
                'sampled': {'view': 'visible', 'edit': 'visible'},
206
                'to_be_preserved': {'view': 'visible', 'edit': 'visible'},
207
                'sample_received': {'view': 'visible', 'edit': 'visible'},
208
                'attachment_due': {'view': 'visible', 'edit': 'visible'},
209
                'to_be_verified': {'view': 'visible', 'edit': 'visible'},
210
                'verified': {'view': 'visible', 'edit': 'invisible'},
211
                'published': {'view': 'visible', 'edit': 'invisible'},
212
                'invalid': {'view': 'visible', 'edit': 'invisible'},
213
                'rejected': {'view': 'visible', 'edit': 'invisible'},
214
            },
215
            render_own_label=True,
216
            size=20,
217
        ),
218
    ),
219
220
    ReferenceField(
221
        'Client',
222
        required=1,
223
        allowed_types=('Client',),
224
        relationship='AnalysisRequestClient',
225
        mode="rw",
226
        read_permission=View,
227
        write_permission=ModifyPortalContent,
228
        widget=ReferenceWidget(
229
            label=_("Client"),
230
            description=_("The assigned client of this request"),
231
            size=20,
232
            render_own_label=True,
233
            visible={
234
                'edit': 'visible',
235
                'view': 'visible',
236
                'add': 'edit',
237
                'header_table': 'visible',
238
                'sample_registered': {
239
                    'view': 'invisible', 'edit': 'visible', 'add': 'edit'},
240
                'to_be_sampled': {'view': 'invisible', 'edit': 'invisible'},
241
                'scheduled_sampling': {
242
                    'view': 'invisible', 'edit': 'invisible'},
243
                'sampled': {'view': 'invisible', 'edit': 'invisible'},
244
                'to_be_preserved': {'view': 'invisible', 'edit': 'invisible'},
245
                'sample_received': {'view': 'invisible', 'edit': 'invisible'},
246
                'attachment_due': {'view': 'invisible', 'edit': 'invisible'},
247
                'to_be_verified': {'view': 'invisible', 'edit': 'invisible'},
248
                'verified': {'view': 'invisible', 'edit': 'invisible'},
249
                'published': {'view': 'invisible', 'edit': 'invisible'},
250
                'invalid': {'view': 'invisible', 'edit': 'invisible'},
251
                'rejected': {'view': 'invisible', 'edit': 'invisible'},
252
            },
253
            base_query={'review_state': 'active'},
254
            showOn=True,
255
            add_button={
256
                    'visible': True,
257
                    'url': 'clients/createObject?type_name=Client',
258
                    'return_fields': ['Title'],
259
                    'js_controllers': ['#client-base-edit'],
260
                    'overlay_handler': 'ClientOverlayHandler',
261
                }
262
        ),
263
    ),
264
265
    UIDReferenceField(
266
        'Sample',
267
        allowed_types=('Sample',),
268
        mode="rw",
269
        read_permission=View,
270
        write_permission=ModifyPortalContent,
271
        widget=ReferenceWidget(
272
            label=_("Sample"),
273
            description=_("Select a sample to create a secondary AR"),
274
            size=20,
275
            render_own_label=True,
276
            visible={
277
                'edit': 'visible',
278
                'view': 'visible',
279
                'add': 'edit',
280
                'header_table': 'visible',
281
                'sample_registered': {
282
                    'view': 'visible', 'edit': 'visible', 'add': 'edit'},
283
                'to_be_sampled': {'view': 'visible', 'edit': 'invisible'},
284
                'scheduled_sampling': {'view': 'visible', 'edit': 'invisible'},
285
                'sampled': {'view': 'visible', 'edit': 'invisible'},
286
                'to_be_preserved': {'view': 'visible', 'edit': 'invisible'},
287
                'sample_due': {'view': 'visible', 'edit': 'invisible'},
288
                'sample_received': {'view': 'visible', 'edit': 'invisible'},
289
                'attachment_due': {'view': 'visible', 'edit': 'invisible'},
290
                'to_be_verified': {'view': 'visible', 'edit': 'invisible'},
291
                'verified': {'view': 'visible', 'edit': 'invisible'},
292
                'published': {'view': 'visible', 'edit': 'invisible'},
293
                'invalid': {'view': 'visible', 'edit': 'invisible'},
294
                'rejected': {'view': 'visible', 'edit': 'invisible'},
295
            },
296
            catalog_name='bika_catalog',
297
            base_query={'cancellation_state': 'active',
298
                        'review_state': ['sample_due', 'sample_received', ]},
299
            showOn=True,
300
        ),
301
    ),
302
303
    ReferenceField(
304
        'Batch',
305
        allowed_types=('Batch',),
306
        relationship='AnalysisRequestBatch',
307
        mode="rw",
308
        read_permission=View,
309
        write_permission=ModifyPortalContent,
310
        widget=ReferenceWidget(
311
            label=_("Batch"),
312
            size=20,
313
            description=_("The assigned batch of this request"),
314
            render_own_label=True,
315
            visible={
316
                'edit': 'visible',
317
                'view': 'visible',
318
                'add': 'edit',
319
                'header_table': 'visible',
320
                'sample_registered': {
321
                    'view': 'visible', 'edit': 'visible', 'add': 'edit'},
322
                'to_be_sampled': {'view': 'visible', 'edit': 'visible'},
323
                'scheduled_sampling': {'view': 'visible', 'edit': 'visible'},
324
                'sampled': {'view': 'visible', 'edit': 'visible'},
325
                'to_be_preserved': {'view': 'visible', 'edit': 'visible'},
326
                'sample_due': {'view': 'visible', 'edit': 'visible'},
327
                'sample_received': {'view': 'visible', 'edit': 'visible'},
328
                'attachment_due': {'view': 'visible', 'edit': 'visible'},
329
                'to_be_verified': {'view': 'visible', 'edit': 'visible'},
330
                'verified': {'view': 'visible', 'edit': 'visible'},
331
                'published': {'view': 'visible', 'edit': 'invisible'},
332
                'invalid': {'view': 'visible', 'edit': 'invisible'},
333
                'rejected': {'view': 'visible', 'edit': 'invisible'},
334
            },
335
            catalog_name='bika_catalog',
336
            base_query={'review_state': 'open',
337
                        'cancellation_state': 'active'},
338
            showOn=True,
339
        ),
340
    ),
341
342
    ReferenceField(
343
        'SamplingRound',
344
        allowed_types=('SamplingRound',),
345
        relationship='AnalysisRequestSamplingRound',
346
        mode="rw",
347
        read_permission=View,
348
        write_permission=ModifyPortalContent,
349
        widget=ReferenceWidget(
350
            label=_("Sampling Round"),
351
            description=_("The assigned sampling round of this request"),
352
            size=20,
353
            render_own_label=True,
354
            visible={
355
                'edit': 'visible',
356
                'view': 'visible',
357
                'add': 'edit',
358
                'header_table': 'visible',
359
                'sample_registered': {
360
                    'view': 'visible', 'edit': 'visible', 'add': 'edit'},
361
                'to_be_sampled': {'view': 'visible', 'edit': 'visible'},
362
                'scheduled_sampling': {'view': 'visible', 'edit': 'visible'},
363
                'sampled': {'view': 'visible', 'edit': 'visible'},
364
                'to_be_preserved': {'view': 'visible', 'edit': 'visible'},
365
                'sample_due': {'view': 'visible', 'edit': 'visible'},
366
                'sample_received': {'view': 'visible', 'edit': 'visible'},
367
                'attachment_due': {'view': 'visible', 'edit': 'visible'},
368
                'to_be_verified': {'view': 'visible', 'edit': 'visible'},
369
                'verified': {'view': 'visible', 'edit': 'visible'},
370
                'published': {'view': 'visible', 'edit': 'invisible'},
371
                'invalid': {'view': 'visible', 'edit': 'invisible'},
372
                'rejected': {'view': 'visible', 'edit': 'invisible'},
373
            },
374
            catalog_name='portal_catalog',
375
            base_query={},
376
            showOn=True,
377
        ),
378
    ),
379
380
    ReferenceField(
381
        'SubGroup',
382
        required=False,
383
        allowed_types=('SubGroup',),
384
        referenceClass=HoldingReference,
385
        relationship='AnalysisRequestSubGroup',
386
        widget=ReferenceWidget(
387
            label=_("Batch Sub-group"),
388
            description=_("The assigned batch sub group of this request"),
389
            size=20,
390
            render_own_label=True,
391
            visible={
392
                'edit': 'visible',
393
                'view': 'visible',
394
                'add': 'edit',
395
                'header_table': 'visible',
396
                'sample_registered': {
397
                    'view': 'visible', 'edit': 'visible', 'add': 'edit'},
398
                'to_be_sampled': {'view': 'visible', 'edit': 'visible'},
399
                'scheduled_sampling': {'view': 'visible', 'edit': 'visible'},
400
                'sampled': {'view': 'visible', 'edit': 'visible'},
401
                'to_be_preserved': {'view': 'visible', 'edit': 'visible'},
402
                'sample_due': {'view': 'visible', 'edit': 'visible'},
403
                'sample_received': {'view': 'visible', 'edit': 'visible'},
404
                'attachment_due': {'view': 'visible', 'edit': 'visible'},
405
                'to_be_verified': {'view': 'visible', 'edit': 'visible'},
406
                'verified': {'view': 'visible', 'edit': 'visible'},
407
                'published': {'view': 'visible', 'edit': 'invisible'},
408
                'invalid': {'view': 'visible', 'edit': 'invisible'},
409
                'rejected': {'view': 'visible', 'edit': 'invisible'},
410
            },
411
            catalog_name='bika_setup_catalog',
412
            colModel=[
413
                {'columnName': 'Title', 'width': '30',
414
                 'label': _('Title'), 'align': 'left'},
415
                {'columnName': 'Description', 'width': '70',
416
                 'label': _('Description'), 'align': 'left'},
417
                {'columnName': 'SortKey', 'hidden': True},
418
                {'columnName': 'UID', 'hidden': True},
419
            ],
420
            base_query={'inactive_state': 'active'},
421
            sidx='SortKey',
422
            sord='asc',
423
            showOn=True,
424
        ),
425
    ),
426
427
    ReferenceField(
428
        'Template',
429
        allowed_types=('ARTemplate',),
430
        referenceClass=HoldingReference,
431
        relationship='AnalysisRequestARTemplate',
432
        mode="rw",
433
        read_permission=View,
434
        write_permission=ModifyPortalContent,
435
        widget=ReferenceWidget(
436
            label=_("AR Template"),
437
            description=_("The predefined values of the AR template are set "
438
                          "in the request"),
439
            size=20,
440
            render_own_label=True,
441
            visible={
442
                'edit': 'visible',
443
                'view': 'visible',
444
                'add': 'edit',
445
                'secondary': 'disabled',
446
                'header_table': 'visible',
447
                'sample_registered': {
448
                    'view': 'visible', 'edit': 'visible', 'add': 'edit'},
449
                'to_be_sampled': {'view': 'visible', 'edit': 'invisible'},
450
                'scheduled_sampling': {'view': 'visible', 'edit': 'invisible'},
451
                'sampled': {'view': 'visible', 'edit': 'invisible'},
452
                'to_be_preserved': {'view': 'visible', 'edit': 'invisible'},
453
                'sample_due': {'view': 'visible', 'edit': 'invisible'},
454
                'sample_received': {'view': 'visible', 'edit': 'invisible'},
455
                'attachment_due': {'view': 'visible', 'edit': 'invisible'},
456
                'to_be_verified': {'view': 'visible', 'edit': 'invisible'},
457
                'verified': {'view': 'visible', 'edit': 'invisible'},
458
                'published': {'view': 'visible', 'edit': 'invisible'},
459
                'invalid': {'view': 'visible', 'edit': 'invisible'},
460
                'rejected': {'view': 'visible', 'edit': 'invisible'},
461
            },
462
            catalog_name='bika_setup_catalog',
463
            base_query={'inactive_state': 'active'},
464
            showOn=True,
465
        ),
466
    ),
467
468
    # TODO: Profile'll be delated
469
    ReferenceField(
470
        'Profile',
471
        allowed_types=('AnalysisProfile',),
472
        referenceClass=HoldingReference,
473
        relationship='AnalysisRequestAnalysisProfile',
474
        mode="rw",
475
        read_permission=View,
476
        write_permission=ModifyPortalContent,
477
        widget=ReferenceWidget(
478
            label=_("Analysis Profile"),
479
            description=_("Analysis profiles apply a certain set of analyses"),
480
            size=20,
481
            render_own_label=True,
482
            visible=False,
483
            catalog_name='bika_setup_catalog',
484
            base_query={'inactive_state': 'active'},
485
            showOn=False,
486
        ),
487
    ),
488
489
    ReferenceField(
490
        'Profiles',
491
        multiValued=1,
492
        allowed_types=('AnalysisProfile',),
493
        referenceClass=HoldingReference,
494
        vocabulary_display_path_bound=sys.maxsize,
495
        relationship='AnalysisRequestAnalysisProfiles',
496
        mode="rw",
497
        read_permission=View,
498
        write_permission=ModifyPortalContent,
499
        widget=ReferenceWidget(
500
            label=_("Analysis Profiles"),
501
            description=_("Analysis profiles apply a certain set of analyses"),
502
            size=20,
503
            render_own_label=True,
504
            visible={
505
                'edit': 'visible',
506
                'view': 'visible',
507
                'add': 'edit',
508
                'header_table': 'visible',
509
                'sample_registered': {
510
                    'view': 'visible', 'edit': 'visible', 'add': 'edit'},
511
                'to_be_sampled': {'view': 'visible', 'edit': 'invisible'},
512
                'scheduled_sampling': {'view': 'visible', 'edit': 'invisible'},
513
                'sampled': {'view': 'visible', 'edit': 'invisible'},
514
                'to_be_preserved': {'view': 'visible', 'edit': 'invisible'},
515
                'sample_due': {'view': 'visible', 'edit': 'invisible'},
516
                'sample_received': {'view': 'visible', 'edit': 'invisible'},
517
                'attachment_due': {'view': 'visible', 'edit': 'invisible'},
518
                'to_be_verified': {'view': 'visible', 'edit': 'invisible'},
519
                'verified': {'view': 'visible', 'edit': 'invisible'},
520
                'published': {'view': 'visible', 'edit': 'invisible'},
521
                'invalid': {'view': 'visible', 'edit': 'invisible'},
522
                'rejected': {'view': 'visible', 'edit': 'invisible'},
523
            },
524
            catalog_name='bika_setup_catalog',
525
            base_query={'inactive_state': 'active'},
526
            showOn=True,
527
        ),
528
    ),
529
    # This field is a mirror of a field in Sample with the same name
530
    ProxyField(
531
        'DateSampled',
532
        proxy="context.getSample()",
533
        mode="rw",
534
        read_permission=View,
535
        write_permission=ModifyPortalContent,
536
        widget=DateTimeWidget(
537
            label=_("Date Sampled"),
538
            description=_("The date when the sample was taken"),
539
            size=20,
540
            show_time=True,
541
            datepicker_nofuture=1,
542
            visible={
543
                'edit': 'visible',
544
                'view': 'visible',
545
                'add': 'edit',
546
                'secondary': 'disabled',
547
                'header_table': 'prominent',
548
                'sample_registered': {
549
                    'view': 'invisible', 'edit': 'invisible'},
550
                'to_be_sampled': {'view': 'invisible', 'edit': 'visible'},
551
                'scheduled_sampling': {'view': 'invisible', 'edit': 'visible'},
552
                'sampled': {'view': 'visible', 'edit': 'invisible'},
553
                'to_be_preserved': {'view': 'visible', 'edit': 'invisible'},
554
                'sample_due': {'view': 'visible', 'edit': 'invisible'},
555
                'sample_received': {'view': 'visible', 'edit': 'invisible'},
556
                'attachment_due': {'view': 'visible', 'edit': 'invisible'},
557
                'to_be_verified': {'view': 'visible', 'edit': 'invisible'},
558
                'verified': {'view': 'visible', 'edit': 'invisible'},
559
                'published': {'view': 'visible', 'edit': 'invisible'},
560
                'invalid': {'view': 'visible', 'edit': 'invisible'},
561
                'rejected': {'view': 'visible', 'edit': 'invisible'},
562
            },
563
            render_own_label=True,
564
        ),
565
    ),
566
    # This field is a mirror of a field in Sample with the same name
567
    ProxyField(
568
        'Sampler',
569
        proxy="context.getSample()",
570
        mode="rw",
571
        read_permission=View,
572
        write_permission=SampleSample,
573
        vocabulary='getSamplers',
574
        widget=BikaSelectionWidget(
575
            format='select',
576
            label=_("Sampler"),
577
            description=_("The person who took the sample"),
578
            # see SamplingWOrkflowWidgetVisibility
579
            visible={
580
                'edit': 'visible',
581
                'view': 'visible',
582
                'add': 'edit',
583
                'header_table': 'prominent',
584
                'sample_registered': {
585
                    'view': 'invisible', 'edit': 'invisible'},
586
                'to_be_sampled': {'view': 'invisible', 'edit': 'visible'},
587
                'scheduled_sampling': {'view': 'invisible', 'edit': 'visible'},
588
                'sampled': {'view': 'visible', 'edit': 'invisible'},
589
                'to_be_preserved': {'view': 'visible', 'edit': 'invisible'},
590
                'sample_due': {'view': 'visible', 'edit': 'invisible'},
591
                'sample_received': {'view': 'visible', 'edit': 'invisible'},
592
                'attachment_due': {'view': 'visible', 'edit': 'invisible'},
593
                'to_be_verified': {'view': 'visible', 'edit': 'invisible'},
594
                'verified': {'view': 'visible', 'edit': 'invisible'},
595
                'published': {'view': 'visible', 'edit': 'invisible'},
596
                'invalid': {'view': 'visible', 'edit': 'invisible'},
597
                'rejected': {'view': 'visible', 'edit': 'invisible'},
598
            },
599
            render_own_label=True,
600
        ),
601
    ),
602
603
    # Sample field
604
    ProxyField(
605
        'ScheduledSamplingSampler',
606
        proxy="context.getSample()",
607
        mode="rw",
608
        read_permission=View,
609
        write_permission=ScheduleSampling,
610
        vocabulary='getSamplers',
611
        widget=BikaSelectionWidget(
612
            description=_("Define the sampler supposed to do the sample in "
613
                          "the scheduled date"),
614
            format='select',
615
            label=_("Sampler for scheduled sampling"),
616
            visible={
617
                'edit': 'visible',
618
                'view': 'visible',
619
                'header_table': 'visible',
620
                'sample_registered': {
621
                    'view': 'invisible', 'edit': 'invisible'},
622
                'to_be_sampled': {'view': 'visible', 'edit': 'visible'},
623
                'scheduled_sampling': {'view': 'visible', 'edit': 'visible'},
624
                'sampled': {'view': 'invisible', 'edit': 'invisible'},
625
                'to_be_preserved': {'view': 'invisible', 'edit': 'invisible'},
626
                'sample_due': {'view': 'invisible', 'edit': 'invisible'},
627
                'sample_received': {'view': 'invisible', 'edit': 'invisible'},
628
                'expired': {'view': 'invisible', 'edit': 'invisible'},
629
                'disposed': {'view': 'invisible', 'edit': 'invisible'},
630
            },
631
            render_own_label=True,
632
        ),
633
    ),  # This field is a mirror of a Sample field with the same name
634
635
    # Sample field
636
    ProxyField(
637
        'SamplingDate',
638
        required=0,
639
        proxy="context.getSample()",
640
        mode="rw",
641
        read_permission=View,
642
        write_permission=ModifyPortalContent,
643
        widget=DateTimeWidget(
644
            label=_("Expected Sampling Date"),
645
            description=_("The date when the sample will be taken"),
646
            size=20,
647
            show_time=True,
648
            render_own_label=True,
649
            datepicker_nopast=1,
650
            # We must use SamplingWOrkflowWidgetVisibility soon.
651
            # For now we will handle it through JS
652
            # see SamplingWOrkflowWidgetVisibility
653
            visible={
654
                'edit': 'visible',
655
                'view': 'visible',
656
                'add': 'edit',
657
                'header_table': 'visible',
658
                'secondary': 'disabled',
659
                'sample_registered': {
660
                    'view': 'visible', 'edit': 'visible', 'add': 'edit'},
661
                'to_be_sampled': {'view': 'visible', 'edit': 'visible'},
662
                'scheduled_sampling': {'view': 'visible', 'edit': 'visible'},
663
                'sampled': {'view': 'visible', 'edit': 'invisible'},
664
                'to_be_preserved': {'view': 'visible', 'edit': 'invisible'},
665
                'sample_due': {'view': 'visible', 'edit': 'invisible'},
666
                'sample_received': {'view': 'visible', 'edit': 'invisible'},
667
                'attachment_due': {'view': 'visible', 'edit': 'invisible'},
668
                'to_be_verified': {'view': 'visible', 'edit': 'invisible'},
669
                'verified': {'view': 'visible', 'edit': 'invisible'},
670
                'published': {'view': 'visible', 'edit': 'invisible'},
671
                'invalid': {'view': 'visible', 'edit': 'invisible'},
672
                'rejected': {'view': 'visible', 'edit': 'invisible'},
673
            },
674
        ),
675
    ),
676
677
    # Sample field
678
    ProxyField(
679
        'SampleType',
680
        proxy="context.getSample()",
681
        required=1,
682
        allowed_types='SampleType',
683
        relationship='AnalysisRequestSampleType',
684
        mode="rw",
685
        read_permission=View,
686
        write_permission=ModifyPortalContent,
687
        widget=ReferenceWidget(
688
            label=_("Sample Type"),
689
            description=_("Create a new sample of this type"),
690
            size=20,
691
            render_own_label=True,
692
            visible={
693
                'edit': 'visible',
694
                'view': 'visible',
695
                'add': 'edit',
696
                'secondary': 'disabled',
697
                'header_table': 'visible',
698
                'sample_registered': {'view': 'visible', 'edit': 'visible', 'add': 'edit'},
699
                'to_be_sampled': {'view': 'visible', 'edit': 'visible'},
700
                'scheduled_sampling': {'view': 'invisible', 'edit': 'visible'},
701
                'sampled': {'view': 'visible', 'edit': 'visible'},
702
                'to_be_preserved': {'view': 'visible', 'edit': 'visible'},
703
                'sample_due': {'view': 'visible', 'edit': 'visible'},
704
                'sample_received': {'view': 'visible', 'edit': 'invisible'},
705
                'attachment_due': {'view': 'visible', 'edit': 'invisible'},
706
                'to_be_verified': {'view': 'visible', 'edit': 'invisible'},
707
                'verified': {'view': 'visible', 'edit': 'invisible'},
708
                'published': {'view': 'visible', 'edit': 'invisible'},
709
                'invalid': {'view': 'visible', 'edit': 'invisible'},
710
                'rejected': {'view': 'visible', 'edit': 'invisible'},
711
            },
712
            catalog_name='bika_setup_catalog',
713
            base_query={'inactive_state': 'active'},
714
            showOn=True,
715
        ),
716
    ),
717
718
    RecordsField(
719
        'RejectionReasons',
720
        widget=RejectionWidget(
721
            label=_("Sample Rejection"),
722
            description=_("Set the Sample Rejection workflow and the reasons"),
723
            render_own_label=False,
724
            visible={
725
                'edit': 'visible',
726
                'view': 'visible',
727
                'add': 'edit',
728
                'secondary': 'disabled',
729
                'header_table': 'visible',
730
                'sample_registered': {
731
                    'view': 'visible', 'edit': 'visible', 'add': 'edit'},
732
                'to_be_sampled': {'view': 'visible', 'edit': 'visible'},
733
                'sampled': {'view': 'visible', 'edit': 'visible'},
734
                'to_be_preserved': {'view': 'visible', 'edit': 'visible'},
735
                'sample_due': {'view': 'visible', 'edit': 'visible'},
736
                'sample_received': {'view': 'visible', 'edit': 'visible'},
737
                'attachment_due': {'view': 'visible', 'edit': 'visible'},
738
                'to_be_verified': {'view': 'visible', 'edit': 'visible'},
739
                'verified': {'view': 'visible', 'edit': 'visible'},
740
                'published': {'view': 'visible', 'edit': 'visible'},
741
                'invalid': {'view': 'visible', 'edit': 'visible'},
742
                'rejected': {'view': 'visible', 'edit': 'invisible'},
743
            },
744
        ),
745
    ),
746
747
    ReferenceField(
748
        'Specification',
749
        required=0,
750
        allowed_types='AnalysisSpec',
751
        relationship='AnalysisRequestAnalysisSpec',
752
        mode="rw",
753
        read_permission=View,
754
        write_permission=ModifyPortalContent,
755
        widget=ReferenceWidget(
756
            label=_("Analysis Specification"),
757
            description=_("Choose default AR specification values"),
758
            size=20,
759
            render_own_label=True,
760
            visible={
761
                'edit': 'visible',
762
                'view': 'visible',
763
                'add': 'edit',
764
                'header_table': 'visible',
765
                'sample_registered': {
766
                    'view': 'visible', 'edit': 'visible', 'add': 'edit'},
767
                'to_be_sampled': {'view': 'visible', 'edit': 'visible'},
768
                'scheduled_sampling': {'view': 'visible', 'edit': 'visible'},
769
                'sampled': {'view': 'visible', 'edit': 'visible'},
770
                'to_be_preserved': {'view': 'visible', 'edit': 'visible'},
771
                'sample_due': {'view': 'visible', 'edit': 'visible'},
772
                'sample_received': {'view': 'visible', 'edit': 'visible'},
773
                'attachment_due': {'view': 'visible', 'edit': 'visible'},
774
                'to_be_verified': {'view': 'visible', 'edit': 'invisible'},
775
                'verified': {'view': 'visible', 'edit': 'invisible'},
776
                'published': {'view': 'visible', 'edit': 'invisible'},
777
                'invalid': {'view': 'visible', 'edit': 'invisible'},
778
                'rejected': {'view': 'visible', 'edit': 'invisible'},
779
            },
780
            catalog_name='bika_setup_catalog',
781
            colModel=[
782
                {'columnName': 'contextual_title',
783
                 'width': '30',
784
                 'label': _('Title'),
785
                 'align': 'left'},
786
                {'columnName': 'SampleTypeTitle',
787
                 'width': '70',
788
                 'label': _('SampleType'),
789
                 'align': 'left'},
790
                # UID is required in colModel
791
                {'columnName': 'UID', 'hidden': True},
792
            ],
793
            showOn=True,
794
        ),
795
    ),
796
797
    # see setResultsRange below.
798
    RecordsField(
799
        'ResultsRange',
800
        required=0,
801
        type='resultsrange',
802
        subfields=('keyword', 'min', 'max', 'warn_min', 'warn_max', 'hidemin',
803
                   'hidemax', 'rangecomment', 'min_operator', 'max_operator'),
804
        widget=ComputedWidget(visible=False),
805
    ),
806
807
    ReferenceField(
808
        'PublicationSpecification',
809
        required=0,
810
        allowed_types='AnalysisSpec',
811
        relationship='AnalysisRequestPublicationSpec',
812
        mode="rw",
813
        read_permission=View,
814
        write_permission=View,
815
        widget=ReferenceWidget(
816
            label=_("Publication Specification"),
817
            description=_(
818
                "Set the specification to be used before publishing an AR."),
819
            size=20,
820
            render_own_label=True,
821
            visible={
822
                'edit': 'visible',
823
                'view': 'visible',
824
                'header_table': 'visible',
825
                'sample_registered': {
826
                    'view': 'invisible', 'edit': 'invisible'},
827
                'to_be_sampled': {'view': 'invisible', 'edit': 'invisible'},
828
                'scheduled_sampling': {
829
                    'view': 'invisible', 'edit': 'invisible'},
830
                'sampled': {'view': 'invisible', 'edit': 'invisible'},
831
                'to_be_preserved': {'view': 'invisible', 'edit': 'invisible'},
832
                'sample_due': {'view': 'invisible', 'edit': 'invisible'},
833
                'sample_received': {'view': 'invisible', 'edit': 'invisible'},
834
                'attachment_due': {'view': 'invisible', 'edit': 'invisible'},
835
                'to_be_verified': {'view': 'invisible', 'edit': 'invisible'},
836
                'verified': {'view': 'visible', 'edit': 'visible'},
837
                'published': {'view': 'visible', 'edit': 'visible'},
838
                'invalid': {'view': 'visible', 'edit': 'invisible'},
839
                'rejected': {'view': 'visible', 'edit': 'invisible'},
840
            },
841
            catalog_name='bika_setup_catalog',
842
            base_query={'inactive_state': 'active'},
843
            showOn=True,
844
        ),
845
    ),
846
847
    # Sample field
848
    ProxyField(
849
        'SamplePoint',
850
        proxy="context.getSample()",
851
        allowed_types='SamplePoint',
852
        relationship='AnalysisRequestSamplePoint',
853
        mode="rw",
854
        read_permission=View,
855
        write_permission=ModifyPortalContent,
856
        widget=ReferenceWidget(
857
            label=_("Sample Point"),
858
            description=_("Location where sample was taken"),
859
            size=20,
860
            render_own_label=True,
861
            visible={
862
                'edit': 'visible',
863
                'view': 'visible',
864
                'add': 'edit',
865
                'secondary': 'disabled',
866
                'header_table': 'visible',
867
                'sample_registered': {
868
                    'view': 'visible', 'edit': 'visible', 'add': 'edit'},
869
                'to_be_sampled': {'view': 'visible', 'edit': 'visible'},
870
                # LIMS-1159
871
                'scheduled_sampling': {'view': 'visible', 'edit': 'visible'},
872
                'sampled': {'view': 'visible', 'edit': 'visible'},
873
                'to_be_preserved': {'view': 'visible', 'edit': 'visible'},
874
                'sample_due': {'view': 'visible', 'edit': 'visible'},
875
                'sample_received': {'view': 'visible', 'edit': 'visible'},
876
                'attachment_due': {'view': 'visible', 'edit': 'visible'},
877
                'to_be_verified': {'view': 'visible', 'edit': 'visible'},
878
                'verified': {'view': 'visible', 'edit': 'invisible'},
879
                'published': {'view': 'visible', 'edit': 'invisible'},
880
                'invalid': {'view': 'visible', 'edit': 'invisible'},
881
                'rejected': {'view': 'visible', 'edit': 'invisible'},
882
            },
883
            catalog_name='bika_setup_catalog',
884
            base_query={'inactive_state': 'active'},
885
            showOn=True,
886
        ),
887
    ),
888
889
    # Sample field
890
    ProxyField(
891
        'StorageLocation',
892
        proxy="context.getSample()",
893
        allowed_types='StorageLocation',
894
        relationship='AnalysisRequestStorageLocation',
895
        mode="rw",
896
        read_permission=View,
897
        write_permission=ModifyPortalContent,
898
        widget=ReferenceWidget(
899
            label=_("Storage Location"),
900
            description=_("Location where sample is kept"),
901
            size=20,
902
            render_own_label=True,
903
            visible={
904
                'edit': 'visible',
905
                'view': 'visible',
906
                'add': 'edit',
907
                'secondary': 'disabled',
908
                'header_table': 'visible',
909
                'sample_registered':
910
                    {'view': 'visible', 'edit': 'visible', 'add': 'edit'},
911
                'to_be_sampled': {'view': 'visible', 'edit': 'visible'},
912
                'scheduled_sampling': {'view': 'visible', 'edit': 'visible'},
913
                'sampled': {'view': 'visible', 'edit': 'visible'},
914
                'to_be_preserved': {'view': 'visible', 'edit': 'visible'},
915
                'sample_due': {'view': 'visible', 'edit': 'visible'},
916
                'sample_received': {'view': 'visible', 'edit': 'visible'},
917
                'attachment_due': {'view': 'visible', 'edit': 'visible'},
918
                'to_be_verified': {'view': 'visible', 'edit': 'visible'},
919
                'verified': {'view': 'visible', 'edit': 'visible'},
920
                'published': {'view': 'visible', 'edit': 'invisible'},
921
                'invalid': {'view': 'visible', 'edit': 'invisible'},
922
                'rejected': {'view': 'visible', 'edit': 'invisible'},
923
            },
924
            catalog_name='bika_setup_catalog',
925
            base_query={'inactive_state': 'active'},
926
            showOn=True,
927
        ),
928
    ),
929
930
    StringField(
931
        'ClientOrderNumber',
932
        mode="rw",
933
        read_permission=View,
934
        write_permission=ModifyPortalContent,
935
        widget=StringWidget(
936
            label=_("Client Order Number"),
937
            description=_("The client side order number for this request"),
938
            size=20,
939
            render_own_label=True,
940
            visible={
941
                'edit': 'visible',
942
                'view': 'visible',
943
                'add': 'edit',
944
                'header_table': 'visible',
945
                'sample_registered': {
946
                    'view': 'visible', 'edit': 'visible', 'add': 'edit'},
947
                'to_be_sampled': {'view': 'visible', 'edit': 'visible'},
948
                'scheduled_sampling': {'view': 'visible', 'edit': 'visible'},
949
                'sampled': {'view': 'visible', 'edit': 'visible'},
950
                'to_be_preserved': {'view': 'visible', 'edit': 'visible'},
951
                'sample_due': {'view': 'visible', 'edit': 'visible'},
952
                'sample_received': {'view': 'visible', 'edit': 'visible'},
953
                'attachment_due': {'view': 'visible', 'edit': 'visible'},
954
                'to_be_verified': {'view': 'visible', 'edit': 'visible'},
955
                'verified': {'view': 'visible', 'edit': 'visible'},
956
                'published': {'view': 'visible', 'edit': 'invisible'},
957
                'invalid': {'view': 'visible', 'edit': 'invisible'},
958
                'rejected': {'view': 'visible', 'edit': 'invisible'},
959
            },
960
        ),
961
    ),
962
963
    # Sample field
964
    ProxyField(
965
        'ClientReference',
966
        proxy="context.getSample()",
967
        mode="rw",
968
        read_permission=View,
969
        write_permission=ModifyPortalContent,
970
        widget=StringWidget(
971
            label=_("Client Reference"),
972
            description=_("The client side reference for this request"),
973
            size=20,
974
            render_own_label=True,
975
            visible={
976
                'edit': 'visible',
977
                'view': 'visible',
978
                'add': 'edit',
979
                'secondary': 'disabled',
980
                'header_table': 'visible',
981
                'sample_registered':
982
                    {'view': 'visible', 'edit': 'visible', 'add': 'edit'},
983
                'to_be_sampled': {'view': 'visible', 'edit': 'visible'},
984
                'scheduled_sampling': {'view': 'visible', 'edit': 'visible'},
985
                'sampled': {'view': 'visible', 'edit': 'visible'},
986
                'to_be_preserved': {'view': 'visible', 'edit': 'visible'},
987
                'sample_due': {'view': 'visible', 'edit': 'visible'},
988
                'sample_received': {'view': 'visible', 'edit': 'visible'},
989
                'attachment_due': {'view': 'visible', 'edit': 'visible'},
990
                'to_be_verified': {'view': 'visible', 'edit': 'visible'},
991
                'verified': {'view': 'visible', 'edit': 'visible'},
992
                'published': {'view': 'visible', 'edit': 'invisible'},
993
                'invalid': {'view': 'visible', 'edit': 'invisible'},
994
                'rejected': {'view': 'visible', 'edit': 'invisible'},
995
            },
996
        ),
997
    ),
998
999
    # Sample field
1000
    ProxyField(
1001
        'ClientSampleID',
1002
        proxy="context.getSample()",
1003
        mode="rw",
1004
        read_permission=View,
1005
        write_permission=ModifyPortalContent,
1006
        widget=StringWidget(
1007
            label=_("Client Sample ID"),
1008
            description=_("The client side identifier of the sample"),
1009
            size=20,
1010
            render_own_label=True,
1011
            visible={
1012
                'edit': 'visible',
1013
                'view': 'visible',
1014
                'add': 'edit',
1015
                'secondary': 'disabled',
1016
                'header_table': 'visible',
1017
                'sample_registered': {'view': 'visible', 'edit': 'visible'},
1018
                'to_be_sampled': {'view': 'visible', 'edit': 'invisible'},
1019
                'scheduled_sampling': {'view': 'visible', 'edit': 'invisible'},
1020
                'sampled': {'view': 'visible', 'edit': 'invisible'},
1021
                'to_be_preserved': {'view': 'visible', 'edit': 'invisible'},
1022
                'sample_due': {'view': 'visible', 'edit': 'invisible'},
1023
                'sample_received': {'view': 'visible', 'edit': 'invisible'},
1024
                'attachment_due': {'view': 'visible', 'edit': 'invisible'},
1025
                'to_be_verified': {'view': 'visible', 'edit': 'invisible'},
1026
                'verified': {'view': 'visible', 'edit': 'invisible'},
1027
                'published': {'view': 'visible', 'edit': 'invisible'},
1028
                'invalid': {'view': 'visible', 'edit': 'invisible'},
1029
                'rejected': {'view': 'visible', 'edit': 'invisible'},
1030
            },
1031
        ),
1032
    ),
1033
1034
    # Sample field
1035
    ProxyField(
1036
        'SamplingDeviation',
1037
        proxy="context.getSample()",
1038
        allowed_types=('SamplingDeviation',),
1039
        relationship='AnalysisRequestSamplingDeviation',
1040
        referenceClass=HoldingReference,
1041
        mode="rw",
1042
        read_permission=View,
1043
        write_permission=ModifyPortalContent,
1044
        widget=ReferenceWidget(
1045
            label=_("Sampling Deviation"),
1046
            description=_("Deviation between the sample and how it "
1047
                          "was sampled"),
1048
            size=20,
1049
            render_own_label=True,
1050
            visible={
1051
                'edit': 'visible',
1052
                'view': 'visible',
1053
                'add': 'edit',
1054
                'secondary': 'disabled',
1055
                'header_table': 'visible',
1056
                'sample_registered': {
1057
                    'view': 'visible', 'edit': 'visible', 'add': 'edit'},
1058
                'to_be_sampled': {'view': 'visible', 'edit': 'visible'},
1059
                'scheduled_sampling': {'view': 'visible', 'edit': 'visible'},
1060
                'sampled': {'view': 'visible', 'edit': 'visible'},
1061
                'to_be_preserved': {'view': 'visible', 'edit': 'visible'},
1062
                'sample_due': {'view': 'visible', 'edit': 'visible'},
1063
                'sample_received': {'view': 'visible', 'edit': 'invisible'},
1064
                'attachment_due': {'view': 'visible', 'edit': 'invisible'},
1065
                'to_be_verified': {'view': 'visible', 'edit': 'invisible'},
1066
                'verified': {'view': 'visible', 'edit': 'invisible'},
1067
                'published': {'view': 'visible', 'edit': 'invisible'},
1068
                'invalid': {'view': 'visible', 'edit': 'invisible'},
1069
                'rejected': {'view': 'visible', 'edit': 'invisible'},
1070
            },
1071
            catalog_name='bika_setup_catalog',
1072
            base_query={'inactive_state': 'active'},
1073
            showOn=True,
1074
        ),
1075
    ),
1076
1077
    # Sample field
1078
    ProxyField(
1079
        'SampleCondition',
1080
        proxy="context.getSample()",
1081
        allowed_types=('SampleCondition',),
1082
        relationship='AnalysisRequestSampleCondition',
1083
        referenceClass=HoldingReference,
1084
        mode="rw",
1085
        read_permission=View,
1086
        write_permission=ModifyPortalContent,
1087
        widget=ReferenceWidget(
1088
            label=_("Sample condition"),
1089
            description=_("The current condition of the sample"),
1090
            size=20,
1091
            render_own_label=True,
1092
            visible={
1093
                'edit': 'visible',
1094
                'view': 'visible',
1095
                'add': 'edit',
1096
                'secondary': 'disabled',
1097
                'header_table': 'visible',
1098
                'sample_registered': {
1099
                    'view': 'visible', 'edit': 'visible', 'add': 'edit'},
1100
                'to_be_sampled': {'view': 'visible', 'edit': 'visible'},
1101
                'scheduled_sampling': {'view': 'visible', 'edit': 'visible'},
1102
                'sampled': {'view': 'visible', 'edit': 'visible'},
1103
                'to_be_preserved': {'view': 'visible', 'edit': 'visible'},
1104
                'sample_due': {'view': 'visible', 'edit': 'visible'},
1105
                'sample_received': {'view': 'visible', 'edit': 'invisible'},
1106
                'attachment_due': {'view': 'visible', 'edit': 'invisible'},
1107
                'to_be_verified': {'view': 'visible', 'edit': 'invisible'},
1108
                'verified': {'view': 'visible', 'edit': 'invisible'},
1109
                'published': {'view': 'visible', 'edit': 'invisible'},
1110
                'invalid': {'view': 'visible', 'edit': 'invisible'},
1111
                'rejected': {'view': 'visible', 'edit': 'invisible'},
1112
            },
1113
            catalog_name='bika_setup_catalog',
1114
            base_query={'inactive_state': 'active'},
1115
            showOn=True,
1116
        ),
1117
    ),
1118
1119
    StringField(
1120
        'Priority',
1121
        default='3',
1122
        vocabulary=PRIORITIES,
1123
        mode='rw',
1124
        read_permission=View,
1125
        write_permission=ModifyPortalContent,
1126
        widget=PrioritySelectionWidget(
1127
            label=_('Priority'),
1128
            format='select',
1129
            visible={
1130
                'edit': 'visible',
1131
                'view': 'visible',
1132
                'add': 'edit',
1133
                'header_table': 'visible',
1134
                'sample_registered':
1135
                    {'view': 'visible', 'edit': 'visible', 'add': 'edit'},
1136
                'to_be_sampled': {'view': 'visible', 'edit': 'visible'},
1137
                'scheduled_sampling': {'view': 'visible', 'edit': 'visible'},
1138
                'sampled': {'view': 'visible', 'edit': 'visible'},
1139
                'to_be_preserved': {'view': 'visible', 'edit': 'visible'},
1140
                'sample_due': {'view': 'visible', 'edit': 'visible'},
1141
                'sample_received': {'view': 'visible', 'edit': 'visible'},
1142
                'attachment_due': {'view': 'visible', 'edit': 'visible'},
1143
                'to_be_verified': {'view': 'visible', 'edit': 'visible'},
1144
                'verified': {'view': 'visible', 'edit': 'visible'},
1145
                'published': {'view': 'visible', 'edit': 'invisible'},
1146
                'invalid': {'view': 'visible', 'edit': 'invisible'},
1147
                'rejected': {'view': 'visible', 'edit': 'invisible'},
1148
            },
1149
        ),
1150
    ),
1151
    # Sample field
1152
    ProxyField(
1153
        'EnvironmentalConditions',
1154
        proxy="context.getSample()",
1155
        mode="rw",
1156
        read_permission=View,
1157
        write_permission=ModifyPortalContent,
1158
        widget=StringWidget(
1159
            label=_("Environmental conditions"),
1160
            description=_("The environmental condition during sampling"),
1161
            visible={
1162
                'edit': 'visible',
1163
                'view': 'visible',
1164
                'add': 'edit',
1165
                'header_table': 'prominent',
1166
                'sample_registered': {
1167
                    'view': 'visible', 'edit': 'visible', 'add': 'edit'},
1168
                'to_be_sampled': {'view': 'visible', 'edit': 'visible'},
1169
                'scheduled_sampling': {'view': 'visible', 'edit': 'visible'},
1170
                'sampled': {'view': 'visible', 'edit': 'visible'},
1171
                'to_be_preserved': {'view': 'visible', 'edit': 'visible'},
1172
                'sample_received': {'view': 'visible', 'edit': 'visible'},
1173
                'attachment_due': {'view': 'visible', 'edit': 'visible'},
1174
                'to_be_verified': {'view': 'visible', 'edit': 'visible'},
1175
                'verified': {'view': 'visible', 'edit': 'invisible'},
1176
                'published': {'view': 'visible', 'edit': 'invisible'},
1177
                'invalid': {'view': 'visible', 'edit': 'invisible'},
1178
                'rejected': {'view': 'visible', 'edit': 'invisible'},
1179
            },
1180
            render_own_label=True,
1181
            size=20,
1182
        ),
1183
    ),
1184
1185
    ReferenceField(
1186
        'DefaultContainerType',
1187
        allowed_types=('ContainerType',),
1188
        relationship='AnalysisRequestContainerType',
1189
        referenceClass=HoldingReference,
1190
        mode="rw",
1191
        read_permission=View,
1192
        write_permission=ModifyPortalContent,
1193
        widget=ReferenceWidget(
1194
            label=_("Default Container"),
1195
            description=_("Default container for new sample partitions"),
1196
            size=20,
1197
            render_own_label=True,
1198
            visible={
1199
                'edit': 'visible',
1200
                'view': 'visible',
1201
                'add': 'edit',
1202
                'secondary': 'disabled',
1203
                'header_table': 'visible',
1204
                'sample_registered': {
1205
                    'view': 'visible', 'edit': 'visible', 'add': 'edit'},
1206
                'to_be_sampled': {'view': 'visible', 'edit': 'invisible'},
1207
                'scheduled_sampling': {'view': 'visible', 'edit': 'invisible'},
1208
                'sampled': {'view': 'visible', 'edit': 'invisible'},
1209
                'to_be_preserved': {'view': 'visible', 'edit': 'invisible'},
1210
                'sample_due': {'view': 'visible', 'edit': 'invisible'},
1211
                'sample_received': {'view': 'visible', 'edit': 'invisible'},
1212
                'attachment_due': {'view': 'visible', 'edit': 'invisible'},
1213
                'to_be_verified': {'view': 'visible', 'edit': 'invisible'},
1214
                'verified': {'view': 'visible', 'edit': 'invisible'},
1215
                'published': {'view': 'visible', 'edit': 'invisible'},
1216
                'invalid': {'view': 'visible', 'edit': 'invisible'},
1217
                'rejected': {'view': 'visible', 'edit': 'invisible'},
1218
            },
1219
            catalog_name='bika_setup_catalog',
1220
            base_query={'inactive_state': 'active'},
1221
            showOn=True,
1222
        ),
1223
    ),
1224
1225
    # Sample field
1226
    ProxyField(
1227
        'AdHoc',
1228
        proxy="context.getSample()",
1229
        default=False,
1230
        mode="rw",
1231
        read_permission=View,
1232
        write_permission=ModifyPortalContent,
1233
        widget=BooleanWidget(
1234
            label=_("Sampled AdHoc"),
1235
            description=_("Was the sample taken in non-scheduled matter, " \
1236
                          "e.g. out of a recurring sampling schedule?"),
1237
            render_own_label=True,
1238
            visible={
1239
                'edit': 'visible',
1240
                'view': 'visible',
1241
                'add': 'edit',
1242
                'secondary': 'disabled',
1243
                'header_table': 'visible',
1244
                'sample_registered':
1245
                    {'view': 'visible', 'edit': 'visible', 'add': 'edit'},
1246
                'to_be_sampled': {'view': 'visible', 'edit': 'invisible'},
1247
                'scheduled_sampling': {'view': 'visible', 'edit': 'invisible'},
1248
                'sampled': {'view': 'visible', 'edit': 'invisible'},
1249
                'to_be_preserved': {'view': 'visible', 'edit': 'invisible'},
1250
                'sample_due': {'view': 'visible', 'edit': 'invisible'},
1251
                'sample_received': {'view': 'visible', 'edit': 'invisible'},
1252
                'attachment_due': {'view': 'visible', 'edit': 'invisible'},
1253
                'to_be_verified': {'view': 'visible', 'edit': 'invisible'},
1254
                'verified': {'view': 'visible', 'edit': 'invisible'},
1255
                'published': {'view': 'visible', 'edit': 'invisible'},
1256
                'invalid': {'view': 'visible', 'edit': 'invisible'},
1257
                'rejected': {'view': 'visible', 'edit': 'invisible'},
1258
            },
1259
        ),
1260
    ),
1261
1262
    # Sample field
1263
    ProxyField(
1264
        'Composite',
1265
        proxy="context.getSample()",
1266
        default=False,
1267
        mode="rw",
1268
        read_permission=View,
1269
        write_permission=ModifyPortalContent,
1270
        widget=BooleanWidget(
1271
            label=_("Composite"),
1272
            render_own_label=True,
1273
            visible={
1274
                'edit': 'visible',
1275
                'view': 'visible',
1276
                'add': 'edit',
1277
                'secondary': 'disabled',
1278
                'header_table': 'visible',
1279
                'sample_registered': {
1280
                    'view': 'visible', 'edit': 'visible', 'add': 'edit'},
1281
                'to_be_sampled': {'view': 'visible', 'edit': 'visible'},
1282
                'scheduled_sampling': {'view': 'visible', 'edit': 'visible'},
1283
                'sampled': {'view': 'visible', 'edit': 'visible'},
1284
                'to_be_preserved': {'view': 'visible', 'edit': 'visible'},
1285
                'sample_due': {'view': 'visible', 'edit': 'visible'},
1286
                'sample_received': {'view': 'visible', 'edit': 'visible'},
1287
                'attachment_due': {'view': 'visible', 'edit': 'visible'},
1288
                'to_be_verified': {'view': 'visible', 'edit': 'visible'},
1289
                'verified': {'view': 'visible', 'edit': 'invisible'},
1290
                'published': {'view': 'visible', 'edit': 'invisible'},
1291
                'invalid': {'view': 'visible', 'edit': 'invisible'},
1292
                'rejected': {'view': 'visible', 'edit': 'invisible'},
1293
            },
1294
        ),
1295
    ),
1296
1297
    BooleanField(
1298
        'InvoiceExclude',
1299
        default=False,
1300
        mode="rw",
1301
        read_permission=View,
1302
        write_permission=ModifyPortalContent,
1303
        widget=BooleanWidget(
1304
            label=_("Invoice Exclude"),
1305
            description=_("Should the analyses be excluded from the invoice?"),
1306
            render_own_label=True,
1307
            visible={
1308
                'edit': 'visible',
1309
                'view': 'visible',
1310
                'add': 'edit',
1311
                'header_table': 'visible',
1312
                'sample_registered': {
1313
                    'view': 'visible', 'edit': 'visible', 'add': 'edit'},
1314
                'to_be_sampled': {'view': 'visible', 'edit': 'visible'},
1315
                'scheduled_sampling': {'view': 'visible', 'edit': 'visible'},
1316
                'sampled': {'view': 'visible', 'edit': 'visible'},
1317
                'to_be_preserved': {'view': 'visible', 'edit': 'visible'},
1318
                'sample_due': {'view': 'visible', 'edit': 'visible'},
1319
                'sample_received': {'view': 'visible', 'edit': 'invisible'},
1320
                'attachment_due': {'view': 'visible', 'edit': 'invisible'},
1321
                'to_be_verified': {'view': 'visible', 'edit': 'invisible'},
1322
                'verified': {'view': 'visible', 'edit': 'invisible'},
1323
                'published': {'view': 'visible', 'edit': 'invisible'},
1324
                'invalid': {'view': 'visible', 'edit': 'invisible'},
1325
                'rejected': {'view': 'visible', 'edit': 'invisible'},
1326
            },
1327
        ),
1328
    ),
1329
1330
    ARAnalysesField(
1331
        'Analyses',
1332
        required=1,
1333
        mode="rw",
1334
        read_permission=View,
1335
        write_permission=ModifyPortalContent,
1336
        widget=ComputedWidget(
1337
            visible={
1338
                'edit': 'invisible',
1339
                'view': 'invisible',
1340
                'sample_registered': {
1341
                    'view': 'visible', 'edit': 'visible', 'add': 'invisible'},
1342
            }
1343
        ),
1344
    ),
1345
1346
    ReferenceField(
1347
        'Attachment',
1348
        multiValued=1,
1349
        allowed_types=('Attachment',),
1350
        referenceClass=HoldingReference,
1351
        relationship='AnalysisRequestAttachment',
1352
        mode="rw",
1353
        read_permission=View,
1354
        write_permission=ModifyPortalContent,
1355
        widget=ComputedWidget(
1356
            visible={
1357
                'edit': 'invisible',
1358
                'view': 'invisible',
1359
            },
1360
        )
1361
    ),
1362
1363
    # This is a virtual field and handled only by AR Add View to allow multi
1364
    # attachment upload in AR Add. It should never contain an own value!
1365
    FileField(
1366
        '_ARAttachment',
1367
        widget=FileWidget(
1368
            label=_("Attachment"),
1369
            description=_("Add one or more attachments to describe the "
1370
                          "sample in this analysis request, or to specify "
1371
                          "your request."),
1372
            render_own_label=True,
1373
            visible={
1374
                'view': 'invisible',
1375
                'add': 'edit',
1376
                'header_table': 'invisible',
1377
            },
1378
        )
1379
    ),
1380
1381
    ReferenceField(
1382
        'Invoice',
1383
        vocabulary_display_path_bound=sys.maxsize,
1384
        allowed_types=('Invoice',),
1385
        referenceClass=HoldingReference,
1386
        relationship='AnalysisRequestInvoice',
1387
        mode="rw",
1388
        read_permission=View,
1389
        write_permission=ModifyPortalContent,
1390
        widget=ComputedWidget(
1391
            visible={
1392
                'edit': 'invisible',
1393
                'view': 'invisible',
1394
            },
1395
        )
1396
    ),
1397
    # This field is a mirror of a field in Sample with the same name
1398
    ProxyField(
1399
        'DateReceived',
1400
        proxy="context.getSample()",
1401
        mode="rw",
1402
        read_permission=View,
1403
        write_permission=ModifyPortalContent,
1404
        widget=DateTimeWidget(
1405
            label=_("Date Sample Received"),
1406
            show_time=True,
1407
            description=_("The date when the sample was received"),
1408
            render_own_label=True,
1409
            visible={
1410
                'edit': 'invisible',
1411
                'view': 'visible',
1412
                'header_table': 'visible',
1413
            },
1414
        ),
1415
    ),
1416
    ComputedField(
1417
        'DatePublished',
1418
        mode="r",
1419
        read_permission=View,
1420
        expression="here.getDatePublished().strftime('%Y-%m-%d %H:%M %p') if here.getDatePublished() else ''",
1421
        widget=DateTimeWidget(
1422
            label=_("Date Published"),
1423
            visible={
1424
                'edit': 'visible',
1425
                'view': 'visible',
1426
                'add': 'invisible',
1427
                'secondary': 'invisible',
1428
                'header_table': 'visible',
1429
                'sample_registered': {
1430
                    'view': 'invisible', 'edit': 'invisible'},
1431
                'to_be_sampled': {'view': 'invisible', 'edit': 'invisible'},
1432
                'scheduled_sampling': {
1433
                    'view': 'invisible', 'edit': 'invisible'},
1434
                'sampled': {'view': 'invisible', 'edit': 'invisible'},
1435
                'to_be_preserved': {'view': 'invisible', 'edit': 'invisible'},
1436
                'sample_due': {'view': 'invisible', 'edit': 'invisible'},
1437
                'sample_received': {'view': 'invisible', 'edit': 'invisible'},
1438
                'attachment_due': {'view': 'invisible', 'edit': 'invisible'},
1439
                'to_be_verified': {'view': 'invisible', 'edit': 'invisible'},
1440
                'verified': {'view': 'invisible', 'edit': 'invisible'},
1441
                'published': {'view': 'visible', 'edit': 'invisible'},
1442
                'invalid': {'view': 'visible', 'edit': 'invisible'},
1443
                'rejected': {'view': 'invisible', 'edit': 'invisible'},
1444
            },
1445
        ),
1446
    ),
1447
1448
    RemarksField(
1449
        'Remarks',
1450
        searchable=True,
1451
        widget=RemarksWidget(
1452
            label=_("Remarks"),
1453
            description=_("Remarks and comments for this request"),
1454
            render_own_label=True,
1455
            visible={
1456
                'edit': 'visible',
1457
                'view': 'visible',
1458
                'add': 'edit'
1459
            },
1460
        ),
1461
    ),
1462
1463
    FixedPointField(
1464
        'MemberDiscount',
1465
        default_method='getDefaultMemberDiscount',
1466
        mode="rw",
1467
        read_permission=View,
1468
        write_permission=ModifyPortalContent,
1469
        widget=DecimalWidget(
1470
            label=_("Member discount %"),
1471
            description=_("Enter percentage value eg. 33.0"),
1472
            render_own_label=True,
1473
            visible={
1474
                'edit': 'visible',
1475
                'view': 'visible',
1476
                'add': 'invisible',
1477
                'sample_registered': {
1478
                    'view': 'invisible', 'edit': 'invisible'},
1479
            },
1480
        ),
1481
    ),
1482
    # TODO-catalog: move all these computed fields to methods
1483
    ComputedField(
1484
        'ClientUID',
1485
        expression='here.aq_parent.UID()',
1486
        widget=ComputedWidget(
1487
            visible=False,
1488
        ),
1489
    ),
1490
1491
    ComputedField(
1492
        'SampleTypeTitle',
1493
        expression="here.getSampleType().Title() if here.getSampleType() "
1494
                   "else ''",
1495
        widget=ComputedWidget(
1496
            visible=False,
1497
        ),
1498
    ),
1499
1500
    ComputedField(
1501
        'SamplePointTitle',
1502
        expression="here.getSamplePoint().Title() if here.getSamplePoint() "
1503
                   "else ''",
1504
        widget=ComputedWidget(
1505
            visible=False,
1506
        ),
1507
    ),
1508
1509
    ComputedField(
1510
        'SampleUID',
1511
        expression="here.getSample() and here.getSample().UID() or ''",
1512
        widget=ComputedWidget(
1513
            visible=False,
1514
        ),
1515
    ),
1516
1517
    ComputedField(
1518
        'SampleID',
1519
        expression="here.getSample() and here.getSample().getId() or ''",
1520
        widget=ComputedWidget(
1521
            visible=False,
1522
        ),
1523
    ),
1524
1525
    ComputedField(
1526
        'ContactUID',
1527
        expression="here.getContact() and here.getContact().UID() or ''",
1528
        widget=ComputedWidget(
1529
            visible=False,
1530
        ),
1531
    ),
1532
1533
    ComputedField(
1534
        'ProfilesUID',
1535
        expression="[p.UID() for p in here.getProfiles()] " \
1536
                   "if here.getProfiles() else []",
1537
        widget=ComputedWidget(
1538
            visible=False,
1539
        ),
1540
    ),
1541
1542
    ComputedField(
1543
        'Invoiced',
1544
        expression='here.getInvoice() and True or False',
1545
        default=False,
1546
        widget=ComputedWidget(
1547
            visible=False,
1548
        ),
1549
    ),
1550
    ComputedField(
1551
        'ReceivedBy',
1552
        expression='here.getReceivedBy()',
1553
        default='',
1554
        widget=ComputedWidget(visible=False,),
1555
    ),
1556
    ComputedField(
1557
        'CreatorFullName',
1558
        expression="here._getCreatorFullName()",
1559
        widget=ComputedWidget(visible=False),
1560
    ),
1561
    ComputedField(
1562
        'CreatorEmail',
1563
        expression="here._getCreatorEmail()",
1564
        widget=ComputedWidget(visible=False),
1565
    ),
1566
    ComputedField(
1567
        'SamplingRoundUID',
1568
        expression="here.getSamplingRound().UID() " \
1569
                   "if here.getSamplingRound() else ''",
1570
        widget=ComputedWidget(visible=False),
1571
    ),
1572
    ComputedField(
1573
        'SampleURL',
1574
        expression="here.getSample().absolute_url_path() " \
1575
                   "if here.getSample() else ''",
1576
        widget=ComputedWidget(visible=False),
1577
    ),
1578
    ComputedField(
1579
        'SamplerFullName',
1580
        expression="here._getSamplerFullName()",
1581
        widget=ComputedWidget(visible=False),
1582
    ),
1583
    ComputedField(
1584
        'SamplerEmail',
1585
        expression="here._getSamplerEmail()",
1586
        widget=ComputedWidget(visible=False),
1587
    ),
1588
    ComputedField(
1589
        'BatchID',
1590
        expression="here.getBatch().getId() if here.getBatch() else ''",
1591
        widget=ComputedWidget(visible=False),
1592
    ),
1593
    ComputedField(
1594
        'BatchURL',
1595
        expression="here.getBatch().absolute_url_path() " \
1596
                   "if here.getBatch() else ''",
1597
        widget=ComputedWidget(visible=False),
1598
    ),
1599
    ComputedField(
1600
        'ClientUID',
1601
        expression="here.getClient().UID() if here.getClient() else ''",
1602
        widget=ComputedWidget(visible=False),
1603
    ),
1604
    ComputedField(
1605
        'ClientTitle',
1606
        expression="here.getClient().Title() if here.getClient() else ''",
1607
        widget=ComputedWidget(visible=False),
1608
    ),
1609
    ComputedField(
1610
        'ClientURL',
1611
        expression="here.getClient().absolute_url_path() " \
1612
                   "if here.getClient() else ''",
1613
        widget=ComputedWidget(visible=False),
1614
    ),
1615
    ComputedField(
1616
        'ContactUsername',
1617
        expression="here.getContact().getUsername() " \
1618
                   "if here.getContact() else ''",
1619
        widget=ComputedWidget(visible=False),
1620
    ),
1621
    ComputedField(
1622
        'ContactFullName',
1623
        expression="here.getContact().getFullname() " \
1624
                   "if here.getContact() else ''",
1625
        widget=ComputedWidget(visible=False),
1626
    ),
1627
    ComputedField(
1628
        'ContactEmail',
1629
        expression="here.getContact().getEmailAddress() " \
1630
                   "if here.getContact() else ''",
1631
        widget=ComputedWidget(visible=False),
1632
    ),
1633
    ComputedField(
1634
        'SampleTypeUID',
1635
        expression="here.getSampleType().UID() " \
1636
                   "if here.getSampleType() else ''",
1637
        widget=ComputedWidget(visible=False),
1638
    ),
1639
    ComputedField(
1640
        'SamplePointUID',
1641
        expression="here.getSamplePoint().UID() " \
1642
                   "if here.getSamplePoint() else ''",
1643
        widget=ComputedWidget(visible=False),
1644
    ),
1645
    ComputedField(
1646
        'StorageLocationUID',
1647
        expression="here.getStorageLocation().UID() " \
1648
                   "if here.getStorageLocation() else ''",
1649
        widget=ComputedWidget(visible=False),
1650
    ),
1651
    ComputedField(
1652
        'ProfilesURL',
1653
        expression="[p.absolute_url_path() for p in here.getProfiles()] " \
1654
                   "if here.getProfiles() else []",
1655
        widget=ComputedWidget(visible=False),
1656
    ),
1657
    ComputedField(
1658
        'ProfilesTitle',
1659
        expression="[p.Title() for p in here.getProfiles()] " \
1660
                   "if here.getProfiles() else []",
1661
        widget=ComputedWidget(visible=False),
1662
    ),
1663
    ComputedField(
1664
        'ProfilesTitleStr',
1665
        expression="', '.join([p.Title() for p in here.getProfiles()]) " \
1666
                   "if here.getProfiles() else ''",
1667
        widget=ComputedWidget(visible=False),
1668
    ),
1669
    ComputedField(
1670
        'TemplateUID',
1671
        expression="here.getTemplate().UID() if here.getTemplate() else ''",
1672
        widget=ComputedWidget(visible=False),
1673
    ),
1674
    ComputedField(
1675
        'TemplateURL',
1676
        expression="here.getTemplate().absolute_url_path() " \
1677
                   "if here.getTemplate() else ''",
1678
        widget=ComputedWidget(visible=False),
1679
    ),
1680
    ComputedField(
1681
        'TemplateTitle',
1682
        expression="here.getTemplate().Title() if here.getTemplate() else ''",
1683
        widget=ComputedWidget(visible=False),
1684
    ),
1685
1686
    ReferenceField(
1687
        'ParentAnalysisRequest',
1688
        allowed_types=('AnalysisRequest',),
1689
        relationship='AnalysisRequestParentAnalysisRequest',
1690
        referenceClass=HoldingReference,
1691
        mode="rw",
1692
        read_permission=View,
1693
        write_permission=ModifyPortalContent,
1694
        widget=ReferenceWidget(
1695
            visible=False,
1696
        ),
1697
    ),
1698
1699
    # The Analysis Request the current Analysis Request comes from because of
1700
    # an invalidation of the former
1701
    ReferenceField(
1702
        'Invalidated',
1703
        allowed_types=('AnalysisRequest',),
1704
        relationship='AnalysisRequestRetracted',
1705
        referenceClass=HoldingReference,
1706
        mode="rw",
1707
        read_permission=View,
1708
        write_permission=ModifyPortalContent,
1709
        widget=ReferenceWidget(
1710
            visible=False,
1711
        ),
1712
    ),
1713
1714
    # The Analysis Request that was automatically generated due to the
1715
    # invalidation of the current Analysis Request
1716
    ComputedField(
1717
        'Retest',
1718
        expression="here.get_retest()",
1719
        widget=ComputedWidget(visible=False)
1720
    ),
1721
1722
    # For comments or results interpretation
1723
    # Old one, to be removed because of the incorporation of
1724
    # ResultsInterpretationDepts (due to LIMS-1628)
1725
    TextField(
1726
        'ResultsInterpretation',
1727
        mode="rw",
1728
        default_content_type='text/html',
1729
        # Input content type for the textfield
1730
        default_output_type='text/x-html-safe',
1731
        # getResultsInterpretation returns a str with html tags
1732
        # to conserve the txt format in the report.
1733
        read_permission=View,
1734
        write_permission=ModifyPortalContent,
1735
        widget=RichWidget(
1736
            description=_("Comments or results interpretation"),
1737
            label=_("Results Interpretation"),
1738
            size=10,
1739
            allow_file_upload=False,
1740
            default_mime_type='text/x-rst',
1741
            output_mime_type='text/x-html',
1742
            rows=3,
1743
            visible=False),
1744
    ),
1745
1746
    RecordsField('ResultsInterpretationDepts',
1747
                 subfields=('uid',
1748
                            'richtext'),
1749
                 subfield_labels={'uid': _('Department'),
1750
                                  'richtext': _('Results Interpreation')},
1751
                 widget=RichWidget(visible=False),
1752
                 ),
1753
    # Custom settings for the assigned analysis services
1754
    # https://jira.bikalabs.com/browse/LIMS-1324
1755
    # Fields:
1756
    #   - uid: Analysis Service UID
1757
    #   - hidden: True/False. Hide/Display in results reports
1758
    RecordsField('AnalysisServicesSettings',
1759
                 required=0,
1760
                 subfields=('uid', 'hidden',),
1761
                 widget=ComputedWidget(visible=False),
1762
                 ),
1763
    StringField(
1764
        'Printed',
1765
        mode="rw",
1766
        read_permission=View,
1767
        widget=StringWidget(
1768
            label=_("Printed"),
1769
            description=_("Indicates if the last ARReport is printed,"),
1770
            visible={'view': 'invisible',
1771
                     'edit': 'invisible'},
1772
        ),
1773
    ),
1774
)
1775
)
1776
1777
1778
# Some schema rearrangement
1779
schema['title'].required = False
1780
schema['id'].widget.visible = {
1781
    'edit': 'invisible',
1782
    'view': 'invisible',
1783
}
1784
schema['title'].widget.visible = {
1785
    'edit': 'invisible',
1786
    'view': 'invisible',
1787
}
1788
schema.moveField('Client', before='Contact')
1789
schema.moveField('ResultsInterpretation', pos='bottom')
1790
schema.moveField('ResultsInterpretationDepts', pos='bottom')
1791
1792
1793
class AnalysisRequest(BaseFolder):
1794
    implements(IAnalysisRequest)
1795
    security = ClassSecurityInfo()
1796
    displayContentsTab = False
1797
    schema = schema
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable schema does not seem to be defined.
Loading history...
1798
1799
    _at_rename_after_creation = True
1800
1801
    def _renameAfterCreation(self, check_auto_id=False):
1802
        from bika.lims.idserver import renameAfterCreation
1803
        renameAfterCreation(self)
1804
1805
    def _getCatalogTool(self):
1806
        from bika.lims.catalog import getCatalog
1807
        return getCatalog(self)
1808
1809
    def Title(self):
1810
        """ Return the Request ID as title """
1811
        return self.getId()
1812
1813
    def sortable_title(self):
1814
        """
1815
        Some lists expects this index
1816
        """
1817
        return self.getId()
1818
1819
    def Description(self):
1820
        """Returns searchable data as Description"""
1821
        descr = " ".join((self.getId(), self.aq_parent.Title()))
1822
        return safe_unicode(descr).encode('utf-8')
1823
1824
    def getClient(self):
1825
        if self.aq_parent.portal_type == 'Client':
1826
            return self.aq_parent
1827
        if self.aq_parent.portal_type == 'Batch':
1828
            return self.aq_parent.getClient()
1829
        return ''
1830
1831
    def getClientPath(self):
1832
        return "/".join(self.aq_parent.getPhysicalPath())
1833
1834
    def getProfilesTitle(self):
1835
        return [profile.Title() for profile in self.getProfiles()]
1836
1837
    def setPublicationSpecification(self, value):
1838
        """Never contains a value; this field is here for the UI." \
1839
        """
1840
        return value
1841
1842
    def getAnalysisService(self):
1843
        proxies = self.getAnalyses(full_objects=False)
1844
        value = set()
1845
        for proxy in proxies:
1846
            value.add(proxy.Title)
1847
        return list(value)
1848
1849
    def getAnalysts(self):
1850
        proxies = self.getAnalyses(full_objects=True)
1851
        value = []
1852
        for proxy in proxies:
1853
            val = proxy.getAnalyst()
1854
            if val not in value:
1855
                value.append(val)
1856
        return value
1857
1858
    def getDistrict(self):
1859
        client = self.aq_parent
1860
        return client.getDistrict()
1861
1862
    def getProvince(self):
1863
        client = self.aq_parent
1864
        return client.getProvince()
1865
1866
    @security.public
1867
    def getBatch(self):
1868
        # The parent type may be "Batch" during ar_add.
1869
        # This function fills the hidden field in ar_add.pt
1870
        if self.aq_parent.portal_type == 'Batch':
1871
            return self.aq_parent
1872
        else:
1873
            return self.Schema()['Batch'].get(self)
1874
1875
    @security.public
1876
    def getBatchUID(self):
1877
        batch = self.getBatch()
1878
        if batch:
1879
            return batch.UID()
1880
1881
    @security.public
1882
    def setBatch(self, value=None):
1883
        original_value = self.Schema().getField('Batch').get(self)
1884
        if original_value != value:
1885
            self.Schema().getField('Batch').set(self, value)
1886
            self._reindexAnalyses(['getBatchUID'], False)
1887
1888
    def getDefaultMemberDiscount(self):
1889
        """Compute default member discount if it applies
1890
        """
1891
        if hasattr(self, 'getMemberDiscountApplies'):
1892
            if self.getMemberDiscountApplies():
1893
                settings = self.bika_setup
1894
                return settings.getMemberDiscount()
1895
            else:
1896
                return "0.00"
1897
1898
    @security.public
1899
    def getAnalysesNum(self):
1900
        """ Returns an array with the number of analyses for the current AR in
1901
            different statuses, like follows:
1902
                [verified, total, not_submitted, to_be_verified]
1903
        """
1904
        an_nums = [0, 0, 0, 0]
1905
        for analysis in self.getAnalyses():
1906
            review_state = analysis.review_state
1907
            if review_state in ['retracted', 'rejected']:
1908
                # Discard retracted analyses
1909
                continue
1910
1911
            if not api.is_active(analysis):
1912
                # Discard non-active analyses
1913
                continue
1914
1915
            analysis_object = api.get_object(analysis)
1916
            actions = getReviewHistoryActionsList(analysis_object)
1917
            if 'verify' in actions:
1918
                # Assume the "last" state of analysis is verified
1919
                index = 0
1920
1921
            elif 'submit' not in actions or review_state != 'to_be_verified':
1922
                # Assume the "first" state of analysis is results_pending
1923
                index = 2
1924
1925
            else:
1926
                index = 3
1927
            an_nums[index] += 1
1928
            an_nums[1] += 1
1929
        return an_nums
1930
1931
    @security.public
1932
    def getResponsible(self):
1933
        """Return all manager info of responsible departments
1934
        """
1935
        managers = {}
1936
        for department in self.getDepartments():
1937
            manager = department.getManager()
1938
            if manager is None:
1939
                continue
1940
            manager_id = manager.getId()
1941
            if manager_id not in managers:
1942
                managers[manager_id] = {}
1943
                managers[manager_id]['salutation'] = safe_unicode(
1944
                    manager.getSalutation())
1945
                managers[manager_id]['name'] = safe_unicode(
1946
                    manager.getFullname())
1947
                managers[manager_id]['email'] = safe_unicode(
1948
                    manager.getEmailAddress())
1949
                managers[manager_id]['phone'] = safe_unicode(
1950
                    manager.getBusinessPhone())
1951
                managers[manager_id]['job_title'] = safe_unicode(
1952
                    manager.getJobTitle())
1953
                if manager.getSignature():
1954
                    managers[manager_id]['signature'] = \
1955
                        '{}/Signature'.format(manager.absolute_url())
1956
                else:
1957
                    managers[manager_id]['signature'] = False
1958
                managers[manager_id]['departments'] = ''
1959
            mngr_dept = managers[manager_id]['departments']
1960
            if mngr_dept:
1961
                mngr_dept += ', '
1962
            mngr_dept += safe_unicode(department.Title())
1963
            managers[manager_id]['departments'] = mngr_dept
1964
        mngr_keys = managers.keys()
1965
        mngr_info = {'ids': mngr_keys, 'dict': managers}
1966
1967
        return mngr_info
1968
1969
    @security.public
1970
    def getManagers(self):
1971
        """Return all managers of responsible departments
1972
        """
1973
        manager_ids = []
1974
        manager_list = []
1975
        for department in self.getDepartments():
1976
            manager = department.getManager()
1977
            if manager is None:
1978
                continue
1979
            manager_id = manager.getId()
1980
            if manager_id not in manager_ids:
1981
                manager_ids.append(manager_id)
1982
                manager_list.append(manager)
1983
        return manager_list
1984
1985
    security.declareProtected(View, 'getLate')
1986
1987
    def getLate(self):
1988
        """Return True if there is at least one late analysis in this Request
1989
        """
1990
        for analysis in self.getAnalyses(full_objects=True, retracted=False):
1991
            if analysis.isLateAnalysis():
1992
                return True
1993
        return False
1994
1995
    def getPrinted(self):
1996
        """ returns "0", "1" or "2" to indicate Printed state.
1997
            0 -> Never printed.
1998
            1 -> Printed after last publish
1999
            2 -> Printed but republished afterwards.
2000
        """
2001
        workflow = getToolByName(self, 'portal_workflow')
2002
        review_state = workflow.getInfoFor(self, 'review_state', '')
2003
        if review_state not in ['published']:
2004
            return "0"
2005
        report_list = sorted(self.objectValues('ARReport'),
2006
                             key=lambda report: report.getDatePublished())
2007
        if not report_list:
2008
            return "0"
2009
        last_report = report_list[-1]
2010
        if last_report.getDatePrinted():
2011
            return "1"
2012
        else:
2013
            for report in report_list:
2014
                if report.getDatePrinted():
2015
                    return "2"
2016
        return "0"
2017
2018
    def printLastReport(self):
2019
        """Setting Printed Time of the last report, so its Printed value will be 1
2020
        """
2021
        workflow = getToolByName(self, 'portal_workflow')
2022
        review_state = workflow.getInfoFor(self, 'review_state', '')
2023
        if review_state not in ['published']:
2024
            return
2025
        last_report = sorted(self.objectValues('ARReport'),
2026
                             key=lambda report: report.getDatePublished())[-1]
2027
        if last_report and not last_report.getDatePrinted():
2028
            last_report.setDatePrinted(DateTime())
2029
            self.reindexObject(idxs=['getPrinted'])
2030
2031
    security.declareProtected(View, 'getBillableItems')
2032
2033
    def getBillableItems(self):
2034
        """The main purpose of this function is to obtain the analysis services
2035
        and profiles from the analysis request
2036
        whose prices are needed to quote the analysis request.
2037
        If an analysis belongs to a profile, this analysis will only be
2038
        included in the analyses list if the profile
2039
        has disabled "Use Analysis Profile Price".
2040
2041
        :returns: a tuple of two lists. The first one only contains analysis
2042
                  services not belonging to a profile with active "Use Analysis
2043
                  Profile Price". The second list contains the profiles with
2044
                  activated "Use Analysis Profile Price".
2045
        """
2046
        workflow = getToolByName(self, 'portal_workflow')
2047
        # REMEMBER: Analysis != Analysis services
2048
        analyses = []
2049
        analysis_profiles = []
2050
        to_be_billed = []
2051
        # Getting all analysis request analyses
2052
        # Getting all analysis request analyses
2053
        ar_analyses = self.getAnalyses(cancellation_state='active',
2054
                                       full_objects=True)
2055
        for analysis in ar_analyses:
2056
            review_state = workflow.getInfoFor(analysis, 'review_state', '')
2057
            if review_state not in ('not_requested', 'retracted'):
2058
                analyses.append(analysis)
2059
        # Getting analysis request profiles
2060
        for profile in self.getProfiles():
2061
            # Getting the analysis profiles which has "Use Analysis Profile
2062
            # Price" enabled
2063
            if profile.getUseAnalysisProfilePrice():
2064
                analysis_profiles.append(profile)
2065
            else:
2066
                # we only need the analysis service keywords from these
2067
                # profiles
2068
                to_be_billed += [service.getKeyword() for service in
2069
                                 profile.getService()]
2070
        # So far we have three arrays:
2071
        #   - analyses: has all analyses (even if they are included inside a
2072
        # profile or not)
2073
        #   - analysis_profiles: has the profiles with "Use Analysis Profile
2074
        # Price" enabled
2075
        #   - to_be_quoted: has analysis services keywords from analysis
2076
        # profiles with "Use Analysis Profile Price"
2077
        #     disabled
2078
        # If a profile has its own price, we don't need their analises'
2079
        # prices, so we have to quit all
2080
        # analysis belonging to that profile. But if another profile has the
2081
        # same analysis service but has
2082
        # "Use Analysis Profile Price" disabled, the service must be included
2083
        #  as billable.
2084
        for profile in analysis_profiles:
2085
            for analysis_service in profile.getService():
2086
                for analysis in analyses:
2087
                    if analysis_service.getKeyword() == analysis.getKeyword() \
2088
                            and analysis.getKeyword() not in to_be_billed:
2089
                        analyses.remove(analysis)
2090
        return analyses, analysis_profiles
2091
2092
    def getServicesAndProfiles(self):
2093
        """This function gets all analysis services and all profiles and removes
2094
        the services belonging to a profile.
2095
2096
        :returns: a tuple of three lists, where the first list contains the
2097
        analyses and the second list the profiles.
2098
        The third contains the analyses objects used by the profiles.
2099
        """
2100
        # Getting requested analyses
2101
        workflow = getToolByName(self, 'portal_workflow')
2102
        analyses = []
2103
        # profile_analyses contains the profile's analyses (analysis !=
2104
        # service") objects to obtain
2105
        # the correct price later
2106
        profile_analyses = []
2107
        for analysis in self.objectValues('Analysis'):
2108
            review_state = workflow.getInfoFor(analysis, 'review_state', '')
2109
            if review_state != 'not_requested':
2110
                analyses.append(analysis)
2111
        # Getting all profiles
2112
        analysis_profiles = self.getProfiles() if len(
2113
            self.getProfiles()) > 0 else []
2114
        # Cleaning services included in profiles
2115
        for profile in analysis_profiles:
2116
            for analysis_service in profile.getService():
2117
                for analysis in analyses:
2118
                    if analysis_service.getKeyword() == analysis.getKeyword():
2119
                        analyses.remove(analysis)
2120
                        profile_analyses.append(analysis)
2121
        return analyses, analysis_profiles, profile_analyses
2122
2123
    security.declareProtected(View, 'getSubtotal')
2124
2125
    def getSubtotal(self):
2126
        """Compute Subtotal (without member discount and without vat)
2127
        """
2128
        analyses, a_profiles = self.getBillableItems()
2129
        return sum(
2130
            [Decimal(obj.getPrice()) for obj in analyses] +
2131
            [Decimal(obj.getAnalysisProfilePrice()) for obj in a_profiles]
2132
        )
2133
2134
    security.declareProtected(View, 'getSubtotalVATAmount')
2135
2136
    def getSubtotalVATAmount(self):
2137
        """Compute VAT amount without member discount
2138
        """
2139
        analyses, a_profiles = self.getBillableItems()
2140
        if len(analyses) > 0 or len(a_profiles) > 0:
2141
            return sum(
2142
                [Decimal(o.getVATAmount()) for o in analyses] +
2143
                [Decimal(o.getVATAmount()) for o in a_profiles]
2144
            )
2145
        return 0
2146
2147
    security.declareProtected(View, 'getSubtotalTotalPrice')
2148
2149
    def getSubtotalTotalPrice(self):
2150
        """Compute the price with VAT but no member discount
2151
        """
2152
        return self.getSubtotal() + self.getSubtotalVATAmount()
2153
2154
    security.declareProtected(View, 'getDiscountAmount')
2155
2156
    def getDiscountAmount(self):
2157
        """It computes and returns the analysis service's discount amount
2158
        without VAT
2159
        """
2160
        has_client_discount = self.aq_parent.getMemberDiscountApplies()
2161
        if has_client_discount:
2162
            discount = Decimal(self.getDefaultMemberDiscount())
2163
            return Decimal(self.getSubtotal() * discount / 100)
2164
        else:
2165
            return 0
2166
2167
    def getVATAmount(self):
2168
        """It computes the VAT amount from (subtotal-discount.)*VAT/100, but
2169
        each analysis has its own VAT!
2170
2171
        :returns: the analysis request VAT amount with the discount
2172
        """
2173
        has_client_discount = self.aq_parent.getMemberDiscountApplies()
2174
        VATAmount = self.getSubtotalVATAmount()
2175
        if has_client_discount:
2176
            discount = Decimal(self.getDefaultMemberDiscount())
2177
            return Decimal((1 - discount / 100) * VATAmount)
2178
        else:
2179
            return VATAmount
2180
2181
    security.declareProtected(View, 'getTotalPrice')
2182
2183
    def getTotalPrice(self):
2184
        """It gets the discounted price from analyses and profiles to obtain the
2185
        total value with the VAT and the discount applied
2186
2187
        :returns: analysis request's total price including VATs and discounts
2188
        """
2189
        price = (self.getSubtotal() - self.getDiscountAmount() +
2190
                 self.getVATAmount())
2191
        return price
2192
2193
    getTotal = getTotalPrice
2194
2195
    security.declareProtected(ManageInvoices, 'issueInvoice')
2196
2197
    # noinspection PyUnusedLocal
2198
    def issueInvoice(self, REQUEST=None, RESPONSE=None):
2199
        """Issue invoice
2200
        """
2201
        # check for an adhoc invoice batch for this month
2202
        # noinspection PyCallingNonCallable
2203
        now = DateTime()
2204
        batch_month = now.strftime('%b %Y')
2205
        batch_title = '%s - %s' % (batch_month, 'ad hoc')
2206
        invoice_batch = None
2207
        for b_proxy in self.portal_catalog(portal_type='InvoiceBatch',
2208
                                           Title=batch_title):
2209
            invoice_batch = b_proxy.getObject()
2210
        if not invoice_batch:
2211
            # noinspection PyCallingNonCallable
2212
            first_day = DateTime(now.year(), now.month(), 1)
2213
            start_of_month = first_day.earliestTime()
2214
            last_day = first_day + 31
2215
            # noinspection PyUnresolvedReferences
2216
            while last_day.month() != now.month():
2217
                last_day -= 1
2218
            # noinspection PyUnresolvedReferences
2219
            end_of_month = last_day.latestTime()
2220
2221
            invoices = self.invoices
2222
            batch_id = invoices.generateUniqueId('InvoiceBatch')
2223
            invoice_batch = _createObjectByType("InvoiceBatch", invoices,
2224
                                                batch_id)
2225
            invoice_batch.edit(
2226
                title=batch_title,
2227
                BatchStartDate=start_of_month,
2228
                BatchEndDate=end_of_month,
2229
            )
2230
            invoice_batch.processForm()
2231
2232
        client_uid = self.getClientUID()
2233
        # Get the created invoice
2234
        invoice = invoice_batch.createInvoice(client_uid, [self, ])
2235
        invoice.setAnalysisRequest(self)
2236
        # Set the created invoice in the schema
2237
        self.Schema()['Invoice'].set(self, invoice)
2238
2239
    security.declarePublic('printInvoice')
2240
2241
    # noinspection PyUnusedLocal
2242
    def printInvoice(self, REQUEST=None, RESPONSE=None):
2243
        """Print invoice
2244
        """
2245
        invoice = self.getInvoice()
2246
        invoice_url = invoice.absolute_url()
2247
        RESPONSE.redirect('{}/invoice_print'.format(invoice_url))
2248
2249
    @deprecated("addARAttachment will be removed in senaite.core 1.3.0")
2250
    def addARAttachment(self, REQUEST=None, RESPONSE=None):
2251
        """Add the file as an attachment
2252
        """
2253
        workflow = getToolByName(self, 'portal_workflow')
2254
2255
        this_file = self.REQUEST.form['AttachmentFile_file']
2256
        if 'Analysis' in self.REQUEST.form:
2257
            analysis_uid = self.REQUEST.form['Analysis']
2258
        else:
2259
            analysis_uid = None
2260
2261
        attachmentid = self.generateUniqueId('Attachment')
2262
        attachment = _createObjectByType("Attachment", self.aq_parent,
2263
                                         attachmentid)
2264
        attachment.edit(
2265
            AttachmentFile=this_file,
2266
            AttachmentType=self.REQUEST.form.get('AttachmentType', ''),
2267
            AttachmentKeys=self.REQUEST.form['AttachmentKeys'])
2268
        attachment.processForm()
2269
        attachment.reindexObject()
2270
2271
        if analysis_uid:
2272
            tool = getToolByName(self, REFERENCE_CATALOG)
2273
            analysis = tool.lookupObject(analysis_uid)
2274
            others = analysis.getAttachment()
2275
            attachments = []
2276
            for other in others:
2277
                attachments.append(other.UID())
2278
            attachments.append(attachment.UID())
2279
            analysis.setAttachment(attachments)
2280
            if workflow.getInfoFor(analysis,
2281
                                   'review_state') == 'attachment_due':
2282
                workflow.doActionFor(analysis, 'attach')
2283
        else:
2284
            others = self.getAttachment()
2285
            attachments = []
2286
            for other in others:
2287
                attachments.append(other.UID())
2288
            attachments.append(attachment.UID())
2289
2290
            self.setAttachment(attachments)
2291
2292
        if REQUEST['HTTP_REFERER'].endswith('manage_results'):
2293
            RESPONSE.redirect('{}/manage_results'.format(self.absolute_url()))
2294
        else:
2295
            RESPONSE.redirect(self.absolute_url())
2296
2297
    @deprecated("delARAttachment will be removed in senaite.core 1.3.0")
2298
    def delARAttachment(self, REQUEST=None, RESPONSE=None):
2299
        """Delete the attachment
2300
        """
2301
        tool = getToolByName(self, REFERENCE_CATALOG)
2302
        if 'Attachment' in self.REQUEST.form:
2303
            attachment_uid = self.REQUEST.form['Attachment']
2304
            attachment = tool.lookupObject(attachment_uid)
2305
            parent_r = attachment.getRequest()
2306
            parent_a = attachment.getAnalysis()
2307
2308
            parent = parent_a if parent_a else parent_r
2309
            others = parent.getAttachment()
2310
            attachments = []
2311
            for other in others:
2312
                if not other.UID() == attachment_uid:
2313
                    attachments.append(other.UID())
2314
            parent.setAttachment(attachments)
2315
            client = attachment.aq_parent
2316
            ids = [attachment.getId(), ]
2317
            BaseFolder.manage_delObjects(client, ids, REQUEST)
2318
2319
        RESPONSE.redirect(self.REQUEST.get_header('referer'))
2320
2321
    security.declarePublic('getVerifier')
2322
2323
    @deprecated("Use getVerifiers instead")
2324
    def getVerifier(self):
2325
        """Returns the user that verified the whole Analysis Request. Since the
2326
        verification is done automatically as soon as all the analyses it
2327
        contains are verified, this function returns the user that verified the
2328
        last analysis pending.
2329
        """
2330
        wtool = getToolByName(self, 'portal_workflow')
2331
        mtool = getToolByName(self, 'portal_membership')
2332
2333
        verifier = None
2334
        # noinspection PyBroadException
2335
        try:
2336
            review_history = wtool.getInfoFor(self, 'review_history')
2337
        except:  # noqa FIXME: remove blind except!
2338
            return 'access denied'
2339
2340
        if not review_history:
2341
            return 'no history'
2342
        for items in review_history:
2343
            action = items.get('action')
2344
            if action != 'verify':
2345
                continue
2346
            actor = items.get('actor')
2347
            member = mtool.getMemberById(actor)
2348
            verifier = member.getProperty('fullname')
2349
            if verifier is None or verifier == '':
2350
                verifier = actor
2351
        return verifier
2352
2353
    @security.public
2354
    def getVerifiersIDs(self):
2355
        """Returns the ids from users that have verified at least one analysis
2356
        from this Analysis Request
2357
        """
2358
        verifiers_ids = list()
2359
        for brain in self.getAnalyses():
2360
            verifiers = brain.getVerificators or ""
2361
            verifiers_ids += verifiers.split(",")
2362
        return list(set(verifiers_ids))
2363
2364
    @security.public
2365
    def getVerifiers(self):
2366
        """Returns the list of lab contacts that have verified at least one
2367
        analysis from this Analysis Request
2368
        """
2369
        contacts = list()
2370
        for verifier in self.getVerifiersIDs():
2371
            user = api.get_user(verifier)
2372
            contact = api.get_user_contact(user, ["LabContact"])
2373
            if contact:
2374
                contacts.append(contact)
2375
        return contacts
2376
2377
    security.declarePublic('getContactUIDForUser')
2378
2379
    def getContactUIDForUser(self):
2380
        """get the UID of the contact associated with the authenticated user
2381
        """
2382
        mt = getToolByName(self, 'portal_membership')
2383
        user = mt.getAuthenticatedMember()
2384
        user_id = user.getUserName()
2385
        pc = getToolByName(self, 'portal_catalog')
2386
        r = pc(portal_type='Contact',
2387
               getUsername=user_id)
2388
        if len(r) == 1:
2389
            return r[0].UID
2390
2391
    security.declarePublic('current_date')
2392
2393
    def current_date(self):
2394
        """return current date
2395
        """
2396
        # noinspection PyCallingNonCallable
2397
        return DateTime()
2398
2399
    def getQCAnalyses(self, qctype=None, review_state=None):
2400
        """return the QC analyses performed in the worksheet in which, at
2401
        least, one sample of this AR is present.
2402
2403
        Depending on qctype value, returns the analyses of:
2404
2405
            - 'b': all Blank Reference Samples used in related worksheet/s
2406
            - 'c': all Control Reference Samples used in related worksheet/s
2407
            - 'd': duplicates only for samples contained in this AR
2408
2409
        If qctype==None, returns all type of qc analyses mentioned above
2410
        """
2411
        qcanalyses = []
2412
        suids = []
2413
        ans = self.getAnalyses()
2414
        wf = getToolByName(self, 'portal_workflow')
2415
        for an in ans:
2416
            an = an.getObject()
2417
            if an.getServiceUID() not in suids:
2418
                suids.append(an.getServiceUID())
2419
2420
        def valid_dup(wan):
2421
            if wan.portal_type == 'ReferenceAnalysis':
2422
                return False
2423
            an_state = wf.getInfoFor(wan, 'review_state')
2424
            return \
2425
                wan.portal_type == 'DuplicateAnalysis' \
2426
                and wan.getRequestID() == self.id \
2427
                and (review_state is None or an_state in review_state)
2428
2429
        def valid_ref(wan):
2430
            if wan.portal_type != 'ReferenceAnalysis':
2431
                return False
2432
            an_state = wf.getInfoFor(wan, 'review_state')
2433
            an_reftype = wan.getReferenceType()
2434
            return wan.getServiceUID() in suids \
2435
                and wan not in qcanalyses \
2436
                and (qctype is None or an_reftype == qctype) \
2437
                and (review_state is None or an_state in review_state)
2438
2439
        for an in ans:
2440
            an = an.getObject()
2441
            br = an.getBackReferences('WorksheetAnalysis')
2442
            if len(br) > 0:
2443
                ws = br[0]
2444
                was = ws.getAnalyses()
2445
                for wa in was:
2446
                    if valid_dup(wa):
2447
                        qcanalyses.append(wa)
2448
                    elif valid_ref(wa):
2449
                        qcanalyses.append(wa)
2450
2451
        return qcanalyses
2452
2453
    def isInvalid(self):
2454
        """return if the Analysis Request has been invalidated
2455
        """
2456
        workflow = getToolByName(self, 'portal_workflow')
2457
        return workflow.getInfoFor(self, 'review_state') == 'invalid'
2458
2459
    def getRequestedAnalyses(self):
2460
        """It returns all requested analyses, even if they belong to an
2461
        analysis profile or not.
2462
        """
2463
        #
2464
        # title=Get requested analyses
2465
        #
2466
        result = []
2467
        cats = {}
2468
        workflow = getToolByName(self, 'portal_workflow')
2469
        for analysis in self.getAnalyses(full_objects=True):
2470
            review_state = workflow.getInfoFor(analysis, 'review_state')
2471
            if review_state == 'not_requested':
2472
                continue
2473
            category_name = analysis.getCategoryTitle()
2474
            if category_name not in cats:
2475
                cats[category_name] = {}
2476
            cats[category_name][analysis.Title()] = analysis
2477
        cat_keys = sorted(cats.keys(), key=methodcaller('lower'))
2478
        for cat_key in cat_keys:
2479
            analyses = cats[cat_key]
2480
            analysis_keys = sorted(analyses.keys(),
2481
                                   key=methodcaller('lower'))
2482
            for analysis_key in analysis_keys:
2483
                result.append(analyses[analysis_key])
2484
        return result
2485
2486
    def getSamplingRoundUID(self):
2487
        """Obtains the sampling round UID
2488
        :returns: UID
2489
        """
2490
        sr = self.getSamplingRound()
2491
        if sr:
2492
            return sr.UID()
2493
        else:
2494
            return ''
2495
2496
    def getStorageLocationTitle(self):
2497
        """ A method for AR listing catalog metadata
2498
        :return: Title of Storage Location
2499
        """
2500
        sl = self.getStorageLocation()
2501
        if sl:
2502
            return sl.Title()
2503
        return ''
2504
2505
    @security.public
2506
    def getResultsRange(self):
2507
        """Returns the valid result ranges for the analyses this Analysis
2508
        Request contains.
2509
2510
        By default uses the result ranges defined in the Analysis Specification
2511
        set in "Specification" field if any. Values manually set through
2512
        `ResultsRange` field for any given analysis keyword have priority over
2513
        the result ranges defined in "Specification" field.
2514
2515
        :return: A list of dictionaries, where each dictionary defines the
2516
            result range to use for any analysis contained in this Analysis
2517
            Request for the keyword specified. Each dictionary has, at least,
2518
                the following keys: "keyword", "min", "max"
2519
        :rtype: dict
2520
        """
2521
        specs_range = []
2522
        specification = self.getSpecification()
2523
        if specification:
2524
            specs_range = specification.getResultsRange()
2525
            specs_range = specs_range and specs_range or []
2526
2527
        # Override with AR's custom ranges
2528
        ar_range = self.Schema().getField("ResultsRange").get(self)
2529
        if not ar_range:
2530
            return specs_range
2531
2532
        # Remove those analysis ranges that neither min nor max are floatable
2533
        an_specs = [an for an in ar_range if
2534
                    api.is_floatable(an.get('min', None)) or
2535
                    api.is_floatable(an.get('max', None))]
2536
        # Want to know which are the analyses that needs to be overriden
2537
        keywords = map(lambda item: item.get('keyword'), an_specs)
2538
        # Get rid of those analyses to be overriden
2539
        out_specs = [sp for sp in specs_range if sp['keyword'] not in keywords]
2540
        # Add manually set ranges
2541
        out_specs.extend(an_specs)
2542
        return map(lambda spec: ResultsRangeDict(spec), out_specs)
2543
2544
    def getDatePublished(self):
2545
        """
2546
        Returns the transition date from the Analysis Request object
2547
        """
2548
        return getTransitionDate(self, 'publish', return_as_datetime=True)
2549
2550
    security.declarePublic('getSamplingDeviationTitle')
2551
2552
    def getSamplingDeviationTitle(self):
2553
        """
2554
        It works as a metacolumn.
2555
        """
2556
        sd = self.getSamplingDeviation()
2557
        if sd:
2558
            return sd.Title()
2559
        return ''
2560
2561
    security.declarePublic('getHazardous')
2562
2563
    def getHazardous(self):
2564
        """
2565
        It works as a metacolumn.
2566
        """
2567
        sample_type = self.getSampleType()
2568
        if sample_type:
2569
            return sample_type.getHazardous()
2570
        return False
2571
2572
    security.declarePublic('getContactURL')
2573
2574
    def getContactURL(self):
2575
        """
2576
        It works as a metacolumn.
2577
        """
2578
        contact = self.getContact()
2579
        if contact:
2580
            return contact.absolute_url_path()
2581
        return ''
2582
2583
    security.declarePublic('getSamplingWorkflowEnabled')
2584
2585
    def getSamplingWorkflowEnabled(self):
2586
        """
2587
        It works as a metacolumn.
2588
        """
2589
        sample = self.getSample()
2590
        if sample:
2591
            return sample.getSamplingWorkflowEnabled()
2592
        return ''
2593
2594
    def getSamplers(self):
2595
        return getUsers(self, ['Sampler', ])
2596
2597
    def getDepartments(self):
2598
        """Returns a list of the departments assigned to the Analyses
2599
        from this Analysis Request
2600
        """
2601
        bsc = getToolByName(self, 'bika_setup_catalog')
2602
        dept_uids = []
2603
        for an in self.getAnalyses():
2604
            deptuid = None
2605
            if hasattr(an, 'getDepartmentUID'):
2606
                deptuid = an.getDepartmentUID
2607
            if not deptuid and hasattr(an, 'getObject'):
2608
                deptuid = an.getObject().getDepartmentUID()
2609
            if deptuid:
2610
                dept_uids.append(deptuid)
2611
        brains = bsc(portal_type='Department', UID=dept_uids)
2612
        depts = [b.getObject() for b in brains]
2613
        return list(set(depts))
2614
2615
    def getDepartmentUIDs(self):
2616
        """Return a list of the UIDs of departments assigned to the Analyses
2617
        from this Analysis Request.
2618
        """
2619
        return [dept.UID() for dept in self.getDepartments()]
2620
2621
    def getResultsInterpretationByDepartment(self, department=None):
2622
        """Returns the results interpretation for this Analysis Request
2623
           and department. If department not set, returns the results
2624
           interpretation tagged as 'General'.
2625
2626
        :returns: a dict with the following keys:
2627
            {'uid': <department_uid> or 'general', 'richtext': <text/plain>}
2628
        """
2629
        uid = department.UID() if department else 'general'
2630
        rows = self.Schema()['ResultsInterpretationDepts'].get(self)
2631
        row = [row for row in rows if row.get('uid') == uid]
2632
        if len(row) > 0:
2633
            row = row[0]
2634
        elif uid == 'general' \
2635
                and hasattr(self, 'getResultsInterpretation') \
2636
                and self.getResultsInterpretation():
2637
            row = {'uid': uid, 'richtext': self.getResultsInterpretation()}
2638
        else:
2639
            row = {'uid': uid, 'richtext': ''}
2640
        return row
2641
2642
    def getAnalysisServiceSettings(self, uid):
2643
        """Returns a dictionary with the settings for the analysis service that
2644
        match with the uid provided.
2645
2646
        If there are no settings for the analysis service and
2647
        analysis requests:
2648
2649
        1. looks for settings in AR's ARTemplate. If found, returns the
2650
           settings for the AnalysisService set in the Template
2651
        2. If no settings found, looks in AR's ARProfile. If found, returns the
2652
           settings for the AnalysisService from the AR Profile. Otherwise,
2653
           returns a one entry dictionary with only the key 'uid'
2654
        """
2655
        sets = [s for s in self.getAnalysisServicesSettings()
2656
                if s.get('uid', '') == uid]
2657
2658
        # Created by using an ARTemplate?
2659
        if not sets and self.getTemplate():
2660
            adv = self.getTemplate().getAnalysisServiceSettings(uid)
2661
            sets = [adv] if 'hidden' in adv else []
2662
2663
        # Created by using an AR Profile?
2664
        if not sets and self.getProfiles():
2665
            adv = []
2666
            adv += [profile.getAnalysisServiceSettings(uid) for profile in
2667
                    self.getProfiles()]
2668
            sets = adv if 'hidden' in adv[0] else []
2669
2670
        return sets[0] if sets else {'uid': uid}
2671
2672
    def getPartitions(self):
2673
        """This functions returns the partitions from the analysis request's
2674
        analyses.
2675
2676
        :returns: a list with the full partition objects
2677
        """
2678
        analyses = self.getRequestedAnalyses()
2679
        partitions = []
2680
        for analysis in analyses:
2681
            if analysis.getSamplePartition() not in partitions:
2682
                partitions.append(analysis.getSamplePartition())
2683
        return partitions
2684
2685
    def getContainers(self):
2686
        """This functions returns the containers from the analysis request's
2687
        analyses
2688
2689
        :returns: a list with the full partition objects
2690
        """
2691
        partitions = self.getPartitions()
2692
        containers = []
2693
        for partition in partitions:
2694
            if partition.getContainer():
2695
                containers.append(partition.getContainer())
2696
        return containers
2697
2698 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...
2699
        """Checks if the analysis service that match with the uid provided must
2700
        be hidden in results. If no hidden assignment has been set for the
2701
        analysis in this request, returns the visibility set to the analysis
2702
        itself.
2703
2704
        Raise a TypeError if the uid is empty or None
2705
2706
        Raise a ValueError if there is no hidden assignment in this request or
2707
        no analysis service found for this uid.
2708
        """
2709
        if not uid:
2710
            raise TypeError('None type or empty uid')
2711
        sets = self.getAnalysisServiceSettings(uid)
2712
        if 'hidden' not in sets:
2713
            uc = getToolByName(self, 'uid_catalog')
2714
            serv = uc(UID=uid)
2715
            if serv and len(serv) == 1:
2716
                return serv[0].getObject().getRawHidden()
2717
            else:
2718
                raise ValueError('{} is not valid'.format(uid))
2719
        return sets.get('hidden', False)
2720
2721
    def getRejecter(self):
2722
        """If the Analysis Request has been rejected, returns the user who did the
2723
        rejection. If it was not rejected or the current user has not enough
2724
        privileges to access to this information, returns None.
2725
        """
2726
        wtool = getToolByName(self, 'portal_workflow')
2727
        mtool = getToolByName(self, 'portal_membership')
2728
        # noinspection PyBroadException
2729
        try:
2730
            review_history = wtool.getInfoFor(self, 'review_history')
2731
        except:  # noqa FIXME: remove blind except!
2732
            return None
2733
        for items in review_history:
2734
            action = items.get('action')
2735
            if action != 'reject':
2736
                continue
2737
            actor = items.get('actor')
2738
            return mtool.getMemberById(actor)
2739
        return None
2740
2741
    def getReceivedBy(self):
2742
        """
2743
        Returns the User who received the analysis request.
2744
        :returns: the user id
2745
        """
2746
        user = getTransitionUsers(self, 'receive', last_user=True)
2747
        return user[0] if user else ''
2748
2749
    def getDateVerified(self):
2750
        """
2751
        Returns the date of verification as a DateTime object.
2752
        """
2753
        return getTransitionDate(self, 'verify', return_as_datetime=True)
2754
2755
    @security.public
2756
    def getPrioritySortkey(self):
2757
        """Returns the key that will be used to sort the current Analysis
2758
        Request based on both its priority and creation date. On ASC sorting,
2759
        the oldest item with highest priority will be displayed.
2760
        :return: string used for sorting
2761
        """
2762
        priority = self.getPriority()
2763
        created_date = self.created().ISO8601()
2764
        return '%s.%s' % (priority, created_date)
2765
2766
    @security.public
2767
    def setPriority(self, value):
2768
        if not value:
2769
            value = self.Schema().getField('Priority').getDefault(self)
2770
        original_value = self.Schema().getField('Priority').get(self)
2771
        if original_value != value:
2772
            self.Schema().getField('Priority').set(self, value)
2773
            self._reindexAnalyses(['getPrioritySortkey'], True)
2774
2775
    @security.private
2776
    def _reindexAnalyses(self, idxs=None, update_metadata=False):
2777
        if not idxs and not update_metadata:
2778
            return
2779
        if not idxs:
2780
            idxs = []
2781
        analyses = self.getAnalyses()
2782
        catalog = getToolByName(self, CATALOG_ANALYSIS_LISTING)
2783
        for analysis in analyses:
2784
            analysis_obj = analysis.getObject()
2785
            catalog.reindexObject(analysis_obj, idxs=idxs, update_metadata=1)
2786
2787
    def _getCreatorFullName(self):
2788
        """
2789
        Returns the full name of this analysis request's creator.
2790
        """
2791
        return user_fullname(self, self.Creator())
2792
2793
    def _getCreatorEmail(self):
2794
        """
2795
        Returns the email of this analysis request's creator.
2796
        """
2797
        return user_email(self, self.Creator())
2798
2799
    def _getSamplerFullName(self):
2800
        """
2801
        Returns the full name's defined sampler.
2802
        """
2803
        return user_fullname(self, self.getSampler())
2804
2805
    def _getSamplerEmail(self):
2806
        """
2807
        Returns the email of this analysis request's sampler.
2808
        """
2809
        return user_email(self, self.Creator())
2810
2811
    # TODO Workflow, AnalysisRequest Move to guards.verify?
2812 View Code Duplication
    def isVerifiable(self):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
2813
        """Checks it the current Analysis Request can be verified. This is, its
2814
        not a cancelled Analysis Request and all the analyses that contains
2815
        are verifiable too. Note that verifying an Analysis Request is in fact,
2816
        the same as verifying all the analyses that contains. Therefore, the
2817
        'verified' state of an Analysis Request shouldn't be a 'real' state,
2818
        rather a kind-of computed state, based on the statuses of the analyses
2819
        it contains. This is why this function checks if the analyses
2820
        contained are verifiable, cause otherwise, the Analysis Request will
2821
        never be able to reach a 'verified' state.
2822
        :returns: True or False
2823
        """
2824
        # Check if the analysis request is active
2825
        workflow = getToolByName(self, "portal_workflow")
2826
        objstate = workflow.getInfoFor(self, 'cancellation_state', 'active')
2827
        if objstate == "cancelled":
2828
            return False
2829
2830
        # Check if the analysis request state is to_be_verified
2831
        review_state = workflow.getInfoFor(self, "review_state")
2832
        if review_state == 'to_be_verified':
2833
            # This means that all the analyses from this analysis request have
2834
            # already been transitioned to a 'verified' state, and so the
2835
            # analysis request itself
2836
            return True
2837
        else:
2838
            # Check if the analyses contained in this analysis request are
2839
            # verifiable. Only check those analyses not cancelled and that
2840
            # are not in a kind-of already verified state
2841
            canbeverified = True
2842
            omit = ['published', 'retracted', 'rejected', 'verified']
2843
            for a in self.getAnalyses(full_objects=True):
2844
                st = workflow.getInfoFor(a, 'cancellation_state', 'active')
2845
                if st == 'cancelled':
2846
                    continue
2847
                st = workflow.getInfoFor(a, 'review_state')
2848
                if st in omit:
2849
                    continue
2850
                # Can the analysis be verified?
2851
                if not a.isVerifiable(self):
2852
                    canbeverified = False
2853
                    break
2854
            return canbeverified
2855
2856 View Code Duplication
    def getObjectWorkflowStates(self):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
2857
        """
2858
        This method is used as a metacolumn.
2859
        Returns a dictionary with the workflow id as key and workflow state as
2860
        value.
2861
        :returns: {'review_state':'active',...}
2862
        """
2863
        workflow = getToolByName(self, 'portal_workflow')
2864
        states = {}
2865
        for w in workflow.getWorkflowsFor(self):
2866
            state = w._getWorkflowStateOf(self).id
2867
            states[w.state_var] = state
2868
        return states
2869
2870
    # TODO Workflow, AnalysisRequest Move to guards.verify?
2871
    def isUserAllowedToVerify(self, member):
2872
        """Checks if the specified user has enough privileges to verify the
2873
        current AR. Apart from the roles, this function also checks if the
2874
        current user has enough privileges to verify all the analyses contained
2875
        in this Analysis Request. Note that this function only returns if the
2876
        user can verify the analysis request according to his/her privileges
2877
        and the analyses contained (see isVerifiable function)
2878
2879
        :member: user to be tested
2880
        :returns: true or false
2881
        """
2882
        # Check if the user has "Bika: Verify" privileges
2883
        username = member.getUserName()
2884
        allowed = ploneapi.user.has_permission(VerifyPermission,
2885
                                               username=username)
2886
        if not allowed:
2887
            return False
2888
        # Check if the user is allowed to verify all the contained analyses
2889
        # TODO-performance: gettin all analysis each time this function is
2890
        # called
2891
        notallowed = [a for a in self.getAnalyses(full_objects=True)
2892
                      if not a.isUserAllowedToVerify(member)]
2893
        return not notallowed
2894
2895
    @security.public
2896
    def guard_to_be_preserved(self):
2897
        return guards.to_be_preserved(self)
2898
2899
    @security.public
2900
    def guard_verify_transition(self):
2901
        return guards.verify(self)
2902
2903
    @security.public
2904
    def guard_unassign_transition(self):
2905
        return guards.unassign(self)
2906
2907
    @security.public
2908
    def guard_assign_transition(self):
2909
        return guards.assign(self)
2910
2911
    @security.public
2912
    def guard_receive_transition(self):
2913
        return guards.receive(self)
2914
2915
    @security.public
2916
    def guard_schedule_sampling_transition(self):
2917
        return guards.schedule_sampling(self)
2918
2919
    @security.public
2920
    def guard_publish_transition(self):
2921
        return guards.publish(self)
2922
2923
    @security.public
2924
    def guard_prepublish_transition(self):
2925
        return guards.prepublish(self)
2926
2927
    @security.public
2928
    def workflow_script_no_sampling_workflow(self):
2929
        events.after_no_sampling_workflow(self)
2930
2931
    @security.public
2932
    def workflow_script_sampling_workflow(self):
2933
        events.after_sampling_workflow(self)
2934
2935
    @security.public
2936
    def workflow_script_sample(self):
2937
        events.after_sample(self)
2938
2939
    @security.public
2940
    def workflow_script_receive(self):
2941
        events.after_receive(self)
2942
2943
    @security.public
2944
    def workflow_script_preserve(self):
2945
        events.after_preserve(self)
2946
2947
    @security.public
2948
    def workflow_script_attach(self):
2949
        events.after_attach(self)
2950
2951
    @security.public
2952
    def workflow_script_verify(self):
2953
        events.after_verify(self)
2954
2955
    @security.public
2956
    def workflow_script_publish(self):
2957
        events.after_publish(self)
2958
2959
    @security.public
2960
    def workflow_script_reinstate(self):
2961
        events.after_reinstate(self)
2962
2963
    @security.public
2964
    def workflow_script_cancel(self):
2965
        events.after_cancel(self)
2966
2967
    @security.public
2968
    def workflow_script_schedule_sampling(self):
2969
        events.after_schedule_sampling(self)
2970
2971
    @security.public
2972
    def workflow_script_reject(self):
2973
        events.after_reject(self)
2974
2975
    @security.public
2976
    def workflow_script_retract(self):
2977
        events.after_retract(self)
2978
2979
    @security.public
2980
    def workflow_script_invalidate(self):
2981
        events.after_invalidate(self)
2982
2983
    def SearchableText(self):
2984
        """
2985
        Override searchable text logic based on the requirements.
2986
2987
        This method constructs a text blob which contains all full-text
2988
        searchable text for this content item.
2989
        https://docs.plone.org/develop/plone/searching_and_indexing/indexing.html#full-text-searching
2990
        """
2991
2992
        # Speed up string concatenation ops by using a buffer
2993
        entries = []
2994
2995
        # plain text fields we index from ourself,
2996
        # a list of accessor methods of the class
2997
        plain_text_fields = ("getId", )
2998
2999
        def read(acc):
3000
            """
3001
            Call a class accessor method to give a value for certain Archetypes
3002
            field.
3003
            """
3004
            try:
3005
                val = acc()
3006
            except Exception as e:
3007
                message = "Error getting the accessor parameter in " \
3008
                          "SearchableText from the Analysis Request Object " \
3009
                          "{}: {}".format(self.getId(), e.message)
3010
                logger.error(message)
3011
                val = ""
3012
3013
            if val is None:
3014
                val = ""
3015
3016
            return val
3017
3018
        # Concatenate plain text fields as they are
3019
        for f in plain_text_fields:
3020
            accessor = getattr(self, f)
3021
            value = read(accessor)
3022
            entries.append(value)
3023
3024
        # Adding HTML Fields to SearchableText can be uncommented if necessary
3025
        # transforms = getToolByName(self, 'portal_transforms')
3026
        #
3027
        # # Run HTML valued fields through text/plain conversion
3028
        # for f in html_fields:
3029
        #     accessor = getattr(self, f)
3030
        #     value = read(accessor)
3031
        #
3032
        #     if value != "":
3033
        #         stream = transforms.convertTo('text/plain', value,
3034
        #                                       mimetype='text/html')
3035
        #         value = stream.getData()
3036
        #
3037
        #     entries.append(value)
3038
3039
        # Plone accessor methods assume utf-8
3040
        def convertToUTF8(text):
3041
            if type(text) == unicode:
3042
                return text.encode("utf-8")
3043
            return text
3044
3045
        entries = [convertToUTF8(entry) for entry in entries]
3046
3047
        # Concatenate all strings to one text blob
3048
        return " ".join(entries)
3049
3050
    def getPriorityText(self):
3051
        """
3052
        This function looks up the priority text from priorities vocab
3053
        :returns: the priority text or ''
3054
        """
3055
        if self.getPriority():
3056
            return PRIORITIES.getValue(self.getPriority())
3057
        return ''
3058
3059
    def get_ARAttachment(self):
3060
        logger.warn("_ARAttachment is a virtual field used in AR Add. "
3061
                    "It can not hold an own value!")
3062
        return None
3063
3064
    def set_ARAttachment(self, value):
3065
        logger.warn("_ARAttachment is a virtual field used in AR Add. "
3066
                    "It can not hold an own value!")
3067
        return None
3068
3069
    def get_retest(self):
3070
        """Returns the Analysis Request automatically generated because of the
3071
        retraction of the current analysis request
3072
        """
3073
        relationship = "AnalysisRequestRetracted"
3074
        retest = self.getBackReferences(relationship=relationship)
3075
        if retest and len(retest) > 1:
3076
            logger.warn("More than one retest for {0}".format(self.getId()))
3077
        return retest and retest[0] or None
3078
3079
registerType(AnalysisRequest, PROJECTNAME)
3080