Completed
Push — master ( 8d2e48...36dfc2 )
by Ramon
07:01 queued 02:36
created

AnalysisRequest.get_retest()   A

Complexity

Conditions 3

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 6
dl 0
loc 9
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 any analyses are late
1989
        """
1990
        workflow = getToolByName(self, 'portal_workflow')
1991
        review_state = workflow.getInfoFor(self, 'review_state', '')
1992
        resultdate = 0
1993
        if review_state in ['to_be_sampled', 'to_be_preserved',
1994
                            'sample_due', 'published']:
1995
            return False
1996
1997
        for analysis in self.objectValues('Analysis'):
1998
            review_state = workflow.getInfoFor(analysis, 'review_state', '')
1999
            if review_state == 'published':
2000
                continue
2001
            # This situation can be met during analysis request creation
2002
            calculation = analysis.getCalculation()
2003
            if not calculation or (
2004
                    calculation and not calculation.getDependentServices()):
2005
                resultdate = analysis.getResultCaptureDate()
2006
            duedate = analysis.getDueDate()
2007
            # noinspection PyCallingNonCallable
2008
            if (resultdate and resultdate > duedate) \
2009
                    or (not resultdate and DateTime() > duedate):
2010
                return True
2011
        return False
2012
2013
    def getPrinted(self):
2014
        """ returns "0", "1" or "2" to indicate Printed state.
2015
            0 -> Never printed.
2016
            1 -> Printed after last publish
2017
            2 -> Printed but republished afterwards.
2018
        """
2019
        workflow = getToolByName(self, 'portal_workflow')
2020
        review_state = workflow.getInfoFor(self, 'review_state', '')
2021
        if review_state not in ['published']:
2022
            return "0"
2023
        report_list = sorted(self.objectValues('ARReport'),
2024
                             key=lambda report: report.getDatePublished())
2025
        if not report_list:
2026
            return "0"
2027
        last_report = report_list[-1]
2028
        if last_report.getDatePrinted():
2029
            return "1"
2030
        else:
2031
            for report in report_list:
2032
                if report.getDatePrinted():
2033
                    return "2"
2034
        return "0"
2035
2036
    def printLastReport(self):
2037
        """Setting Printed Time of the last report, so its Printed value will be 1
2038
        """
2039
        workflow = getToolByName(self, 'portal_workflow')
2040
        review_state = workflow.getInfoFor(self, 'review_state', '')
2041
        if review_state not in ['published']:
2042
            return
2043
        last_report = sorted(self.objectValues('ARReport'),
2044
                             key=lambda report: report.getDatePublished())[-1]
2045
        if last_report and not last_report.getDatePrinted():
2046
            last_report.setDatePrinted(DateTime())
2047
            self.reindexObject(idxs=['getPrinted'])
2048
2049
    security.declareProtected(View, 'getBillableItems')
2050
2051
    def getBillableItems(self):
2052
        """The main purpose of this function is to obtain the analysis services
2053
        and profiles from the analysis request
2054
        whose prices are needed to quote the analysis request.
2055
        If an analysis belongs to a profile, this analysis will only be
2056
        included in the analyses list if the profile
2057
        has disabled "Use Analysis Profile Price".
2058
2059
        :returns: a tuple of two lists. The first one only contains analysis
2060
                  services not belonging to a profile with active "Use Analysis
2061
                  Profile Price". The second list contains the profiles with
2062
                  activated "Use Analysis Profile Price".
2063
        """
2064
        workflow = getToolByName(self, 'portal_workflow')
2065
        # REMEMBER: Analysis != Analysis services
2066
        analyses = []
2067
        analysis_profiles = []
2068
        to_be_billed = []
2069
        # Getting all analysis request analyses
2070
        # Getting all analysis request analyses
2071
        ar_analyses = self.getAnalyses(cancellation_state='active',
2072
                                       full_objects=True)
2073
        for analysis in ar_analyses:
2074
            review_state = workflow.getInfoFor(analysis, 'review_state', '')
2075
            if review_state not in ('not_requested', 'retracted'):
2076
                analyses.append(analysis)
2077
        # Getting analysis request profiles
2078
        for profile in self.getProfiles():
2079
            # Getting the analysis profiles which has "Use Analysis Profile
2080
            # Price" enabled
2081
            if profile.getUseAnalysisProfilePrice():
2082
                analysis_profiles.append(profile)
2083
            else:
2084
                # we only need the analysis service keywords from these
2085
                # profiles
2086
                to_be_billed += [service.getKeyword() for service in
2087
                                 profile.getService()]
2088
        # So far we have three arrays:
2089
        #   - analyses: has all analyses (even if they are included inside a
2090
        # profile or not)
2091
        #   - analysis_profiles: has the profiles with "Use Analysis Profile
2092
        # Price" enabled
2093
        #   - to_be_quoted: has analysis services keywords from analysis
2094
        # profiles with "Use Analysis Profile Price"
2095
        #     disabled
2096
        # If a profile has its own price, we don't need their analises'
2097
        # prices, so we have to quit all
2098
        # analysis belonging to that profile. But if another profile has the
2099
        # same analysis service but has
2100
        # "Use Analysis Profile Price" disabled, the service must be included
2101
        #  as billable.
2102
        for profile in analysis_profiles:
2103
            for analysis_service in profile.getService():
2104
                for analysis in analyses:
2105
                    if analysis_service.getKeyword() == analysis.getKeyword() \
2106
                            and analysis.getKeyword() not in to_be_billed:
2107
                        analyses.remove(analysis)
2108
        return analyses, analysis_profiles
2109
2110
    def getServicesAndProfiles(self):
2111
        """This function gets all analysis services and all profiles and removes
2112
        the services belonging to a profile.
2113
2114
        :returns: a tuple of three lists, where the first list contains the
2115
        analyses and the second list the profiles.
2116
        The third contains the analyses objects used by the profiles.
2117
        """
2118
        # Getting requested analyses
2119
        workflow = getToolByName(self, 'portal_workflow')
2120
        analyses = []
2121
        # profile_analyses contains the profile's analyses (analysis !=
2122
        # service") objects to obtain
2123
        # the correct price later
2124
        profile_analyses = []
2125
        for analysis in self.objectValues('Analysis'):
2126
            review_state = workflow.getInfoFor(analysis, 'review_state', '')
2127
            if review_state != 'not_requested':
2128
                analyses.append(analysis)
2129
        # Getting all profiles
2130
        analysis_profiles = self.getProfiles() if len(
2131
            self.getProfiles()) > 0 else []
2132
        # Cleaning services included in profiles
2133
        for profile in analysis_profiles:
2134
            for analysis_service in profile.getService():
2135
                for analysis in analyses:
2136
                    if analysis_service.getKeyword() == analysis.getKeyword():
2137
                        analyses.remove(analysis)
2138
                        profile_analyses.append(analysis)
2139
        return analyses, analysis_profiles, profile_analyses
2140
2141
    security.declareProtected(View, 'getSubtotal')
2142
2143
    def getSubtotal(self):
2144
        """Compute Subtotal (without member discount and without vat)
2145
        """
2146
        analyses, a_profiles = self.getBillableItems()
2147
        return sum(
2148
            [Decimal(obj.getPrice()) for obj in analyses] +
2149
            [Decimal(obj.getAnalysisProfilePrice()) for obj in a_profiles]
2150
        )
2151
2152
    security.declareProtected(View, 'getSubtotalVATAmount')
2153
2154
    def getSubtotalVATAmount(self):
2155
        """Compute VAT amount without member discount
2156
        """
2157
        analyses, a_profiles = self.getBillableItems()
2158
        if len(analyses) > 0 or len(a_profiles) > 0:
2159
            return sum(
2160
                [Decimal(o.getVATAmount()) for o in analyses] +
2161
                [Decimal(o.getVATAmount()) for o in a_profiles]
2162
            )
2163
        return 0
2164
2165
    security.declareProtected(View, 'getSubtotalTotalPrice')
2166
2167
    def getSubtotalTotalPrice(self):
2168
        """Compute the price with VAT but no member discount
2169
        """
2170
        return self.getSubtotal() + self.getSubtotalVATAmount()
2171
2172
    security.declareProtected(View, 'getDiscountAmount')
2173
2174
    def getDiscountAmount(self):
2175
        """It computes and returns the analysis service's discount amount
2176
        without VAT
2177
        """
2178
        has_client_discount = self.aq_parent.getMemberDiscountApplies()
2179
        if has_client_discount:
2180
            discount = Decimal(self.getDefaultMemberDiscount())
2181
            return Decimal(self.getSubtotal() * discount / 100)
2182
        else:
2183
            return 0
2184
2185
    def getVATAmount(self):
2186
        """It computes the VAT amount from (subtotal-discount.)*VAT/100, but
2187
        each analysis has its own VAT!
2188
2189
        :returns: the analysis request VAT amount with the discount
2190
        """
2191
        has_client_discount = self.aq_parent.getMemberDiscountApplies()
2192
        VATAmount = self.getSubtotalVATAmount()
2193
        if has_client_discount:
2194
            discount = Decimal(self.getDefaultMemberDiscount())
2195
            return Decimal((1 - discount / 100) * VATAmount)
2196
        else:
2197
            return VATAmount
2198
2199
    security.declareProtected(View, 'getTotalPrice')
2200
2201
    def getTotalPrice(self):
2202
        """It gets the discounted price from analyses and profiles to obtain the
2203
        total value with the VAT and the discount applied
2204
2205
        :returns: analysis request's total price including VATs and discounts
2206
        """
2207
        price = (self.getSubtotal() - self.getDiscountAmount() +
2208
                 self.getVATAmount())
2209
        return price
2210
2211
    getTotal = getTotalPrice
2212
2213
    security.declareProtected(ManageInvoices, 'issueInvoice')
2214
2215
    # noinspection PyUnusedLocal
2216
    def issueInvoice(self, REQUEST=None, RESPONSE=None):
2217
        """Issue invoice
2218
        """
2219
        # check for an adhoc invoice batch for this month
2220
        # noinspection PyCallingNonCallable
2221
        now = DateTime()
2222
        batch_month = now.strftime('%b %Y')
2223
        batch_title = '%s - %s' % (batch_month, 'ad hoc')
2224
        invoice_batch = None
2225
        for b_proxy in self.portal_catalog(portal_type='InvoiceBatch',
2226
                                           Title=batch_title):
2227
            invoice_batch = b_proxy.getObject()
2228
        if not invoice_batch:
2229
            # noinspection PyCallingNonCallable
2230
            first_day = DateTime(now.year(), now.month(), 1)
2231
            start_of_month = first_day.earliestTime()
2232
            last_day = first_day + 31
2233
            # noinspection PyUnresolvedReferences
2234
            while last_day.month() != now.month():
2235
                last_day -= 1
2236
            # noinspection PyUnresolvedReferences
2237
            end_of_month = last_day.latestTime()
2238
2239
            invoices = self.invoices
2240
            batch_id = invoices.generateUniqueId('InvoiceBatch')
2241
            invoice_batch = _createObjectByType("InvoiceBatch", invoices,
2242
                                                batch_id)
2243
            invoice_batch.edit(
2244
                title=batch_title,
2245
                BatchStartDate=start_of_month,
2246
                BatchEndDate=end_of_month,
2247
            )
2248
            invoice_batch.processForm()
2249
2250
        client_uid = self.getClientUID()
2251
        # Get the created invoice
2252
        invoice = invoice_batch.createInvoice(client_uid, [self, ])
2253
        invoice.setAnalysisRequest(self)
2254
        # Set the created invoice in the schema
2255
        self.Schema()['Invoice'].set(self, invoice)
2256
2257
    security.declarePublic('printInvoice')
2258
2259
    # noinspection PyUnusedLocal
2260
    def printInvoice(self, REQUEST=None, RESPONSE=None):
2261
        """Print invoice
2262
        """
2263
        invoice = self.getInvoice()
2264
        invoice_url = invoice.absolute_url()
2265
        RESPONSE.redirect('{}/invoice_print'.format(invoice_url))
2266
2267
    @deprecated("addARAttachment will be removed in senaite.core 1.3.0")
2268
    def addARAttachment(self, REQUEST=None, RESPONSE=None):
2269
        """Add the file as an attachment
2270
        """
2271
        workflow = getToolByName(self, 'portal_workflow')
2272
2273
        this_file = self.REQUEST.form['AttachmentFile_file']
2274
        if 'Analysis' in self.REQUEST.form:
2275
            analysis_uid = self.REQUEST.form['Analysis']
2276
        else:
2277
            analysis_uid = None
2278
2279
        attachmentid = self.generateUniqueId('Attachment')
2280
        attachment = _createObjectByType("Attachment", self.aq_parent,
2281
                                         attachmentid)
2282
        attachment.edit(
2283
            AttachmentFile=this_file,
2284
            AttachmentType=self.REQUEST.form.get('AttachmentType', ''),
2285
            AttachmentKeys=self.REQUEST.form['AttachmentKeys'])
2286
        attachment.processForm()
2287
        attachment.reindexObject()
2288
2289
        if analysis_uid:
2290
            tool = getToolByName(self, REFERENCE_CATALOG)
2291
            analysis = tool.lookupObject(analysis_uid)
2292
            others = analysis.getAttachment()
2293
            attachments = []
2294
            for other in others:
2295
                attachments.append(other.UID())
2296
            attachments.append(attachment.UID())
2297
            analysis.setAttachment(attachments)
2298
            if workflow.getInfoFor(analysis,
2299
                                   'review_state') == 'attachment_due':
2300
                workflow.doActionFor(analysis, 'attach')
2301
        else:
2302
            others = self.getAttachment()
2303
            attachments = []
2304
            for other in others:
2305
                attachments.append(other.UID())
2306
            attachments.append(attachment.UID())
2307
2308
            self.setAttachment(attachments)
2309
2310
        if REQUEST['HTTP_REFERER'].endswith('manage_results'):
2311
            RESPONSE.redirect('{}/manage_results'.format(self.absolute_url()))
2312
        else:
2313
            RESPONSE.redirect(self.absolute_url())
2314
2315
    @deprecated("delARAttachment will be removed in senaite.core 1.3.0")
2316
    def delARAttachment(self, REQUEST=None, RESPONSE=None):
2317
        """Delete the attachment
2318
        """
2319
        tool = getToolByName(self, REFERENCE_CATALOG)
2320
        if 'Attachment' in self.REQUEST.form:
2321
            attachment_uid = self.REQUEST.form['Attachment']
2322
            attachment = tool.lookupObject(attachment_uid)
2323
            parent_r = attachment.getRequest()
2324
            parent_a = attachment.getAnalysis()
2325
2326
            parent = parent_a if parent_a else parent_r
2327
            others = parent.getAttachment()
2328
            attachments = []
2329
            for other in others:
2330
                if not other.UID() == attachment_uid:
2331
                    attachments.append(other.UID())
2332
            parent.setAttachment(attachments)
2333
            client = attachment.aq_parent
2334
            ids = [attachment.getId(), ]
2335
            BaseFolder.manage_delObjects(client, ids, REQUEST)
2336
2337
        RESPONSE.redirect(self.REQUEST.get_header('referer'))
2338
2339
    security.declarePublic('getVerifier')
2340
2341
    @deprecated("Use getVerifiers instead")
2342
    def getVerifier(self):
2343
        """Returns the user that verified the whole Analysis Request. Since the
2344
        verification is done automatically as soon as all the analyses it
2345
        contains are verified, this function returns the user that verified the
2346
        last analysis pending.
2347
        """
2348
        wtool = getToolByName(self, 'portal_workflow')
2349
        mtool = getToolByName(self, 'portal_membership')
2350
2351
        verifier = None
2352
        # noinspection PyBroadException
2353
        try:
2354
            review_history = wtool.getInfoFor(self, 'review_history')
2355
        except:  # noqa FIXME: remove blind except!
2356
            return 'access denied'
2357
2358
        if not review_history:
2359
            return 'no history'
2360
        for items in review_history:
2361
            action = items.get('action')
2362
            if action != 'verify':
2363
                continue
2364
            actor = items.get('actor')
2365
            member = mtool.getMemberById(actor)
2366
            verifier = member.getProperty('fullname')
2367
            if verifier is None or verifier == '':
2368
                verifier = actor
2369
        return verifier
2370
2371
    @security.public
2372
    def getVerifiersIDs(self):
2373
        """Returns the ids from users that have verified at least one analysis
2374
        from this Analysis Request
2375
        """
2376
        verifiers_ids = list()
2377
        for brain in self.getAnalyses():
2378
            verifiers = brain.getVerificators or ""
2379
            verifiers_ids += verifiers.split(",")
2380
        return list(set(verifiers_ids))
2381
2382
    @security.public
2383
    def getVerifiers(self):
2384
        """Returns the list of lab contacts that have verified at least one
2385
        analysis from this Analysis Request
2386
        """
2387
        contacts = list()
2388
        for verifier in self.getVerifiersIDs():
2389
            user = api.get_user(verifier)
2390
            contact = api.get_user_contact(user, ["LabContact"])
2391
            if contact:
2392
                contacts.append(contact)
2393
        return contacts
2394
2395
    security.declarePublic('getContactUIDForUser')
2396
2397
    def getContactUIDForUser(self):
2398
        """get the UID of the contact associated with the authenticated user
2399
        """
2400
        mt = getToolByName(self, 'portal_membership')
2401
        user = mt.getAuthenticatedMember()
2402
        user_id = user.getUserName()
2403
        pc = getToolByName(self, 'portal_catalog')
2404
        r = pc(portal_type='Contact',
2405
               getUsername=user_id)
2406
        if len(r) == 1:
2407
            return r[0].UID
2408
2409
    security.declarePublic('current_date')
2410
2411
    def current_date(self):
2412
        """return current date
2413
        """
2414
        # noinspection PyCallingNonCallable
2415
        return DateTime()
2416
2417
    def getQCAnalyses(self, qctype=None, review_state=None):
2418
        """return the QC analyses performed in the worksheet in which, at
2419
        least, one sample of this AR is present.
2420
2421
        Depending on qctype value, returns the analyses of:
2422
2423
            - 'b': all Blank Reference Samples used in related worksheet/s
2424
            - 'c': all Control Reference Samples used in related worksheet/s
2425
            - 'd': duplicates only for samples contained in this AR
2426
2427
        If qctype==None, returns all type of qc analyses mentioned above
2428
        """
2429
        qcanalyses = []
2430
        suids = []
2431
        ans = self.getAnalyses()
2432
        wf = getToolByName(self, 'portal_workflow')
2433
        for an in ans:
2434
            an = an.getObject()
2435
            if an.getServiceUID() not in suids:
2436
                suids.append(an.getServiceUID())
2437
2438
        def valid_dup(wan):
2439
            if wan.portal_type == 'ReferenceAnalysis':
2440
                return False
2441
            an_state = wf.getInfoFor(wan, 'review_state')
2442
            return \
2443
                wan.portal_type == 'DuplicateAnalysis' \
2444
                and wan.getRequestID() == self.id \
2445
                and (review_state is None or an_state in review_state)
2446
2447
        def valid_ref(wan):
2448
            if wan.portal_type != 'ReferenceAnalysis':
2449
                return False
2450
            an_state = wf.getInfoFor(wan, 'review_state')
2451
            an_reftype = wan.getReferenceType()
2452
            return wan.getServiceUID() in suids \
2453
                and wan not in qcanalyses \
2454
                and (qctype is None or an_reftype == qctype) \
2455
                and (review_state is None or an_state in review_state)
2456
2457
        for an in ans:
2458
            an = an.getObject()
2459
            br = an.getBackReferences('WorksheetAnalysis')
2460
            if len(br) > 0:
2461
                ws = br[0]
2462
                was = ws.getAnalyses()
2463
                for wa in was:
2464
                    if valid_dup(wa):
2465
                        qcanalyses.append(wa)
2466
                    elif valid_ref(wa):
2467
                        qcanalyses.append(wa)
2468
2469
        return qcanalyses
2470
2471
    def isInvalid(self):
2472
        """return if the Analysis Request has been invalidated
2473
        """
2474
        workflow = getToolByName(self, 'portal_workflow')
2475
        return workflow.getInfoFor(self, 'review_state') == 'invalid'
2476
2477
    def getRequestedAnalyses(self):
2478
        """It returns all requested analyses, even if they belong to an
2479
        analysis profile or not.
2480
        """
2481
        #
2482
        # title=Get requested analyses
2483
        #
2484
        result = []
2485
        cats = {}
2486
        workflow = getToolByName(self, 'portal_workflow')
2487
        for analysis in self.getAnalyses(full_objects=True):
2488
            review_state = workflow.getInfoFor(analysis, 'review_state')
2489
            if review_state == 'not_requested':
2490
                continue
2491
            category_name = analysis.getCategoryTitle()
2492
            if category_name not in cats:
2493
                cats[category_name] = {}
2494
            cats[category_name][analysis.Title()] = analysis
2495
        cat_keys = sorted(cats.keys(), key=methodcaller('lower'))
2496
        for cat_key in cat_keys:
2497
            analyses = cats[cat_key]
2498
            analysis_keys = sorted(analyses.keys(),
2499
                                   key=methodcaller('lower'))
2500
            for analysis_key in analysis_keys:
2501
                result.append(analyses[analysis_key])
2502
        return result
2503
2504
    def getSamplingRoundUID(self):
2505
        """Obtains the sampling round UID
2506
        :returns: UID
2507
        """
2508
        sr = self.getSamplingRound()
2509
        if sr:
2510
            return sr.UID()
2511
        else:
2512
            return ''
2513
2514
    def getStorageLocationTitle(self):
2515
        """ A method for AR listing catalog metadata
2516
        :return: Title of Storage Location
2517
        """
2518
        sl = self.getStorageLocation()
2519
        if sl:
2520
            return sl.Title()
2521
        return ''
2522
2523
    @security.public
2524
    def getResultsRange(self):
2525
        """Returns the valid result ranges for the analyses this Analysis
2526
        Request contains.
2527
2528
        By default uses the result ranges defined in the Analysis Specification
2529
        set in "Specification" field if any. Values manually set through
2530
        `ResultsRange` field for any given analysis keyword have priority over
2531
        the result ranges defined in "Specification" field.
2532
2533
        :return: A list of dictionaries, where each dictionary defines the
2534
            result range to use for any analysis contained in this Analysis
2535
            Request for the keyword specified. Each dictionary has, at least,
2536
                the following keys: "keyword", "min", "max"
2537
        :rtype: dict
2538
        """
2539
        specs_range = []
2540
        specification = self.getSpecification()
2541
        if specification:
2542
            specs_range = specification.getResultsRange()
2543
            specs_range = specs_range and specs_range or []
2544
2545
        # Override with AR's custom ranges
2546
        ar_range = self.Schema().getField("ResultsRange").get(self)
2547
        if not ar_range:
2548
            return specs_range
2549
2550
        # Remove those analysis ranges that neither min nor max are floatable
2551
        an_specs = [an for an in ar_range if
2552
                    api.is_floatable(an.get('min', None)) or
2553
                    api.is_floatable(an.get('max', None))]
2554
        # Want to know which are the analyses that needs to be overriden
2555
        keywords = map(lambda item: item.get('keyword'), an_specs)
2556
        # Get rid of those analyses to be overriden
2557
        out_specs = [sp for sp in specs_range if sp['keyword'] not in keywords]
2558
        # Add manually set ranges
2559
        out_specs.extend(an_specs)
2560
        return map(lambda spec: ResultsRangeDict(spec), out_specs)
2561
2562
    def getDatePublished(self):
2563
        """
2564
        Returns the transition date from the Analysis Request object
2565
        """
2566
        return getTransitionDate(self, 'publish', return_as_datetime=True)
2567
2568
    security.declarePublic('getSamplingDeviationTitle')
2569
2570
    def getSamplingDeviationTitle(self):
2571
        """
2572
        It works as a metacolumn.
2573
        """
2574
        sd = self.getSamplingDeviation()
2575
        if sd:
2576
            return sd.Title()
2577
        return ''
2578
2579
    security.declarePublic('getHazardous')
2580
2581
    def getHazardous(self):
2582
        """
2583
        It works as a metacolumn.
2584
        """
2585
        sample_type = self.getSampleType()
2586
        if sample_type:
2587
            return sample_type.getHazardous()
2588
        return False
2589
2590
    security.declarePublic('getContactURL')
2591
2592
    def getContactURL(self):
2593
        """
2594
        It works as a metacolumn.
2595
        """
2596
        contact = self.getContact()
2597
        if contact:
2598
            return contact.absolute_url_path()
2599
        return ''
2600
2601
    security.declarePublic('getSamplingWorkflowEnabled')
2602
2603
    def getSamplingWorkflowEnabled(self):
2604
        """
2605
        It works as a metacolumn.
2606
        """
2607
        sample = self.getSample()
2608
        if sample:
2609
            return sample.getSamplingWorkflowEnabled()
2610
        return ''
2611
2612
    def getSamplers(self):
2613
        return getUsers(self, ['Sampler', ])
2614
2615
    def getDepartments(self):
2616
        """Returns a list of the departments assigned to the Analyses
2617
        from this Analysis Request
2618
        """
2619
        bsc = getToolByName(self, 'bika_setup_catalog')
2620
        dept_uids = []
2621
        for an in self.getAnalyses():
2622
            deptuid = None
2623
            if hasattr(an, 'getDepartmentUID'):
2624
                deptuid = an.getDepartmentUID
2625
            if not deptuid and hasattr(an, 'getObject'):
2626
                deptuid = an.getObject().getDepartmentUID()
2627
            if deptuid:
2628
                dept_uids.append(deptuid)
2629
        brains = bsc(portal_type='Department', UID=dept_uids)
2630
        depts = [b.getObject() for b in brains]
2631
        return list(set(depts))
2632
2633
    def getDepartmentUIDs(self):
2634
        """Return a list of the UIDs of departments assigned to the Analyses
2635
        from this Analysis Request.
2636
        """
2637
        return [dept.UID() for dept in self.getDepartments()]
2638
2639
    def getResultsInterpretationByDepartment(self, department=None):
2640
        """Returns the results interpretation for this Analysis Request
2641
           and department. If department not set, returns the results
2642
           interpretation tagged as 'General'.
2643
2644
        :returns: a dict with the following keys:
2645
            {'uid': <department_uid> or 'general', 'richtext': <text/plain>}
2646
        """
2647
        uid = department.UID() if department else 'general'
2648
        rows = self.Schema()['ResultsInterpretationDepts'].get(self)
2649
        row = [row for row in rows if row.get('uid') == uid]
2650
        if len(row) > 0:
2651
            row = row[0]
2652
        elif uid == 'general' \
2653
                and hasattr(self, 'getResultsInterpretation') \
2654
                and self.getResultsInterpretation():
2655
            row = {'uid': uid, 'richtext': self.getResultsInterpretation()}
2656
        else:
2657
            row = {'uid': uid, 'richtext': ''}
2658
        return row
2659
2660
    def getAnalysisServiceSettings(self, uid):
2661
        """Returns a dictionary with the settings for the analysis service that
2662
        match with the uid provided.
2663
2664
        If there are no settings for the analysis service and
2665
        analysis requests:
2666
2667
        1. looks for settings in AR's ARTemplate. If found, returns the
2668
           settings for the AnalysisService set in the Template
2669
        2. If no settings found, looks in AR's ARProfile. If found, returns the
2670
           settings for the AnalysisService from the AR Profile. Otherwise,
2671
           returns a one entry dictionary with only the key 'uid'
2672
        """
2673
        sets = [s for s in self.getAnalysisServicesSettings()
2674
                if s.get('uid', '') == uid]
2675
2676
        # Created by using an ARTemplate?
2677
        if not sets and self.getTemplate():
2678
            adv = self.getTemplate().getAnalysisServiceSettings(uid)
2679
            sets = [adv] if 'hidden' in adv else []
2680
2681
        # Created by using an AR Profile?
2682
        if not sets and self.getProfiles():
2683
            adv = []
2684
            adv += [profile.getAnalysisServiceSettings(uid) for profile in
2685
                    self.getProfiles()]
2686
            sets = adv if 'hidden' in adv[0] else []
2687
2688
        return sets[0] if sets else {'uid': uid}
2689
2690
    def getPartitions(self):
2691
        """This functions returns the partitions from the analysis request's
2692
        analyses.
2693
2694
        :returns: a list with the full partition objects
2695
        """
2696
        analyses = self.getRequestedAnalyses()
2697
        partitions = []
2698
        for analysis in analyses:
2699
            if analysis.getSamplePartition() not in partitions:
2700
                partitions.append(analysis.getSamplePartition())
2701
        return partitions
2702
2703
    def getContainers(self):
2704
        """This functions returns the containers from the analysis request's
2705
        analyses
2706
2707
        :returns: a list with the full partition objects
2708
        """
2709
        partitions = self.getPartitions()
2710
        containers = []
2711
        for partition in partitions:
2712
            if partition.getContainer():
2713
                containers.append(partition.getContainer())
2714
        return containers
2715
2716 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...
2717
        """Checks if the analysis service that match with the uid provided must
2718
        be hidden in results. If no hidden assignment has been set for the
2719
        analysis in this request, returns the visibility set to the analysis
2720
        itself.
2721
2722
        Raise a TypeError if the uid is empty or None
2723
2724
        Raise a ValueError if there is no hidden assignment in this request or
2725
        no analysis service found for this uid.
2726
        """
2727
        if not uid:
2728
            raise TypeError('None type or empty uid')
2729
        sets = self.getAnalysisServiceSettings(uid)
2730
        if 'hidden' not in sets:
2731
            uc = getToolByName(self, 'uid_catalog')
2732
            serv = uc(UID=uid)
2733
            if serv and len(serv) == 1:
2734
                return serv[0].getObject().getRawHidden()
2735
            else:
2736
                raise ValueError('{} is not valid'.format(uid))
2737
        return sets.get('hidden', False)
2738
2739
    def getRejecter(self):
2740
        """If the Analysis Request has been rejected, returns the user who did the
2741
        rejection. If it was not rejected or the current user has not enough
2742
        privileges to access to this information, returns None.
2743
        """
2744
        wtool = getToolByName(self, 'portal_workflow')
2745
        mtool = getToolByName(self, 'portal_membership')
2746
        # noinspection PyBroadException
2747
        try:
2748
            review_history = wtool.getInfoFor(self, 'review_history')
2749
        except:  # noqa FIXME: remove blind except!
2750
            return None
2751
        for items in review_history:
2752
            action = items.get('action')
2753
            if action != 'reject':
2754
                continue
2755
            actor = items.get('actor')
2756
            return mtool.getMemberById(actor)
2757
        return None
2758
2759
    def getReceivedBy(self):
2760
        """
2761
        Returns the User who received the analysis request.
2762
        :returns: the user id
2763
        """
2764
        user = getTransitionUsers(self, 'receive', last_user=True)
2765
        return user[0] if user else ''
2766
2767
    def getDateVerified(self):
2768
        """
2769
        Returns the date of verification as a DateTime object.
2770
        """
2771
        return getTransitionDate(self, 'verify', return_as_datetime=True)
2772
2773
    @security.public
2774
    def getPrioritySortkey(self):
2775
        """Returns the key that will be used to sort the current Analysis
2776
        Request based on both its priority and creation date. On ASC sorting,
2777
        the oldest item with highest priority will be displayed.
2778
        :return: string used for sorting
2779
        """
2780
        priority = self.getPriority()
2781
        created_date = self.created().ISO8601()
2782
        return '%s.%s' % (priority, created_date)
2783
2784
    @security.public
2785
    def setPriority(self, value):
2786
        if not value:
2787
            value = self.Schema().getField('Priority').getDefault(self)
2788
        original_value = self.Schema().getField('Priority').get(self)
2789
        if original_value != value:
2790
            self.Schema().getField('Priority').set(self, value)
2791
            self._reindexAnalyses(['getPrioritySortkey'], True)
2792
2793
    @security.private
2794
    def _reindexAnalyses(self, idxs=None, update_metadata=False):
2795
        if not idxs and not update_metadata:
2796
            return
2797
        if not idxs:
2798
            idxs = []
2799
        analyses = self.getAnalyses()
2800
        catalog = getToolByName(self, CATALOG_ANALYSIS_LISTING)
2801
        for analysis in analyses:
2802
            analysis_obj = analysis.getObject()
2803
            catalog.reindexObject(analysis_obj, idxs=idxs, update_metadata=1)
2804
2805
    def _getCreatorFullName(self):
2806
        """
2807
        Returns the full name of this analysis request's creator.
2808
        """
2809
        return user_fullname(self, self.Creator())
2810
2811
    def _getCreatorEmail(self):
2812
        """
2813
        Returns the email of this analysis request's creator.
2814
        """
2815
        return user_email(self, self.Creator())
2816
2817
    def _getSamplerFullName(self):
2818
        """
2819
        Returns the full name's defined sampler.
2820
        """
2821
        return user_fullname(self, self.getSampler())
2822
2823
    def _getSamplerEmail(self):
2824
        """
2825
        Returns the email of this analysis request's sampler.
2826
        """
2827
        return user_email(self, self.Creator())
2828
2829
    # TODO Workflow, AnalysisRequest Move to guards.verify?
2830 View Code Duplication
    def isVerifiable(self):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
2831
        """Checks it the current Analysis Request can be verified. This is, its
2832
        not a cancelled Analysis Request and all the analyses that contains
2833
        are verifiable too. Note that verifying an Analysis Request is in fact,
2834
        the same as verifying all the analyses that contains. Therefore, the
2835
        'verified' state of an Analysis Request shouldn't be a 'real' state,
2836
        rather a kind-of computed state, based on the statuses of the analyses
2837
        it contains. This is why this function checks if the analyses
2838
        contained are verifiable, cause otherwise, the Analysis Request will
2839
        never be able to reach a 'verified' state.
2840
        :returns: True or False
2841
        """
2842
        # Check if the analysis request is active
2843
        workflow = getToolByName(self, "portal_workflow")
2844
        objstate = workflow.getInfoFor(self, 'cancellation_state', 'active')
2845
        if objstate == "cancelled":
2846
            return False
2847
2848
        # Check if the analysis request state is to_be_verified
2849
        review_state = workflow.getInfoFor(self, "review_state")
2850
        if review_state == 'to_be_verified':
2851
            # This means that all the analyses from this analysis request have
2852
            # already been transitioned to a 'verified' state, and so the
2853
            # analysis request itself
2854
            return True
2855
        else:
2856
            # Check if the analyses contained in this analysis request are
2857
            # verifiable. Only check those analyses not cancelled and that
2858
            # are not in a kind-of already verified state
2859
            canbeverified = True
2860
            omit = ['published', 'retracted', 'rejected', 'verified']
2861
            for a in self.getAnalyses(full_objects=True):
2862
                st = workflow.getInfoFor(a, 'cancellation_state', 'active')
2863
                if st == 'cancelled':
2864
                    continue
2865
                st = workflow.getInfoFor(a, 'review_state')
2866
                if st in omit:
2867
                    continue
2868
                # Can the analysis be verified?
2869
                if not a.isVerifiable(self):
2870
                    canbeverified = False
2871
                    break
2872
            return canbeverified
2873
2874 View Code Duplication
    def getObjectWorkflowStates(self):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
2875
        """
2876
        This method is used as a metacolumn.
2877
        Returns a dictionary with the workflow id as key and workflow state as
2878
        value.
2879
        :returns: {'review_state':'active',...}
2880
        """
2881
        workflow = getToolByName(self, 'portal_workflow')
2882
        states = {}
2883
        for w in workflow.getWorkflowsFor(self):
2884
            state = w._getWorkflowStateOf(self).id
2885
            states[w.state_var] = state
2886
        return states
2887
2888
    # TODO Workflow, AnalysisRequest Move to guards.verify?
2889
    def isUserAllowedToVerify(self, member):
2890
        """Checks if the specified user has enough privileges to verify the
2891
        current AR. Apart from the roles, this function also checks if the
2892
        current user has enough privileges to verify all the analyses contained
2893
        in this Analysis Request. Note that this function only returns if the
2894
        user can verify the analysis request according to his/her privileges
2895
        and the analyses contained (see isVerifiable function)
2896
2897
        :member: user to be tested
2898
        :returns: true or false
2899
        """
2900
        # Check if the user has "Bika: Verify" privileges
2901
        username = member.getUserName()
2902
        allowed = ploneapi.user.has_permission(VerifyPermission,
2903
                                               username=username)
2904
        if not allowed:
2905
            return False
2906
        # Check if the user is allowed to verify all the contained analyses
2907
        # TODO-performance: gettin all analysis each time this function is
2908
        # called
2909
        notallowed = [a for a in self.getAnalyses(full_objects=True)
2910
                      if not a.isUserAllowedToVerify(member)]
2911
        return not notallowed
2912
2913
    @security.public
2914
    def guard_to_be_preserved(self):
2915
        return guards.to_be_preserved(self)
2916
2917
    @security.public
2918
    def guard_verify_transition(self):
2919
        return guards.verify(self)
2920
2921
    @security.public
2922
    def guard_unassign_transition(self):
2923
        return guards.unassign(self)
2924
2925
    @security.public
2926
    def guard_assign_transition(self):
2927
        return guards.assign(self)
2928
2929
    @security.public
2930
    def guard_receive_transition(self):
2931
        return guards.receive(self)
2932
2933
    @security.public
2934
    def guard_schedule_sampling_transition(self):
2935
        return guards.schedule_sampling(self)
2936
2937
    @security.public
2938
    def guard_publish_transition(self):
2939
        return guards.publish(self)
2940
2941
    @security.public
2942
    def guard_prepublish_transition(self):
2943
        return guards.prepublish(self)
2944
2945
    @security.public
2946
    def workflow_script_no_sampling_workflow(self):
2947
        events.after_no_sampling_workflow(self)
2948
2949
    @security.public
2950
    def workflow_script_sampling_workflow(self):
2951
        events.after_sampling_workflow(self)
2952
2953
    @security.public
2954
    def workflow_script_sample(self):
2955
        events.after_sample(self)
2956
2957
    @security.public
2958
    def workflow_script_receive(self):
2959
        events.after_receive(self)
2960
2961
    @security.public
2962
    def workflow_script_preserve(self):
2963
        events.after_preserve(self)
2964
2965
    @security.public
2966
    def workflow_script_attach(self):
2967
        events.after_attach(self)
2968
2969
    @security.public
2970
    def workflow_script_verify(self):
2971
        events.after_verify(self)
2972
2973
    @security.public
2974
    def workflow_script_publish(self):
2975
        events.after_publish(self)
2976
2977
    @security.public
2978
    def workflow_script_reinstate(self):
2979
        events.after_reinstate(self)
2980
2981
    @security.public
2982
    def workflow_script_cancel(self):
2983
        events.after_cancel(self)
2984
2985
    @security.public
2986
    def workflow_script_schedule_sampling(self):
2987
        events.after_schedule_sampling(self)
2988
2989
    @security.public
2990
    def workflow_script_reject(self):
2991
        events.after_reject(self)
2992
2993
    @security.public
2994
    def workflow_script_retract(self):
2995
        events.after_retract(self)
2996
2997
    @security.public
2998
    def workflow_script_invalidate(self):
2999
        events.after_invalidate(self)
3000
3001
    def SearchableText(self):
3002
        """
3003
        Override searchable text logic based on the requirements.
3004
3005
        This method constructs a text blob which contains all full-text
3006
        searchable text for this content item.
3007
        https://docs.plone.org/develop/plone/searching_and_indexing/indexing.html#full-text-searching
3008
        """
3009
3010
        # Speed up string concatenation ops by using a buffer
3011
        entries = []
3012
3013
        # plain text fields we index from ourself,
3014
        # a list of accessor methods of the class
3015
        plain_text_fields = ("getId", )
3016
3017
        def read(acc):
3018
            """
3019
            Call a class accessor method to give a value for certain Archetypes
3020
            field.
3021
            """
3022
            try:
3023
                val = acc()
3024
            except Exception as e:
3025
                message = "Error getting the accessor parameter in " \
3026
                          "SearchableText from the Analysis Request Object " \
3027
                          "{}: {}".format(self.getId(), e.message)
3028
                logger.error(message)
3029
                val = ""
3030
3031
            if val is None:
3032
                val = ""
3033
3034
            return val
3035
3036
        # Concatenate plain text fields as they are
3037
        for f in plain_text_fields:
3038
            accessor = getattr(self, f)
3039
            value = read(accessor)
3040
            entries.append(value)
3041
3042
        # Adding HTML Fields to SearchableText can be uncommented if necessary
3043
        # transforms = getToolByName(self, 'portal_transforms')
3044
        #
3045
        # # Run HTML valued fields through text/plain conversion
3046
        # for f in html_fields:
3047
        #     accessor = getattr(self, f)
3048
        #     value = read(accessor)
3049
        #
3050
        #     if value != "":
3051
        #         stream = transforms.convertTo('text/plain', value,
3052
        #                                       mimetype='text/html')
3053
        #         value = stream.getData()
3054
        #
3055
        #     entries.append(value)
3056
3057
        # Plone accessor methods assume utf-8
3058
        def convertToUTF8(text):
3059
            if type(text) == unicode:
3060
                return text.encode("utf-8")
3061
            return text
3062
3063
        entries = [convertToUTF8(entry) for entry in entries]
3064
3065
        # Concatenate all strings to one text blob
3066
        return " ".join(entries)
3067
3068
    def getPriorityText(self):
3069
        """
3070
        This function looks up the priority text from priorities vocab
3071
        :returns: the priority text or ''
3072
        """
3073
        if self.getPriority():
3074
            return PRIORITIES.getValue(self.getPriority())
3075
        return ''
3076
3077
    def get_ARAttachment(self):
3078
        logger.warn("_ARAttachment is a virtual field used in AR Add. "
3079
                    "It can not hold an own value!")
3080
        return None
3081
3082
    def set_ARAttachment(self, value):
3083
        logger.warn("_ARAttachment is a virtual field used in AR Add. "
3084
                    "It can not hold an own value!")
3085
        return None
3086
3087
    def get_retest(self):
3088
        """Returns the Analysis Request automatically generated because of the
3089
        retraction of the current analysis request
3090
        """
3091
        relationship = "AnalysisRequestRetracted"
3092
        retest = self.getBackReferences(relationship=relationship)
3093
        if retest and len(retest) > 1:
3094
            logger.warn("More than one retest for {0}".format(self.getId()))
3095
        return retest and retest[0] or None
3096
3097
registerType(AnalysisRequest, PROJECTNAME)
3098