Passed
Push — master ( 2370a7...d5eb0b )
by Ramon
04:38
created

erify()   A

Complexity

Conditions 2

Size

Total Lines 23
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 9
dl 0
loc 23
rs 9.95
c 0
b 0
f 0
cc 2
nop 2
1
# -*- coding: utf-8 -*-
2
#
3
# This file is part of SENAITE.CORE
4
#
5
# Copyright 2018 by it's authors.
6
# Some rights reserved. See LICENSE.rst, CONTRIBUTORS.rst.
7
8
import sys
9
from decimal import Decimal
10
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
    # TODO Workflow - Request - Fix DateSampled inconsistencies. At the moment,
531
    # one can create an AR (using code) with DateSampled set when sampling_wf at
532
    # the same time sampling workflow is active. This might cause
533
    # inconsistencies: AR still in `to_be_sampled`, but getDateSampled returns
534
    # a valid date!
535
    ProxyField(
536
        'DateSampled',
537
        proxy="context.getSample()",
538
        mode="rw",
539
        read_permission=View,
540
        write_permission=ModifyPortalContent,
541
        widget=DateTimeWidget(
542
            label=_("Date Sampled"),
543
            description=_("The date when the sample was taken"),
544
            size=20,
545
            show_time=True,
546
            datepicker_nofuture=1,
547
            visible={
548
                'edit': 'visible',
549
                'view': 'visible',
550
                'add': 'edit',
551
                'secondary': 'disabled',
552
                'header_table': 'prominent',
553
                'sample_registered': {
554
                    'view': 'invisible', 'edit': 'invisible'},
555
                'to_be_sampled': {'view': 'invisible', 'edit': 'visible'},
556
                'scheduled_sampling': {'view': 'invisible', 'edit': 'visible'},
557
                'sampled': {'view': 'visible', 'edit': 'invisible'},
558
                'to_be_preserved': {'view': 'visible', 'edit': 'invisible'},
559
                'sample_due': {'view': 'visible', 'edit': 'invisible'},
560
                'sample_received': {'view': 'visible', 'edit': 'invisible'},
561
                'attachment_due': {'view': 'visible', 'edit': 'invisible'},
562
                'to_be_verified': {'view': 'visible', 'edit': 'invisible'},
563
                'verified': {'view': 'visible', 'edit': 'invisible'},
564
                'published': {'view': 'visible', 'edit': 'invisible'},
565
                'invalid': {'view': 'visible', 'edit': 'invisible'},
566
                'rejected': {'view': 'visible', 'edit': 'invisible'},
567
            },
568
            render_own_label=True,
569
        ),
570
    ),
571
    # This field is a mirror of a field in Sample with the same name
572
    ProxyField(
573
        'Sampler',
574
        proxy="context.getSample()",
575
        mode="rw",
576
        read_permission=View,
577
        write_permission=SampleSample,
578
        vocabulary='getSamplers',
579
        widget=BikaSelectionWidget(
580
            format='select',
581
            label=_("Sampler"),
582
            description=_("The person who took the sample"),
583
            # see SamplingWOrkflowWidgetVisibility
584
            visible={
585
                'edit': 'visible',
586
                'view': 'visible',
587
                'add': 'edit',
588
                'header_table': 'prominent',
589
                'sample_registered': {
590
                    'view': 'invisible', 'edit': 'invisible'},
591
                'to_be_sampled': {'view': 'invisible', 'edit': 'visible'},
592
                'scheduled_sampling': {'view': 'invisible', 'edit': 'visible'},
593
                'sampled': {'view': 'visible', 'edit': 'invisible'},
594
                'to_be_preserved': {'view': 'visible', 'edit': 'invisible'},
595
                'sample_due': {'view': 'visible', 'edit': 'invisible'},
596
                'sample_received': {'view': 'visible', 'edit': 'invisible'},
597
                'attachment_due': {'view': 'visible', 'edit': 'invisible'},
598
                'to_be_verified': {'view': 'visible', 'edit': 'invisible'},
599
                'verified': {'view': 'visible', 'edit': 'invisible'},
600
                'published': {'view': 'visible', 'edit': 'invisible'},
601
                'invalid': {'view': 'visible', 'edit': 'invisible'},
602
                'rejected': {'view': 'visible', 'edit': 'invisible'},
603
            },
604
            render_own_label=True,
605
        ),
606
    ),
607
608
    # Sample field
609
    ProxyField(
610
        'ScheduledSamplingSampler',
611
        proxy="context.getSample()",
612
        mode="rw",
613
        read_permission=View,
614
        write_permission=ScheduleSampling,
615
        vocabulary='getSamplers',
616
        widget=BikaSelectionWidget(
617
            description=_("Define the sampler supposed to do the sample in "
618
                          "the scheduled date"),
619
            format='select',
620
            label=_("Sampler for scheduled sampling"),
621
            visible={
622
                'edit': 'visible',
623
                'view': 'visible',
624
                'header_table': 'visible',
625
                'sample_registered': {
626
                    'view': 'invisible', 'edit': 'invisible'},
627
                'to_be_sampled': {'view': 'visible', 'edit': 'visible'},
628
                'scheduled_sampling': {'view': 'visible', 'edit': 'visible'},
629
                'sampled': {'view': 'invisible', 'edit': 'invisible'},
630
                'to_be_preserved': {'view': 'invisible', 'edit': 'invisible'},
631
                'sample_due': {'view': 'invisible', 'edit': 'invisible'},
632
                'sample_received': {'view': 'invisible', 'edit': 'invisible'},
633
                'expired': {'view': 'invisible', 'edit': 'invisible'},
634
                'disposed': {'view': 'invisible', 'edit': 'invisible'},
635
            },
636
            render_own_label=True,
637
        ),
638
    ),  # This field is a mirror of a Sample field with the same name
639
640
    # Sample field
641
    ProxyField(
642
        'SamplingDate',
643
        required=0,
644
        proxy="context.getSample()",
645
        mode="rw",
646
        read_permission=View,
647
        write_permission=ModifyPortalContent,
648
        widget=DateTimeWidget(
649
            label=_("Expected Sampling Date"),
650
            description=_("The date when the sample will be taken"),
651
            size=20,
652
            show_time=True,
653
            render_own_label=True,
654
            datepicker_nopast=1,
655
            # We must use SamplingWOrkflowWidgetVisibility soon.
656
            # For now we will handle it through JS
657
            # see SamplingWOrkflowWidgetVisibility
658
            visible={
659
                'edit': 'visible',
660
                'view': 'visible',
661
                'add': 'edit',
662
                'header_table': 'visible',
663
                'secondary': 'disabled',
664
                'sample_registered': {
665
                    'view': 'visible', 'edit': 'visible', 'add': 'edit'},
666
                'to_be_sampled': {'view': 'visible', 'edit': 'visible'},
667
                'scheduled_sampling': {'view': 'visible', 'edit': 'visible'},
668
                'sampled': {'view': 'visible', 'edit': 'invisible'},
669
                'to_be_preserved': {'view': 'visible', 'edit': 'invisible'},
670
                'sample_due': {'view': 'visible', 'edit': 'invisible'},
671
                'sample_received': {'view': 'visible', 'edit': 'invisible'},
672
                'attachment_due': {'view': 'visible', 'edit': 'invisible'},
673
                'to_be_verified': {'view': 'visible', 'edit': 'invisible'},
674
                'verified': {'view': 'visible', 'edit': 'invisible'},
675
                'published': {'view': 'visible', 'edit': 'invisible'},
676
                'invalid': {'view': 'visible', 'edit': 'invisible'},
677
                'rejected': {'view': 'visible', 'edit': 'invisible'},
678
            },
679
        ),
680
    ),
681
682
    # Sample field
683
    ProxyField(
684
        'SampleType',
685
        proxy="context.getSample()",
686
        required=1,
687
        allowed_types='SampleType',
688
        relationship='AnalysisRequestSampleType',
689
        mode="rw",
690
        read_permission=View,
691
        write_permission=ModifyPortalContent,
692
        widget=ReferenceWidget(
693
            label=_("Sample Type"),
694
            description=_("Create a new sample of this type"),
695
            size=20,
696
            render_own_label=True,
697
            visible={
698
                'edit': 'visible',
699
                'view': 'visible',
700
                'add': 'edit',
701
                'secondary': 'disabled',
702
                'header_table': 'visible',
703
                'sample_registered': {'view': 'visible', 'edit': 'visible', 'add': 'edit'},
704
                'to_be_sampled': {'view': 'visible', 'edit': 'visible'},
705
                'scheduled_sampling': {'view': 'invisible', 'edit': 'visible'},
706
                'sampled': {'view': 'visible', 'edit': 'visible'},
707
                'to_be_preserved': {'view': 'visible', 'edit': 'visible'},
708
                'sample_due': {'view': 'visible', 'edit': 'visible'},
709
                'sample_received': {'view': 'visible', 'edit': 'invisible'},
710
                'attachment_due': {'view': 'visible', 'edit': 'invisible'},
711
                'to_be_verified': {'view': 'visible', 'edit': 'invisible'},
712
                'verified': {'view': 'visible', 'edit': 'invisible'},
713
                'published': {'view': 'visible', 'edit': 'invisible'},
714
                'invalid': {'view': 'visible', 'edit': 'invisible'},
715
                'rejected': {'view': 'visible', 'edit': 'invisible'},
716
            },
717
            catalog_name='bika_setup_catalog',
718
            base_query={'inactive_state': 'active'},
719
            showOn=True,
720
        ),
721
    ),
722
723
    RecordsField(
724
        'RejectionReasons',
725
        widget=RejectionWidget(
726
            label=_("Sample Rejection"),
727
            description=_("Set the Sample Rejection workflow and the reasons"),
728
            render_own_label=False,
729
            visible={
730
                'edit': 'visible',
731
                'view': 'visible',
732
                'add': 'edit',
733
                'secondary': 'disabled',
734
                'header_table': 'visible',
735
                'sample_registered': {
736
                    'view': 'visible', 'edit': 'visible', 'add': 'edit'},
737
                'to_be_sampled': {'view': 'visible', 'edit': 'visible'},
738
                'sampled': {'view': 'visible', 'edit': 'visible'},
739
                'to_be_preserved': {'view': 'visible', 'edit': 'visible'},
740
                'sample_due': {'view': 'visible', 'edit': 'visible'},
741
                'sample_received': {'view': 'visible', 'edit': 'visible'},
742
                'attachment_due': {'view': 'visible', 'edit': 'visible'},
743
                'to_be_verified': {'view': 'visible', 'edit': 'visible'},
744
                'verified': {'view': 'visible', 'edit': 'visible'},
745
                'published': {'view': 'visible', 'edit': 'visible'},
746
                'invalid': {'view': 'visible', 'edit': 'visible'},
747
                'rejected': {'view': 'visible', 'edit': 'invisible'},
748
            },
749
        ),
750
    ),
751
752
    ReferenceField(
753
        'Specification',
754
        required=0,
755
        allowed_types='AnalysisSpec',
756
        relationship='AnalysisRequestAnalysisSpec',
757
        mode="rw",
758
        read_permission=View,
759
        write_permission=ModifyPortalContent,
760
        widget=ReferenceWidget(
761
            label=_("Analysis Specification"),
762
            description=_("Choose default AR specification values"),
763
            size=20,
764
            render_own_label=True,
765
            visible={
766
                'edit': 'visible',
767
                'view': 'visible',
768
                'add': 'edit',
769
                'header_table': 'visible',
770
                'sample_registered': {
771
                    'view': 'visible', 'edit': 'visible', 'add': 'edit'},
772
                'to_be_sampled': {'view': 'visible', 'edit': 'visible'},
773
                'scheduled_sampling': {'view': 'visible', 'edit': 'visible'},
774
                'sampled': {'view': 'visible', 'edit': 'visible'},
775
                'to_be_preserved': {'view': 'visible', 'edit': 'visible'},
776
                'sample_due': {'view': 'visible', 'edit': 'visible'},
777
                'sample_received': {'view': 'visible', 'edit': 'visible'},
778
                'attachment_due': {'view': 'visible', 'edit': 'visible'},
779
                'to_be_verified': {'view': 'visible', 'edit': 'invisible'},
780
                'verified': {'view': 'visible', 'edit': 'invisible'},
781
                'published': {'view': 'visible', 'edit': 'invisible'},
782
                'invalid': {'view': 'visible', 'edit': 'invisible'},
783
                'rejected': {'view': 'visible', 'edit': 'invisible'},
784
            },
785
            catalog_name='bika_setup_catalog',
786
            colModel=[
787
                {'columnName': 'contextual_title',
788
                 'width': '30',
789
                 'label': _('Title'),
790
                 'align': 'left'},
791
                {'columnName': 'SampleTypeTitle',
792
                 'width': '70',
793
                 'label': _('SampleType'),
794
                 'align': 'left'},
795
                # UID is required in colModel
796
                {'columnName': 'UID', 'hidden': True},
797
            ],
798
            showOn=True,
799
        ),
800
    ),
801
802
    # see setResultsRange below.
803
    RecordsField(
804
        'ResultsRange',
805
        required=0,
806
        type='resultsrange',
807
        subfields=('keyword', 'min', 'max', 'warn_min', 'warn_max', 'hidemin',
808
                   'hidemax', 'rangecomment', 'min_operator', 'max_operator'),
809
        widget=ComputedWidget(visible=False),
810
    ),
811
812
    ReferenceField(
813
        'PublicationSpecification',
814
        required=0,
815
        allowed_types='AnalysisSpec',
816
        relationship='AnalysisRequestPublicationSpec',
817
        mode="rw",
818
        read_permission=View,
819
        write_permission=View,
820
        widget=ReferenceWidget(
821
            label=_("Publication Specification"),
822
            description=_(
823
                "Set the specification to be used before publishing an AR."),
824
            size=20,
825
            render_own_label=True,
826
            visible={
827
                'edit': 'visible',
828
                'view': 'visible',
829
                'header_table': 'visible',
830
                'sample_registered': {
831
                    'view': 'invisible', 'edit': 'invisible'},
832
                'to_be_sampled': {'view': 'invisible', 'edit': 'invisible'},
833
                'scheduled_sampling': {
834
                    'view': 'invisible', 'edit': 'invisible'},
835
                'sampled': {'view': 'invisible', 'edit': 'invisible'},
836
                'to_be_preserved': {'view': 'invisible', 'edit': 'invisible'},
837
                'sample_due': {'view': 'invisible', 'edit': 'invisible'},
838
                'sample_received': {'view': 'invisible', 'edit': 'invisible'},
839
                'attachment_due': {'view': 'invisible', 'edit': 'invisible'},
840
                'to_be_verified': {'view': 'invisible', 'edit': 'invisible'},
841
                'verified': {'view': 'visible', 'edit': 'visible'},
842
                'published': {'view': 'visible', 'edit': 'visible'},
843
                'invalid': {'view': 'visible', 'edit': 'invisible'},
844
                'rejected': {'view': 'visible', 'edit': 'invisible'},
845
            },
846
            catalog_name='bika_setup_catalog',
847
            base_query={'inactive_state': 'active'},
848
            showOn=True,
849
        ),
850
    ),
851
852
    # Sample field
853
    ProxyField(
854
        'SamplePoint',
855
        proxy="context.getSample()",
856
        allowed_types='SamplePoint',
857
        relationship='AnalysisRequestSamplePoint',
858
        mode="rw",
859
        read_permission=View,
860
        write_permission=ModifyPortalContent,
861
        widget=ReferenceWidget(
862
            label=_("Sample Point"),
863
            description=_("Location where sample was taken"),
864
            size=20,
865
            render_own_label=True,
866
            visible={
867
                'edit': 'visible',
868
                'view': 'visible',
869
                'add': 'edit',
870
                'secondary': 'disabled',
871
                'header_table': 'visible',
872
                'sample_registered': {
873
                    'view': 'visible', 'edit': 'visible', 'add': 'edit'},
874
                'to_be_sampled': {'view': 'visible', 'edit': 'visible'},
875
                # LIMS-1159
876
                'scheduled_sampling': {'view': 'visible', 'edit': 'visible'},
877
                'sampled': {'view': 'visible', 'edit': 'visible'},
878
                'to_be_preserved': {'view': 'visible', 'edit': 'visible'},
879
                'sample_due': {'view': 'visible', 'edit': 'visible'},
880
                'sample_received': {'view': 'visible', 'edit': 'visible'},
881
                'attachment_due': {'view': 'visible', 'edit': 'visible'},
882
                'to_be_verified': {'view': 'visible', 'edit': 'visible'},
883
                'verified': {'view': 'visible', 'edit': 'invisible'},
884
                'published': {'view': 'visible', 'edit': 'invisible'},
885
                'invalid': {'view': 'visible', 'edit': 'invisible'},
886
                'rejected': {'view': 'visible', 'edit': 'invisible'},
887
            },
888
            catalog_name='bika_setup_catalog',
889
            base_query={'inactive_state': 'active'},
890
            showOn=True,
891
        ),
892
    ),
893
894
    # Sample field
895
    ProxyField(
896
        'StorageLocation',
897
        proxy="context.getSample()",
898
        allowed_types='StorageLocation',
899
        relationship='AnalysisRequestStorageLocation',
900
        mode="rw",
901
        read_permission=View,
902
        write_permission=ModifyPortalContent,
903
        widget=ReferenceWidget(
904
            label=_("Storage Location"),
905
            description=_("Location where sample is kept"),
906
            size=20,
907
            render_own_label=True,
908
            visible={
909
                'edit': 'visible',
910
                'view': 'visible',
911
                'add': 'edit',
912
                'secondary': 'disabled',
913
                'header_table': 'visible',
914
                'sample_registered':
915
                    {'view': 'visible', 'edit': 'visible', 'add': 'edit'},
916
                'to_be_sampled': {'view': 'visible', 'edit': 'visible'},
917
                'scheduled_sampling': {'view': 'visible', 'edit': 'visible'},
918
                'sampled': {'view': 'visible', 'edit': 'visible'},
919
                'to_be_preserved': {'view': 'visible', 'edit': 'visible'},
920
                'sample_due': {'view': 'visible', 'edit': 'visible'},
921
                'sample_received': {'view': 'visible', 'edit': 'visible'},
922
                'attachment_due': {'view': 'visible', 'edit': 'visible'},
923
                'to_be_verified': {'view': 'visible', 'edit': 'visible'},
924
                'verified': {'view': 'visible', 'edit': 'visible'},
925
                'published': {'view': 'visible', 'edit': 'invisible'},
926
                'invalid': {'view': 'visible', 'edit': 'invisible'},
927
                'rejected': {'view': 'visible', 'edit': 'invisible'},
928
            },
929
            catalog_name='bika_setup_catalog',
930
            base_query={'inactive_state': 'active'},
931
            showOn=True,
932
        ),
933
    ),
934
935
    StringField(
936
        'ClientOrderNumber',
937
        mode="rw",
938
        read_permission=View,
939
        write_permission=ModifyPortalContent,
940
        widget=StringWidget(
941
            label=_("Client Order Number"),
942
            description=_("The client side order number for this request"),
943
            size=20,
944
            render_own_label=True,
945
            visible={
946
                'edit': 'visible',
947
                'view': 'visible',
948
                'add': 'edit',
949
                'header_table': 'visible',
950
                'sample_registered': {
951
                    'view': 'visible', 'edit': 'visible', 'add': 'edit'},
952
                'to_be_sampled': {'view': 'visible', 'edit': 'visible'},
953
                'scheduled_sampling': {'view': 'visible', 'edit': 'visible'},
954
                'sampled': {'view': 'visible', 'edit': 'visible'},
955
                'to_be_preserved': {'view': 'visible', 'edit': 'visible'},
956
                'sample_due': {'view': 'visible', 'edit': 'visible'},
957
                'sample_received': {'view': 'visible', 'edit': 'visible'},
958
                'attachment_due': {'view': 'visible', 'edit': 'visible'},
959
                'to_be_verified': {'view': 'visible', 'edit': 'visible'},
960
                'verified': {'view': 'visible', 'edit': 'visible'},
961
                'published': {'view': 'visible', 'edit': 'invisible'},
962
                'invalid': {'view': 'visible', 'edit': 'invisible'},
963
                'rejected': {'view': 'visible', 'edit': 'invisible'},
964
            },
965
        ),
966
    ),
967
968
    # Sample field
969
    ProxyField(
970
        'ClientReference',
971
        proxy="context.getSample()",
972
        mode="rw",
973
        read_permission=View,
974
        write_permission=ModifyPortalContent,
975
        widget=StringWidget(
976
            label=_("Client Reference"),
977
            description=_("The client side reference for this request"),
978
            size=20,
979
            render_own_label=True,
980
            visible={
981
                'edit': 'visible',
982
                'view': 'visible',
983
                'add': 'edit',
984
                'secondary': 'disabled',
985
                'header_table': 'visible',
986
                'sample_registered':
987
                    {'view': 'visible', 'edit': 'visible', 'add': 'edit'},
988
                'to_be_sampled': {'view': 'visible', 'edit': 'visible'},
989
                'scheduled_sampling': {'view': 'visible', 'edit': 'visible'},
990
                'sampled': {'view': 'visible', 'edit': 'visible'},
991
                'to_be_preserved': {'view': 'visible', 'edit': 'visible'},
992
                'sample_due': {'view': 'visible', 'edit': 'visible'},
993
                'sample_received': {'view': 'visible', 'edit': 'visible'},
994
                'attachment_due': {'view': 'visible', 'edit': 'visible'},
995
                'to_be_verified': {'view': 'visible', 'edit': 'visible'},
996
                'verified': {'view': 'visible', 'edit': 'visible'},
997
                'published': {'view': 'visible', 'edit': 'invisible'},
998
                'invalid': {'view': 'visible', 'edit': 'invisible'},
999
                'rejected': {'view': 'visible', 'edit': 'invisible'},
1000
            },
1001
        ),
1002
    ),
1003
1004
    # Sample field
1005
    ProxyField(
1006
        'ClientSampleID',
1007
        proxy="context.getSample()",
1008
        mode="rw",
1009
        read_permission=View,
1010
        write_permission=ModifyPortalContent,
1011
        widget=StringWidget(
1012
            label=_("Client Sample ID"),
1013
            description=_("The client side identifier of the sample"),
1014
            size=20,
1015
            render_own_label=True,
1016
            visible={
1017
                'edit': 'visible',
1018
                'view': 'visible',
1019
                'add': 'edit',
1020
                'secondary': 'disabled',
1021
                'header_table': 'visible',
1022
                'sample_registered': {'view': 'visible', 'edit': 'visible'},
1023
                'to_be_sampled': {'view': 'visible', 'edit': 'invisible'},
1024
                'scheduled_sampling': {'view': 'visible', 'edit': 'invisible'},
1025
                'sampled': {'view': 'visible', 'edit': 'invisible'},
1026
                'to_be_preserved': {'view': 'visible', 'edit': 'invisible'},
1027
                'sample_due': {'view': 'visible', 'edit': 'invisible'},
1028
                'sample_received': {'view': 'visible', 'edit': 'invisible'},
1029
                'attachment_due': {'view': 'visible', 'edit': 'invisible'},
1030
                'to_be_verified': {'view': 'visible', 'edit': 'invisible'},
1031
                'verified': {'view': 'visible', 'edit': 'invisible'},
1032
                'published': {'view': 'visible', 'edit': 'invisible'},
1033
                'invalid': {'view': 'visible', 'edit': 'invisible'},
1034
                'rejected': {'view': 'visible', 'edit': 'invisible'},
1035
            },
1036
        ),
1037
    ),
1038
1039
    # Sample field
1040
    ProxyField(
1041
        'SamplingDeviation',
1042
        proxy="context.getSample()",
1043
        allowed_types=('SamplingDeviation',),
1044
        relationship='AnalysisRequestSamplingDeviation',
1045
        referenceClass=HoldingReference,
1046
        mode="rw",
1047
        read_permission=View,
1048
        write_permission=ModifyPortalContent,
1049
        widget=ReferenceWidget(
1050
            label=_("Sampling Deviation"),
1051
            description=_("Deviation between the sample and how it "
1052
                          "was sampled"),
1053
            size=20,
1054
            render_own_label=True,
1055
            visible={
1056
                'edit': 'visible',
1057
                'view': 'visible',
1058
                'add': 'edit',
1059
                'secondary': 'disabled',
1060
                'header_table': 'visible',
1061
                'sample_registered': {
1062
                    'view': 'visible', 'edit': 'visible', 'add': 'edit'},
1063
                'to_be_sampled': {'view': 'visible', 'edit': 'visible'},
1064
                'scheduled_sampling': {'view': 'visible', 'edit': 'visible'},
1065
                'sampled': {'view': 'visible', 'edit': 'visible'},
1066
                'to_be_preserved': {'view': 'visible', 'edit': 'visible'},
1067
                'sample_due': {'view': 'visible', 'edit': 'visible'},
1068
                'sample_received': {'view': 'visible', 'edit': 'invisible'},
1069
                'attachment_due': {'view': 'visible', 'edit': 'invisible'},
1070
                'to_be_verified': {'view': 'visible', 'edit': 'invisible'},
1071
                'verified': {'view': 'visible', 'edit': 'invisible'},
1072
                'published': {'view': 'visible', 'edit': 'invisible'},
1073
                'invalid': {'view': 'visible', 'edit': 'invisible'},
1074
                'rejected': {'view': 'visible', 'edit': 'invisible'},
1075
            },
1076
            catalog_name='bika_setup_catalog',
1077
            base_query={'inactive_state': 'active'},
1078
            showOn=True,
1079
        ),
1080
    ),
1081
1082
    # Sample field
1083
    ProxyField(
1084
        'SampleCondition',
1085
        proxy="context.getSample()",
1086
        allowed_types=('SampleCondition',),
1087
        relationship='AnalysisRequestSampleCondition',
1088
        referenceClass=HoldingReference,
1089
        mode="rw",
1090
        read_permission=View,
1091
        write_permission=ModifyPortalContent,
1092
        widget=ReferenceWidget(
1093
            label=_("Sample condition"),
1094
            description=_("The current condition of the sample"),
1095
            size=20,
1096
            render_own_label=True,
1097
            visible={
1098
                'edit': 'visible',
1099
                'view': 'visible',
1100
                'add': 'edit',
1101
                'secondary': 'disabled',
1102
                'header_table': 'visible',
1103
                'sample_registered': {
1104
                    'view': 'visible', 'edit': 'visible', 'add': 'edit'},
1105
                'to_be_sampled': {'view': 'visible', 'edit': 'visible'},
1106
                'scheduled_sampling': {'view': 'visible', 'edit': 'visible'},
1107
                'sampled': {'view': 'visible', 'edit': 'visible'},
1108
                'to_be_preserved': {'view': 'visible', 'edit': 'visible'},
1109
                'sample_due': {'view': 'visible', 'edit': 'visible'},
1110
                'sample_received': {'view': 'visible', 'edit': 'invisible'},
1111
                'attachment_due': {'view': 'visible', 'edit': 'invisible'},
1112
                'to_be_verified': {'view': 'visible', 'edit': 'invisible'},
1113
                'verified': {'view': 'visible', 'edit': 'invisible'},
1114
                'published': {'view': 'visible', 'edit': 'invisible'},
1115
                'invalid': {'view': 'visible', 'edit': 'invisible'},
1116
                'rejected': {'view': 'visible', 'edit': 'invisible'},
1117
            },
1118
            catalog_name='bika_setup_catalog',
1119
            base_query={'inactive_state': 'active'},
1120
            showOn=True,
1121
        ),
1122
    ),
1123
1124
    StringField(
1125
        'Priority',
1126
        default='3',
1127
        vocabulary=PRIORITIES,
1128
        mode='rw',
1129
        read_permission=View,
1130
        write_permission=ModifyPortalContent,
1131
        widget=PrioritySelectionWidget(
1132
            label=_('Priority'),
1133
            format='select',
1134
            visible={
1135
                'edit': 'visible',
1136
                'view': 'visible',
1137
                'add': 'edit',
1138
                'header_table': 'visible',
1139
                'sample_registered':
1140
                    {'view': 'visible', 'edit': 'visible', 'add': 'edit'},
1141
                'to_be_sampled': {'view': 'visible', 'edit': 'visible'},
1142
                'scheduled_sampling': {'view': 'visible', 'edit': 'visible'},
1143
                'sampled': {'view': 'visible', 'edit': 'visible'},
1144
                'to_be_preserved': {'view': 'visible', 'edit': 'visible'},
1145
                'sample_due': {'view': 'visible', 'edit': 'visible'},
1146
                'sample_received': {'view': 'visible', 'edit': 'visible'},
1147
                'attachment_due': {'view': 'visible', 'edit': 'visible'},
1148
                'to_be_verified': {'view': 'visible', 'edit': 'visible'},
1149
                'verified': {'view': 'visible', 'edit': 'visible'},
1150
                'published': {'view': 'visible', 'edit': 'invisible'},
1151
                'invalid': {'view': 'visible', 'edit': 'invisible'},
1152
                'rejected': {'view': 'visible', 'edit': 'invisible'},
1153
            },
1154
        ),
1155
    ),
1156
    # Sample field
1157
    ProxyField(
1158
        'EnvironmentalConditions',
1159
        proxy="context.getSample()",
1160
        mode="rw",
1161
        read_permission=View,
1162
        write_permission=ModifyPortalContent,
1163
        widget=StringWidget(
1164
            label=_("Environmental conditions"),
1165
            description=_("The environmental condition during sampling"),
1166
            visible={
1167
                'edit': 'visible',
1168
                'view': 'visible',
1169
                'add': 'edit',
1170
                'header_table': 'prominent',
1171
                'sample_registered': {
1172
                    'view': 'visible', 'edit': 'visible', 'add': 'edit'},
1173
                'to_be_sampled': {'view': 'visible', 'edit': 'visible'},
1174
                'scheduled_sampling': {'view': 'visible', 'edit': 'visible'},
1175
                'sampled': {'view': 'visible', 'edit': 'visible'},
1176
                'to_be_preserved': {'view': 'visible', 'edit': 'visible'},
1177
                'sample_received': {'view': 'visible', 'edit': 'visible'},
1178
                'attachment_due': {'view': 'visible', 'edit': 'visible'},
1179
                'to_be_verified': {'view': 'visible', 'edit': 'visible'},
1180
                'verified': {'view': 'visible', 'edit': 'invisible'},
1181
                'published': {'view': 'visible', 'edit': 'invisible'},
1182
                'invalid': {'view': 'visible', 'edit': 'invisible'},
1183
                'rejected': {'view': 'visible', 'edit': 'invisible'},
1184
            },
1185
            render_own_label=True,
1186
            size=20,
1187
        ),
1188
    ),
1189
1190
    ReferenceField(
1191
        'DefaultContainerType',
1192
        allowed_types=('ContainerType',),
1193
        relationship='AnalysisRequestContainerType',
1194
        referenceClass=HoldingReference,
1195
        mode="rw",
1196
        read_permission=View,
1197
        write_permission=ModifyPortalContent,
1198
        widget=ReferenceWidget(
1199
            label=_("Default Container"),
1200
            description=_("Default container for new sample partitions"),
1201
            size=20,
1202
            render_own_label=True,
1203
            visible={
1204
                'edit': 'visible',
1205
                'view': 'visible',
1206
                'add': 'edit',
1207
                'secondary': 'disabled',
1208
                'header_table': 'visible',
1209
                'sample_registered': {
1210
                    'view': 'visible', 'edit': 'visible', 'add': 'edit'},
1211
                'to_be_sampled': {'view': 'visible', 'edit': 'invisible'},
1212
                'scheduled_sampling': {'view': 'visible', 'edit': 'invisible'},
1213
                'sampled': {'view': 'visible', 'edit': 'invisible'},
1214
                'to_be_preserved': {'view': 'visible', 'edit': 'invisible'},
1215
                'sample_due': {'view': 'visible', 'edit': 'invisible'},
1216
                'sample_received': {'view': 'visible', 'edit': 'invisible'},
1217
                'attachment_due': {'view': 'visible', 'edit': 'invisible'},
1218
                'to_be_verified': {'view': 'visible', 'edit': 'invisible'},
1219
                'verified': {'view': 'visible', 'edit': 'invisible'},
1220
                'published': {'view': 'visible', 'edit': 'invisible'},
1221
                'invalid': {'view': 'visible', 'edit': 'invisible'},
1222
                'rejected': {'view': 'visible', 'edit': 'invisible'},
1223
            },
1224
            catalog_name='bika_setup_catalog',
1225
            base_query={'inactive_state': 'active'},
1226
            showOn=True,
1227
        ),
1228
    ),
1229
1230
    # Sample field
1231
    ProxyField(
1232
        'AdHoc',
1233
        proxy="context.getSample()",
1234
        default=False,
1235
        mode="rw",
1236
        read_permission=View,
1237
        write_permission=ModifyPortalContent,
1238
        widget=BooleanWidget(
1239
            label=_("Sampled AdHoc"),
1240
            description=_("Was the sample taken in non-scheduled matter, " \
1241
                          "e.g. out of a recurring sampling schedule?"),
1242
            render_own_label=True,
1243
            visible={
1244
                'edit': 'visible',
1245
                'view': 'visible',
1246
                'add': 'edit',
1247
                'secondary': 'disabled',
1248
                'header_table': 'visible',
1249
                'sample_registered':
1250
                    {'view': 'visible', 'edit': 'visible', 'add': 'edit'},
1251
                'to_be_sampled': {'view': 'visible', 'edit': 'invisible'},
1252
                'scheduled_sampling': {'view': 'visible', 'edit': 'invisible'},
1253
                'sampled': {'view': 'visible', 'edit': 'invisible'},
1254
                'to_be_preserved': {'view': 'visible', 'edit': 'invisible'},
1255
                'sample_due': {'view': 'visible', 'edit': 'invisible'},
1256
                'sample_received': {'view': 'visible', 'edit': 'invisible'},
1257
                'attachment_due': {'view': 'visible', 'edit': 'invisible'},
1258
                'to_be_verified': {'view': 'visible', 'edit': 'invisible'},
1259
                'verified': {'view': 'visible', 'edit': 'invisible'},
1260
                'published': {'view': 'visible', 'edit': 'invisible'},
1261
                'invalid': {'view': 'visible', 'edit': 'invisible'},
1262
                'rejected': {'view': 'visible', 'edit': 'invisible'},
1263
            },
1264
        ),
1265
    ),
1266
1267
    # Sample field
1268
    ProxyField(
1269
        'Composite',
1270
        proxy="context.getSample()",
1271
        default=False,
1272
        mode="rw",
1273
        read_permission=View,
1274
        write_permission=ModifyPortalContent,
1275
        widget=BooleanWidget(
1276
            label=_("Composite"),
1277
            render_own_label=True,
1278
            visible={
1279
                'edit': 'visible',
1280
                'view': 'visible',
1281
                'add': 'edit',
1282
                'secondary': 'disabled',
1283
                'header_table': 'visible',
1284
                'sample_registered': {
1285
                    'view': 'visible', 'edit': 'visible', 'add': 'edit'},
1286
                'to_be_sampled': {'view': 'visible', 'edit': 'visible'},
1287
                'scheduled_sampling': {'view': 'visible', 'edit': 'visible'},
1288
                'sampled': {'view': 'visible', 'edit': 'visible'},
1289
                'to_be_preserved': {'view': 'visible', 'edit': 'visible'},
1290
                'sample_due': {'view': 'visible', 'edit': 'visible'},
1291
                'sample_received': {'view': 'visible', 'edit': 'visible'},
1292
                'attachment_due': {'view': 'visible', 'edit': 'visible'},
1293
                'to_be_verified': {'view': 'visible', 'edit': 'visible'},
1294
                'verified': {'view': 'visible', 'edit': 'invisible'},
1295
                'published': {'view': 'visible', 'edit': 'invisible'},
1296
                'invalid': {'view': 'visible', 'edit': 'invisible'},
1297
                'rejected': {'view': 'visible', 'edit': 'invisible'},
1298
            },
1299
        ),
1300
    ),
1301
1302
    BooleanField(
1303
        'InvoiceExclude',
1304
        default=False,
1305
        mode="rw",
1306
        read_permission=View,
1307
        write_permission=ModifyPortalContent,
1308
        widget=BooleanWidget(
1309
            label=_("Invoice Exclude"),
1310
            description=_("Should the analyses be excluded from the invoice?"),
1311
            render_own_label=True,
1312
            visible={
1313
                'edit': 'visible',
1314
                'view': 'visible',
1315
                'add': 'edit',
1316
                'header_table': 'visible',
1317
                'sample_registered': {
1318
                    'view': 'visible', 'edit': 'visible', 'add': 'edit'},
1319
                'to_be_sampled': {'view': 'visible', 'edit': 'visible'},
1320
                'scheduled_sampling': {'view': 'visible', 'edit': 'visible'},
1321
                'sampled': {'view': 'visible', 'edit': 'visible'},
1322
                'to_be_preserved': {'view': 'visible', 'edit': 'visible'},
1323
                'sample_due': {'view': 'visible', 'edit': 'visible'},
1324
                'sample_received': {'view': 'visible', 'edit': 'invisible'},
1325
                'attachment_due': {'view': 'visible', 'edit': 'invisible'},
1326
                'to_be_verified': {'view': 'visible', 'edit': 'invisible'},
1327
                'verified': {'view': 'visible', 'edit': 'invisible'},
1328
                'published': {'view': 'visible', 'edit': 'invisible'},
1329
                'invalid': {'view': 'visible', 'edit': 'invisible'},
1330
                'rejected': {'view': 'visible', 'edit': 'invisible'},
1331
            },
1332
        ),
1333
    ),
1334
1335
    ARAnalysesField(
1336
        'Analyses',
1337
        required=1,
1338
        mode="rw",
1339
        read_permission=View,
1340
        write_permission=ModifyPortalContent,
1341
        widget=ComputedWidget(
1342
            visible={
1343
                'edit': 'invisible',
1344
                'view': 'invisible',
1345
                'sample_registered': {
1346
                    'view': 'visible', 'edit': 'visible', 'add': 'invisible'},
1347
            }
1348
        ),
1349
    ),
1350
1351
    ReferenceField(
1352
        'Attachment',
1353
        multiValued=1,
1354
        allowed_types=('Attachment',),
1355
        referenceClass=HoldingReference,
1356
        relationship='AnalysisRequestAttachment',
1357
        mode="rw",
1358
        read_permission=View,
1359
        write_permission=ModifyPortalContent,
1360
        widget=ComputedWidget(
1361
            visible={
1362
                'edit': 'invisible',
1363
                'view': 'invisible',
1364
            },
1365
        )
1366
    ),
1367
1368
    # This is a virtual field and handled only by AR Add View to allow multi
1369
    # attachment upload in AR Add. It should never contain an own value!
1370
    FileField(
1371
        '_ARAttachment',
1372
        widget=FileWidget(
1373
            label=_("Attachment"),
1374
            description=_("Add one or more attachments to describe the "
1375
                          "sample in this analysis request, or to specify "
1376
                          "your request."),
1377
            render_own_label=True,
1378
            visible={
1379
                'view': 'invisible',
1380
                'add': 'edit',
1381
                'header_table': 'invisible',
1382
            },
1383
        )
1384
    ),
1385
1386
    ReferenceField(
1387
        'Invoice',
1388
        vocabulary_display_path_bound=sys.maxsize,
1389
        allowed_types=('Invoice',),
1390
        referenceClass=HoldingReference,
1391
        relationship='AnalysisRequestInvoice',
1392
        mode="rw",
1393
        read_permission=View,
1394
        write_permission=ModifyPortalContent,
1395
        widget=ComputedWidget(
1396
            visible={
1397
                'edit': 'invisible',
1398
                'view': 'invisible',
1399
            },
1400
        )
1401
    ),
1402
    # This field is a mirror of a field in Sample with the same name
1403
    ProxyField(
1404
        'DateReceived',
1405
        proxy="context.getSample()",
1406
        mode="rw",
1407
        read_permission=View,
1408
        write_permission=ModifyPortalContent,
1409
        widget=DateTimeWidget(
1410
            label=_("Date Sample Received"),
1411
            show_time=True,
1412
            description=_("The date when the sample was received"),
1413
            render_own_label=True,
1414
            visible={
1415
                'edit': 'invisible',
1416
                'view': 'visible',
1417
                'header_table': 'visible',
1418
            },
1419
        ),
1420
    ),
1421
    ComputedField(
1422
        'DatePublished',
1423
        mode="r",
1424
        read_permission=View,
1425
        expression="here.getDatePublished().strftime('%Y-%m-%d %H:%M %p') if here.getDatePublished() else ''",
1426
        widget=DateTimeWidget(
1427
            label=_("Date Published"),
1428
            visible={
1429
                'edit': 'visible',
1430
                'view': 'visible',
1431
                'add': 'invisible',
1432
                'secondary': 'invisible',
1433
                'header_table': 'visible',
1434
                'sample_registered': {
1435
                    'view': 'invisible', 'edit': 'invisible'},
1436
                'to_be_sampled': {'view': 'invisible', 'edit': 'invisible'},
1437
                'scheduled_sampling': {
1438
                    'view': 'invisible', 'edit': 'invisible'},
1439
                'sampled': {'view': 'invisible', 'edit': 'invisible'},
1440
                'to_be_preserved': {'view': 'invisible', 'edit': 'invisible'},
1441
                'sample_due': {'view': 'invisible', 'edit': 'invisible'},
1442
                'sample_received': {'view': 'invisible', 'edit': 'invisible'},
1443
                'attachment_due': {'view': 'invisible', 'edit': 'invisible'},
1444
                'to_be_verified': {'view': 'invisible', 'edit': 'invisible'},
1445
                'verified': {'view': 'invisible', 'edit': 'invisible'},
1446
                'published': {'view': 'visible', 'edit': 'invisible'},
1447
                'invalid': {'view': 'visible', 'edit': 'invisible'},
1448
                'rejected': {'view': 'invisible', 'edit': 'invisible'},
1449
            },
1450
        ),
1451
    ),
1452
1453
    RemarksField(
1454
        'Remarks',
1455
        searchable=True,
1456
        widget=RemarksWidget(
1457
            label=_("Remarks"),
1458
            description=_("Remarks and comments for this request"),
1459
            render_own_label=True,
1460
            visible={
1461
                'edit': 'visible',
1462
                'view': 'visible',
1463
                'add': 'edit'
1464
            },
1465
        ),
1466
    ),
1467
1468
    FixedPointField(
1469
        'MemberDiscount',
1470
        default_method='getDefaultMemberDiscount',
1471
        mode="rw",
1472
        read_permission=View,
1473
        write_permission=ModifyPortalContent,
1474
        widget=DecimalWidget(
1475
            label=_("Member discount %"),
1476
            description=_("Enter percentage value eg. 33.0"),
1477
            render_own_label=True,
1478
            visible={
1479
                'edit': 'visible',
1480
                'view': 'visible',
1481
                'add': 'invisible',
1482
                'sample_registered': {
1483
                    'view': 'invisible', 'edit': 'invisible'},
1484
            },
1485
        ),
1486
    ),
1487
    # TODO-catalog: move all these computed fields to methods
1488
    ComputedField(
1489
        'ClientUID',
1490
        expression='here.aq_parent.UID()',
1491
        widget=ComputedWidget(
1492
            visible=False,
1493
        ),
1494
    ),
1495
1496
    ComputedField(
1497
        'SampleTypeTitle',
1498
        expression="here.getSampleType().Title() if here.getSampleType() "
1499
                   "else ''",
1500
        widget=ComputedWidget(
1501
            visible=False,
1502
        ),
1503
    ),
1504
1505
    ComputedField(
1506
        'SamplePointTitle',
1507
        expression="here.getSamplePoint().Title() if here.getSamplePoint() "
1508
                   "else ''",
1509
        widget=ComputedWidget(
1510
            visible=False,
1511
        ),
1512
    ),
1513
1514
    ComputedField(
1515
        'SampleUID',
1516
        expression="here.getSample() and here.getSample().UID() or ''",
1517
        widget=ComputedWidget(
1518
            visible=False,
1519
        ),
1520
    ),
1521
1522
    ComputedField(
1523
        'SampleID',
1524
        expression="here.getSample() and here.getSample().getId() or ''",
1525
        widget=ComputedWidget(
1526
            visible=False,
1527
        ),
1528
    ),
1529
1530
    ComputedField(
1531
        'ContactUID',
1532
        expression="here.getContact() and here.getContact().UID() or ''",
1533
        widget=ComputedWidget(
1534
            visible=False,
1535
        ),
1536
    ),
1537
1538
    ComputedField(
1539
        'ProfilesUID',
1540
        expression="[p.UID() for p in here.getProfiles()] " \
1541
                   "if here.getProfiles() else []",
1542
        widget=ComputedWidget(
1543
            visible=False,
1544
        ),
1545
    ),
1546
1547
    ComputedField(
1548
        'Invoiced',
1549
        expression='here.getInvoice() and True or False',
1550
        default=False,
1551
        widget=ComputedWidget(
1552
            visible=False,
1553
        ),
1554
    ),
1555
    ComputedField(
1556
        'ReceivedBy',
1557
        expression='here.getReceivedBy()',
1558
        default='',
1559
        widget=ComputedWidget(visible=False,),
1560
    ),
1561
    ComputedField(
1562
        'CreatorFullName',
1563
        expression="here._getCreatorFullName()",
1564
        widget=ComputedWidget(visible=False),
1565
    ),
1566
    ComputedField(
1567
        'CreatorEmail',
1568
        expression="here._getCreatorEmail()",
1569
        widget=ComputedWidget(visible=False),
1570
    ),
1571
    ComputedField(
1572
        'SamplingRoundUID',
1573
        expression="here.getSamplingRound().UID() " \
1574
                   "if here.getSamplingRound() else ''",
1575
        widget=ComputedWidget(visible=False),
1576
    ),
1577
    ComputedField(
1578
        'SampleURL',
1579
        expression="here.getSample().absolute_url_path() " \
1580
                   "if here.getSample() else ''",
1581
        widget=ComputedWidget(visible=False),
1582
    ),
1583
    ComputedField(
1584
        'SamplerFullName',
1585
        expression="here._getSamplerFullName()",
1586
        widget=ComputedWidget(visible=False),
1587
    ),
1588
    ComputedField(
1589
        'SamplerEmail',
1590
        expression="here._getSamplerEmail()",
1591
        widget=ComputedWidget(visible=False),
1592
    ),
1593
    ComputedField(
1594
        'BatchID',
1595
        expression="here.getBatch().getId() if here.getBatch() else ''",
1596
        widget=ComputedWidget(visible=False),
1597
    ),
1598
    ComputedField(
1599
        'BatchURL',
1600
        expression="here.getBatch().absolute_url_path() " \
1601
                   "if here.getBatch() else ''",
1602
        widget=ComputedWidget(visible=False),
1603
    ),
1604
    ComputedField(
1605
        'ClientUID',
1606
        expression="here.getClient().UID() if here.getClient() else ''",
1607
        widget=ComputedWidget(visible=False),
1608
    ),
1609
    ComputedField(
1610
        'ClientTitle',
1611
        expression="here.getClient().Title() if here.getClient() else ''",
1612
        widget=ComputedWidget(visible=False),
1613
    ),
1614
    ComputedField(
1615
        'ClientURL',
1616
        expression="here.getClient().absolute_url_path() " \
1617
                   "if here.getClient() else ''",
1618
        widget=ComputedWidget(visible=False),
1619
    ),
1620
    ComputedField(
1621
        'ContactUsername',
1622
        expression="here.getContact().getUsername() " \
1623
                   "if here.getContact() else ''",
1624
        widget=ComputedWidget(visible=False),
1625
    ),
1626
    ComputedField(
1627
        'ContactFullName',
1628
        expression="here.getContact().getFullname() " \
1629
                   "if here.getContact() else ''",
1630
        widget=ComputedWidget(visible=False),
1631
    ),
1632
    ComputedField(
1633
        'ContactEmail',
1634
        expression="here.getContact().getEmailAddress() " \
1635
                   "if here.getContact() else ''",
1636
        widget=ComputedWidget(visible=False),
1637
    ),
1638
    ComputedField(
1639
        'SampleTypeUID',
1640
        expression="here.getSampleType().UID() " \
1641
                   "if here.getSampleType() else ''",
1642
        widget=ComputedWidget(visible=False),
1643
    ),
1644
    ComputedField(
1645
        'SamplePointUID',
1646
        expression="here.getSamplePoint().UID() " \
1647
                   "if here.getSamplePoint() else ''",
1648
        widget=ComputedWidget(visible=False),
1649
    ),
1650
    ComputedField(
1651
        'StorageLocationUID',
1652
        expression="here.getStorageLocation().UID() " \
1653
                   "if here.getStorageLocation() else ''",
1654
        widget=ComputedWidget(visible=False),
1655
    ),
1656
    ComputedField(
1657
        'ProfilesURL',
1658
        expression="[p.absolute_url_path() for p in here.getProfiles()] " \
1659
                   "if here.getProfiles() else []",
1660
        widget=ComputedWidget(visible=False),
1661
    ),
1662
    ComputedField(
1663
        'ProfilesTitle',
1664
        expression="[p.Title() for p in here.getProfiles()] " \
1665
                   "if here.getProfiles() else []",
1666
        widget=ComputedWidget(visible=False),
1667
    ),
1668
    ComputedField(
1669
        'ProfilesTitleStr',
1670
        expression="', '.join([p.Title() for p in here.getProfiles()]) " \
1671
                   "if here.getProfiles() else ''",
1672
        widget=ComputedWidget(visible=False),
1673
    ),
1674
    ComputedField(
1675
        'TemplateUID',
1676
        expression="here.getTemplate().UID() if here.getTemplate() else ''",
1677
        widget=ComputedWidget(visible=False),
1678
    ),
1679
    ComputedField(
1680
        'TemplateURL',
1681
        expression="here.getTemplate().absolute_url_path() " \
1682
                   "if here.getTemplate() else ''",
1683
        widget=ComputedWidget(visible=False),
1684
    ),
1685
    ComputedField(
1686
        'TemplateTitle',
1687
        expression="here.getTemplate().Title() if here.getTemplate() else ''",
1688
        widget=ComputedWidget(visible=False),
1689
    ),
1690
1691
    ReferenceField(
1692
        'ParentAnalysisRequest',
1693
        allowed_types=('AnalysisRequest',),
1694
        relationship='AnalysisRequestParentAnalysisRequest',
1695
        referenceClass=HoldingReference,
1696
        mode="rw",
1697
        read_permission=View,
1698
        write_permission=ModifyPortalContent,
1699
        widget=ReferenceWidget(
1700
            visible=False,
1701
        ),
1702
    ),
1703
1704
    # The Analysis Request the current Analysis Request comes from because of
1705
    # an invalidation of the former
1706
    ReferenceField(
1707
        'Invalidated',
1708
        allowed_types=('AnalysisRequest',),
1709
        relationship='AnalysisRequestRetracted',
1710
        referenceClass=HoldingReference,
1711
        mode="rw",
1712
        read_permission=View,
1713
        write_permission=ModifyPortalContent,
1714
        widget=ReferenceWidget(
1715
            visible=False,
1716
        ),
1717
    ),
1718
1719
    # The Analysis Request that was automatically generated due to the
1720
    # invalidation of the current Analysis Request
1721
    ComputedField(
1722
        'Retest',
1723
        expression="here.get_retest()",
1724
        widget=ComputedWidget(visible=False)
1725
    ),
1726
1727
    # For comments or results interpretation
1728
    # Old one, to be removed because of the incorporation of
1729
    # ResultsInterpretationDepts (due to LIMS-1628)
1730
    TextField(
1731
        'ResultsInterpretation',
1732
        mode="rw",
1733
        default_content_type='text/html',
1734
        # Input content type for the textfield
1735
        default_output_type='text/x-html-safe',
1736
        # getResultsInterpretation returns a str with html tags
1737
        # to conserve the txt format in the report.
1738
        read_permission=View,
1739
        write_permission=ModifyPortalContent,
1740
        widget=RichWidget(
1741
            description=_("Comments or results interpretation"),
1742
            label=_("Results Interpretation"),
1743
            size=10,
1744
            allow_file_upload=False,
1745
            default_mime_type='text/x-rst',
1746
            output_mime_type='text/x-html',
1747
            rows=3,
1748
            visible=False),
1749
    ),
1750
1751
    RecordsField('ResultsInterpretationDepts',
1752
                 subfields=('uid',
1753
                            'richtext'),
1754
                 subfield_labels={'uid': _('Department'),
1755
                                  'richtext': _('Results Interpreation')},
1756
                 widget=RichWidget(visible=False),
1757
                 ),
1758
    # Custom settings for the assigned analysis services
1759
    # https://jira.bikalabs.com/browse/LIMS-1324
1760
    # Fields:
1761
    #   - uid: Analysis Service UID
1762
    #   - hidden: True/False. Hide/Display in results reports
1763
    RecordsField('AnalysisServicesSettings',
1764
                 required=0,
1765
                 subfields=('uid', 'hidden',),
1766
                 widget=ComputedWidget(visible=False),
1767
                 ),
1768
    StringField(
1769
        'Printed',
1770
        mode="rw",
1771
        read_permission=View,
1772
        widget=StringWidget(
1773
            label=_("Printed"),
1774
            description=_("Indicates if the last ARReport is printed,"),
1775
            visible={'view': 'invisible',
1776
                     'edit': 'invisible'},
1777
        ),
1778
    ),
1779
)
1780
)
1781
1782
1783
# Some schema rearrangement
1784
schema['title'].required = False
1785
schema['id'].widget.visible = {
1786
    'edit': 'invisible',
1787
    'view': 'invisible',
1788
}
1789
schema['title'].widget.visible = {
1790
    'edit': 'invisible',
1791
    'view': 'invisible',
1792
}
1793
schema.moveField('Client', before='Contact')
1794
schema.moveField('ResultsInterpretation', pos='bottom')
1795
schema.moveField('ResultsInterpretationDepts', pos='bottom')
1796
1797
1798
class AnalysisRequest(BaseFolder):
1799
    implements(IAnalysisRequest)
1800
    security = ClassSecurityInfo()
1801
    displayContentsTab = False
1802
    schema = schema
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable schema does not seem to be defined.
Loading history...
1803
1804
    _at_rename_after_creation = True
1805
1806
    def _renameAfterCreation(self, check_auto_id=False):
1807
        from bika.lims.idserver import renameAfterCreation
1808
        renameAfterCreation(self)
1809
1810
    def _getCatalogTool(self):
1811
        from bika.lims.catalog import getCatalog
1812
        return getCatalog(self)
1813
1814
    def Title(self):
1815
        """ Return the Request ID as title """
1816
        return self.getId()
1817
1818
    def sortable_title(self):
1819
        """
1820
        Some lists expects this index
1821
        """
1822
        return self.getId()
1823
1824
    def Description(self):
1825
        """Returns searchable data as Description"""
1826
        descr = " ".join((self.getId(), self.aq_parent.Title()))
1827
        return safe_unicode(descr).encode('utf-8')
1828
1829
    def getClient(self):
1830
        if self.aq_parent.portal_type == 'Client':
1831
            return self.aq_parent
1832
        if self.aq_parent.portal_type == 'Batch':
1833
            return self.aq_parent.getClient()
1834
        return ''
1835
1836
    def getClientPath(self):
1837
        return "/".join(self.aq_parent.getPhysicalPath())
1838
1839
    def getProfilesTitle(self):
1840
        return [profile.Title() for profile in self.getProfiles()]
1841
1842
    def setPublicationSpecification(self, value):
1843
        """Never contains a value; this field is here for the UI." \
1844
        """
1845
        return value
1846
1847
    def getAnalysisService(self):
1848
        proxies = self.getAnalyses(full_objects=False)
1849
        value = set()
1850
        for proxy in proxies:
1851
            value.add(proxy.Title)
1852
        return list(value)
1853
1854
    def getAnalysts(self):
1855
        proxies = self.getAnalyses(full_objects=True)
1856
        value = []
1857
        for proxy in proxies:
1858
            val = proxy.getAnalyst()
1859
            if val not in value:
1860
                value.append(val)
1861
        return value
1862
1863
    def getDistrict(self):
1864
        client = self.aq_parent
1865
        return client.getDistrict()
1866
1867
    def getProvince(self):
1868
        client = self.aq_parent
1869
        return client.getProvince()
1870
1871
    @security.public
1872
    def getBatch(self):
1873
        # The parent type may be "Batch" during ar_add.
1874
        # This function fills the hidden field in ar_add.pt
1875
        if self.aq_parent.portal_type == 'Batch':
1876
            return self.aq_parent
1877
        else:
1878
            return self.Schema()['Batch'].get(self)
1879
1880
    @security.public
1881
    def getBatchUID(self):
1882
        batch = self.getBatch()
1883
        if batch:
1884
            return batch.UID()
1885
1886
    @security.public
1887
    def setBatch(self, value=None):
1888
        original_value = self.Schema().getField('Batch').get(self)
1889
        if original_value != value:
1890
            self.Schema().getField('Batch').set(self, value)
1891
            self._reindexAnalyses(['getBatchUID'], False)
1892
1893
    def getDefaultMemberDiscount(self):
1894
        """Compute default member discount if it applies
1895
        """
1896
        if hasattr(self, 'getMemberDiscountApplies'):
1897
            if self.getMemberDiscountApplies():
1898
                settings = self.bika_setup
1899
                return settings.getMemberDiscount()
1900
            else:
1901
                return "0.00"
1902
1903
    @security.public
1904
    def getAnalysesNum(self):
1905
        """ Returns an array with the number of analyses for the current AR in
1906
            different statuses, like follows:
1907
                [verified, total, not_submitted, to_be_verified]
1908
        """
1909
        an_nums = [0, 0, 0, 0]
1910
        for analysis in self.getAnalyses():
1911
            review_state = analysis.review_state
1912
            if review_state in ['retracted', 'rejected']:
1913
                # Discard retracted analyses
1914
                continue
1915
1916
            if not api.is_active(analysis):
1917
                # Discard non-active analyses
1918
                continue
1919
1920
            analysis_object = api.get_object(analysis)
1921
            actions = getReviewHistoryActionsList(analysis_object)
1922
            if 'verify' in actions:
1923
                # Assume the "last" state of analysis is verified
1924
                index = 0
1925
1926
            elif 'submit' not in actions or review_state != 'to_be_verified':
1927
                # Assume the "first" state of analysis is results_pending
1928
                index = 2
1929
1930
            else:
1931
                index = 3
1932
            an_nums[index] += 1
1933
            an_nums[1] += 1
1934
        return an_nums
1935
1936
    @security.public
1937
    def getResponsible(self):
1938
        """Return all manager info of responsible departments
1939
        """
1940
        managers = {}
1941
        for department in self.getDepartments():
1942
            manager = department.getManager()
1943
            if manager is None:
1944
                continue
1945
            manager_id = manager.getId()
1946
            if manager_id not in managers:
1947
                managers[manager_id] = {}
1948
                managers[manager_id]['salutation'] = safe_unicode(
1949
                    manager.getSalutation())
1950
                managers[manager_id]['name'] = safe_unicode(
1951
                    manager.getFullname())
1952
                managers[manager_id]['email'] = safe_unicode(
1953
                    manager.getEmailAddress())
1954
                managers[manager_id]['phone'] = safe_unicode(
1955
                    manager.getBusinessPhone())
1956
                managers[manager_id]['job_title'] = safe_unicode(
1957
                    manager.getJobTitle())
1958
                if manager.getSignature():
1959
                    managers[manager_id]['signature'] = \
1960
                        '{}/Signature'.format(manager.absolute_url())
1961
                else:
1962
                    managers[manager_id]['signature'] = False
1963
                managers[manager_id]['departments'] = ''
1964
            mngr_dept = managers[manager_id]['departments']
1965
            if mngr_dept:
1966
                mngr_dept += ', '
1967
            mngr_dept += safe_unicode(department.Title())
1968
            managers[manager_id]['departments'] = mngr_dept
1969
        mngr_keys = managers.keys()
1970
        mngr_info = {'ids': mngr_keys, 'dict': managers}
1971
1972
        return mngr_info
1973
1974
    @security.public
1975
    def getManagers(self):
1976
        """Return all managers of responsible departments
1977
        """
1978
        manager_ids = []
1979
        manager_list = []
1980
        for department in self.getDepartments():
1981
            manager = department.getManager()
1982
            if manager is None:
1983
                continue
1984
            manager_id = manager.getId()
1985
            if manager_id not in manager_ids:
1986
                manager_ids.append(manager_id)
1987
                manager_list.append(manager)
1988
        return manager_list
1989
1990
    def getDueDate(self):
1991
        """Returns the earliest due date of the analyses this Analysis Request
1992
        contains."""
1993
        due_dates = map(lambda an: an.getDueDate, self.getAnalyses())
1994
        return due_dates and min(due_dates) or None
1995
1996
    security.declareProtected(View, 'getLate')
1997
1998
    def getLate(self):
1999
        """Return True if there is at least one late analysis in this Request
2000
        """
2001
        for analysis in self.getAnalyses():
2002
            if analysis.review_state == "retracted":
2003
                continue
2004
            analysis_obj = api.get_object(analysis)
2005
            if analysis_obj.isLateAnalysis():
2006
                return True
2007
        return False
2008
2009
    def getPrinted(self):
2010
        """ returns "0", "1" or "2" to indicate Printed state.
2011
            0 -> Never printed.
2012
            1 -> Printed after last publish
2013
            2 -> Printed but republished afterwards.
2014
        """
2015
        workflow = getToolByName(self, 'portal_workflow')
2016
        review_state = workflow.getInfoFor(self, 'review_state', '')
2017
        if review_state not in ['published']:
2018
            return "0"
2019
        report_list = sorted(self.objectValues('ARReport'),
2020
                             key=lambda report: report.getDatePublished())
2021
        if not report_list:
2022
            return "0"
2023
        last_report = report_list[-1]
2024
        if last_report.getDatePrinted():
2025
            return "1"
2026
        else:
2027
            for report in report_list:
2028
                if report.getDatePrinted():
2029
                    return "2"
2030
        return "0"
2031
2032
    def printLastReport(self):
2033
        """Setting Printed Time of the last report, so its Printed value will be 1
2034
        """
2035
        workflow = getToolByName(self, 'portal_workflow')
2036
        review_state = workflow.getInfoFor(self, 'review_state', '')
2037
        if review_state not in ['published']:
2038
            return
2039
        last_report = sorted(self.objectValues('ARReport'),
2040
                             key=lambda report: report.getDatePublished())[-1]
2041
        if last_report and not last_report.getDatePrinted():
2042
            last_report.setDatePrinted(DateTime())
2043
            self.reindexObject(idxs=['getPrinted'])
2044
2045
    security.declareProtected(View, 'getBillableItems')
2046
2047
    def getBillableItems(self):
2048
        """Returns the items to be billed
2049
        """
2050
        def get_keywords_set(profiles):
2051
            keys = list()
2052
            for profile in profiles:
2053
                keys += map(lambda s: s.getKeyword(), profile.getService())
2054
            return set(keys)
2055
2056
        # Profiles with a fixed price, regardless of their analyses
2057
        profiles = self.getProfiles()
2058
        billable_items = filter(lambda pr: pr.getUseAnalysisProfilePrice(),
2059
                                 profiles)
2060
        # Profiles w/o a fixed price. The price is the sum of the individual
2061
        # price for each analysis
2062
        non_billable = filter(lambda p: p not in billable_items, profiles)
2063
        billable_keys = get_keywords_set(non_billable) - \
2064
                        get_keywords_set(billable_items)
2065
2066
        # Get the analyses to be billed
2067
        exclude_rs = ['retracted', 'rejected']
2068
        for analysis in self.getAnalyses(cancellation_state="active"):
2069
            if analysis.review_state in exclude_rs:
2070
                continue
2071
            if analysis.getKeyword not in billable_keys:
2072
                continue
2073
            billable_items.append(api.get_object(analysis))
2074
2075
        # Return the analyses that need to be billed individually, together with
2076
        # the profiles with a fixed price
2077
        return billable_items
2078
2079
    # TODO Cleanup - Remove this function, only used in invoice and too complex
2080
    def getServicesAndProfiles(self):
2081
        """This function gets all analysis services and all profiles and removes
2082
        the services belonging to a profile.
2083
2084
        :returns: a tuple of three lists, where the first list contains the
2085
        analyses and the second list the profiles.
2086
        The third contains the analyses objects used by the profiles.
2087
        """
2088
        # profile_analyses contains the profile's analyses (analysis !=
2089
        # service") objects to obtain
2090
        # the correct price later
2091
        exclude_rs = ['retracted', 'rejected']
2092
        analyses = filter(lambda an: an.review_state not in exclude_rs,
2093
                          self.getAnalyses(cancellation_state='active'))
2094
        analyses = map(api.get_object, analyses)
2095
        profiles = self.getProfiles()
2096
2097
        # Get the service keys from all profiles
2098
        profiles_keys = list()
2099
        for profile in profiles:
2100
            profile_keys = map(lambda s: s.getKeyword(), profile.getService())
2101
            profiles_keys.extend(profile_keys)
2102
2103
        # Extract the analyses which service is present in at least one profile
2104
        # and those not present (orphan)
2105
        profile_analyses = list()
2106
        orphan_analyses = list()
2107
        for an in analyses:
2108
            if an.getKeyword() in profiles_keys:
2109
                profile_analyses.append(an)
2110
            else:
2111
                orphan_analyses.append(an)
2112
        return analyses, profiles, profile_analyses
2113
2114
    security.declareProtected(View, 'getSubtotal')
2115
2116
    def getSubtotal(self):
2117
        """Compute Subtotal (without member discount and without vat)
2118
        """
2119
        return sum([Decimal(obj.getPrice()) for obj in self.getBillableItems()])
2120
2121
    security.declareProtected(View, 'getSubtotalVATAmount')
2122
2123
    def getSubtotalVATAmount(self):
2124
        """Compute VAT amount without member discount
2125
        """
2126
        return sum([Decimal(o.getVATAmount()) for o in self.getBillableItems()])
2127
2128
    security.declareProtected(View, 'getSubtotalTotalPrice')
2129
2130
    def getSubtotalTotalPrice(self):
2131
        """Compute the price with VAT but no member discount
2132
        """
2133
        return self.getSubtotal() + self.getSubtotalVATAmount()
2134
2135
    security.declareProtected(View, 'getDiscountAmount')
2136
2137
    def getDiscountAmount(self):
2138
        """It computes and returns the analysis service's discount amount
2139
        without VAT
2140
        """
2141
        has_client_discount = self.aq_parent.getMemberDiscountApplies()
2142
        if has_client_discount:
2143
            discount = Decimal(self.getDefaultMemberDiscount())
2144
            return Decimal(self.getSubtotal() * discount / 100)
2145
        else:
2146
            return 0
2147
2148
    def getVATAmount(self):
2149
        """It computes the VAT amount from (subtotal-discount.)*VAT/100, but
2150
        each analysis has its own VAT!
2151
2152
        :returns: the analysis request VAT amount with the discount
2153
        """
2154
        has_client_discount = self.aq_parent.getMemberDiscountApplies()
2155
        VATAmount = self.getSubtotalVATAmount()
2156
        if has_client_discount:
2157
            discount = Decimal(self.getDefaultMemberDiscount())
2158
            return Decimal((1 - discount / 100) * VATAmount)
2159
        else:
2160
            return VATAmount
2161
2162
    security.declareProtected(View, 'getTotalPrice')
2163
2164
    def getTotalPrice(self):
2165
        """It gets the discounted price from analyses and profiles to obtain the
2166
        total value with the VAT and the discount applied
2167
2168
        :returns: analysis request's total price including VATs and discounts
2169
        """
2170
        price = (self.getSubtotal() - self.getDiscountAmount() +
2171
                 self.getVATAmount())
2172
        return price
2173
2174
    getTotal = getTotalPrice
2175
2176
    security.declareProtected(ManageInvoices, 'issueInvoice')
2177
2178
    # noinspection PyUnusedLocal
2179
    def issueInvoice(self, REQUEST=None, RESPONSE=None):
2180
        """Issue invoice
2181
        """
2182
        # check for an adhoc invoice batch for this month
2183
        # noinspection PyCallingNonCallable
2184
        now = DateTime()
2185
        batch_month = now.strftime('%b %Y')
2186
        batch_title = '%s - %s' % (batch_month, 'ad hoc')
2187
        invoice_batch = None
2188
        for b_proxy in self.portal_catalog(portal_type='InvoiceBatch',
2189
                                           Title=batch_title):
2190
            invoice_batch = b_proxy.getObject()
2191
        if not invoice_batch:
2192
            # noinspection PyCallingNonCallable
2193
            first_day = DateTime(now.year(), now.month(), 1)
2194
            start_of_month = first_day.earliestTime()
2195
            last_day = first_day + 31
2196
            # noinspection PyUnresolvedReferences
2197
            while last_day.month() != now.month():
2198
                last_day -= 1
2199
            # noinspection PyUnresolvedReferences
2200
            end_of_month = last_day.latestTime()
2201
2202
            invoices = self.invoices
2203
            batch_id = invoices.generateUniqueId('InvoiceBatch')
2204
            invoice_batch = _createObjectByType("InvoiceBatch", invoices,
2205
                                                batch_id)
2206
            invoice_batch.edit(
2207
                title=batch_title,
2208
                BatchStartDate=start_of_month,
2209
                BatchEndDate=end_of_month,
2210
            )
2211
            invoice_batch.processForm()
2212
2213
        client_uid = self.getClientUID()
2214
        # Get the created invoice
2215
        invoice = invoice_batch.createInvoice(client_uid, [self, ])
2216
        invoice.setAnalysisRequest(self)
2217
        # Set the created invoice in the schema
2218
        self.Schema()['Invoice'].set(self, invoice)
2219
2220
    security.declarePublic('printInvoice')
2221
2222
    # noinspection PyUnusedLocal
2223
    def printInvoice(self, REQUEST=None, RESPONSE=None):
2224
        """Print invoice
2225
        """
2226
        invoice = self.getInvoice()
2227
        invoice_url = invoice.absolute_url()
2228
        RESPONSE.redirect('{}/invoice_print'.format(invoice_url))
2229
2230
    @deprecated("addARAttachment will be removed in senaite.core 1.3.0")
2231
    def addARAttachment(self, REQUEST=None, RESPONSE=None):
2232
        """Add the file as an attachment
2233
        """
2234
        workflow = getToolByName(self, 'portal_workflow')
2235
2236
        this_file = self.REQUEST.form['AttachmentFile_file']
2237
        if 'Analysis' in self.REQUEST.form:
2238
            analysis_uid = self.REQUEST.form['Analysis']
2239
        else:
2240
            analysis_uid = None
2241
2242
        attachmentid = self.generateUniqueId('Attachment')
2243
        attachment = _createObjectByType("Attachment", self.aq_parent,
2244
                                         attachmentid)
2245
        attachment.edit(
2246
            AttachmentFile=this_file,
2247
            AttachmentType=self.REQUEST.form.get('AttachmentType', ''),
2248
            AttachmentKeys=self.REQUEST.form['AttachmentKeys'])
2249
        attachment.processForm()
2250
        attachment.reindexObject()
2251
2252
        if analysis_uid:
2253
            tool = getToolByName(self, REFERENCE_CATALOG)
2254
            analysis = tool.lookupObject(analysis_uid)
2255
            others = analysis.getAttachment()
2256
            attachments = []
2257
            for other in others:
2258
                attachments.append(other.UID())
2259
            attachments.append(attachment.UID())
2260
            analysis.setAttachment(attachments)
2261
        else:
2262
            others = self.getAttachment()
2263
            attachments = []
2264
            for other in others:
2265
                attachments.append(other.UID())
2266
            attachments.append(attachment.UID())
2267
2268
            self.setAttachment(attachments)
2269
2270
        if REQUEST['HTTP_REFERER'].endswith('manage_results'):
2271
            RESPONSE.redirect('{}/manage_results'.format(self.absolute_url()))
2272
        else:
2273
            RESPONSE.redirect(self.absolute_url())
2274
2275
    @deprecated("delARAttachment will be removed in senaite.core 1.3.0")
2276
    def delARAttachment(self, REQUEST=None, RESPONSE=None):
2277
        """Delete the attachment
2278
        """
2279
        tool = getToolByName(self, REFERENCE_CATALOG)
2280
        if 'Attachment' in self.REQUEST.form:
2281
            attachment_uid = self.REQUEST.form['Attachment']
2282
            attachment = tool.lookupObject(attachment_uid)
2283
            parent_r = attachment.getRequest()
2284
            parent_a = attachment.getAnalysis()
2285
2286
            parent = parent_a if parent_a else parent_r
2287
            others = parent.getAttachment()
2288
            attachments = []
2289
            for other in others:
2290
                if not other.UID() == attachment_uid:
2291
                    attachments.append(other.UID())
2292
            parent.setAttachment(attachments)
2293
            client = attachment.aq_parent
2294
            ids = [attachment.getId(), ]
2295
            BaseFolder.manage_delObjects(client, ids, REQUEST)
2296
2297
        RESPONSE.redirect(self.REQUEST.get_header('referer'))
2298
2299
    security.declarePublic('getVerifier')
2300
2301
    @deprecated("Use getVerifiers instead")
2302
    def getVerifier(self):
2303
        """Returns the user that verified the whole Analysis Request. Since the
2304
        verification is done automatically as soon as all the analyses it
2305
        contains are verified, this function returns the user that verified the
2306
        last analysis pending.
2307
        """
2308
        wtool = getToolByName(self, 'portal_workflow')
2309
        mtool = getToolByName(self, 'portal_membership')
2310
2311
        verifier = None
2312
        # noinspection PyBroadException
2313
        try:
2314
            review_history = wtool.getInfoFor(self, 'review_history')
2315
        except:  # noqa FIXME: remove blind except!
2316
            return 'access denied'
2317
2318
        if not review_history:
2319
            return 'no history'
2320
        for items in review_history:
2321
            action = items.get('action')
2322
            if action != 'verify':
2323
                continue
2324
            actor = items.get('actor')
2325
            member = mtool.getMemberById(actor)
2326
            verifier = member.getProperty('fullname')
2327
            if verifier is None or verifier == '':
2328
                verifier = actor
2329
        return verifier
2330
2331
    @security.public
2332
    def getVerifiersIDs(self):
2333
        """Returns the ids from users that have verified at least one analysis
2334
        from this Analysis Request
2335
        """
2336
        verifiers_ids = list()
2337
        for brain in self.getAnalyses():
2338
            verifiers_ids += brain.getVerificators
2339
        return list(set(verifiers_ids))
2340
2341
    @security.public
2342
    def getVerifiers(self):
2343
        """Returns the list of lab contacts that have verified at least one
2344
        analysis from this Analysis Request
2345
        """
2346
        contacts = list()
2347
        for verifier in self.getVerifiersIDs():
2348
            user = api.get_user(verifier)
2349
            contact = api.get_user_contact(user, ["LabContact"])
2350
            if contact:
2351
                contacts.append(contact)
2352
        return contacts
2353
2354
    security.declarePublic('getContactUIDForUser')
2355
2356
    def getContactUIDForUser(self):
2357
        """get the UID of the contact associated with the authenticated user
2358
        """
2359
        mt = getToolByName(self, 'portal_membership')
2360
        user = mt.getAuthenticatedMember()
2361
        user_id = user.getUserName()
2362
        pc = getToolByName(self, 'portal_catalog')
2363
        r = pc(portal_type='Contact',
2364
               getUsername=user_id)
2365
        if len(r) == 1:
2366
            return r[0].UID
2367
2368
    security.declarePublic('current_date')
2369
2370
    def current_date(self):
2371
        """return current date
2372
        """
2373
        # noinspection PyCallingNonCallable
2374
        return DateTime()
2375
2376
    def getQCAnalyses(self, qctype=None, review_state=None):
2377
        """return the QC analyses performed in the worksheet in which, at
2378
        least, one sample of this AR is present.
2379
2380
        Depending on qctype value, returns the analyses of:
2381
2382
            - 'b': all Blank Reference Samples used in related worksheet/s
2383
            - 'c': all Control Reference Samples used in related worksheet/s
2384
            - 'd': duplicates only for samples contained in this AR
2385
2386
        If qctype==None, returns all type of qc analyses mentioned above
2387
        """
2388
        qcanalyses = []
2389
        suids = []
2390
        ans = self.getAnalyses()
2391
        wf = getToolByName(self, 'portal_workflow')
2392
        for an in ans:
2393
            an = an.getObject()
2394
            if an.getServiceUID() not in suids:
2395
                suids.append(an.getServiceUID())
2396
2397
        def valid_dup(wan):
2398
            if wan.portal_type == 'ReferenceAnalysis':
2399
                return False
2400
            an_state = wf.getInfoFor(wan, 'review_state')
2401
            return \
2402
                wan.portal_type == 'DuplicateAnalysis' \
2403
                and wan.getRequestID() == self.id \
2404
                and (review_state is None or an_state in review_state)
2405
2406
        def valid_ref(wan):
2407
            if wan.portal_type != 'ReferenceAnalysis':
2408
                return False
2409
            an_state = wf.getInfoFor(wan, 'review_state')
2410
            an_reftype = wan.getReferenceType()
2411
            return wan.getServiceUID() in suids \
2412
                and wan not in qcanalyses \
2413
                and (qctype is None or an_reftype == qctype) \
2414
                and (review_state is None or an_state in review_state)
2415
2416
        for an in ans:
2417
            an = an.getObject()
2418
            ws = an.getWorksheet()
2419
            if not ws:
2420
                continue
2421
            was = ws.getAnalyses()
2422
            for wa in was:
2423
                if valid_dup(wa):
2424
                    qcanalyses.append(wa)
2425
                elif valid_ref(wa):
2426
                    qcanalyses.append(wa)
2427
2428
        return qcanalyses
2429
2430
    def isInvalid(self):
2431
        """return if the Analysis Request has been invalidated
2432
        """
2433
        workflow = getToolByName(self, 'portal_workflow')
2434
        return workflow.getInfoFor(self, 'review_state') == 'invalid'
2435
2436
    def getSamplingRoundUID(self):
2437
        """Obtains the sampling round UID
2438
        :returns: UID
2439
        """
2440
        sr = self.getSamplingRound()
2441
        if sr:
2442
            return sr.UID()
2443
        else:
2444
            return ''
2445
2446
    def getStorageLocationTitle(self):
2447
        """ A method for AR listing catalog metadata
2448
        :return: Title of Storage Location
2449
        """
2450
        sl = self.getStorageLocation()
2451
        if sl:
2452
            return sl.Title()
2453
        return ''
2454
2455
    @security.public
2456
    def getResultsRange(self):
2457
        """Returns the valid result ranges for the analyses this Analysis
2458
        Request contains.
2459
2460
        By default uses the result ranges defined in the Analysis Specification
2461
        set in "Specification" field if any. Values manually set through
2462
        `ResultsRange` field for any given analysis keyword have priority over
2463
        the result ranges defined in "Specification" field.
2464
2465
        :return: A list of dictionaries, where each dictionary defines the
2466
            result range to use for any analysis contained in this Analysis
2467
            Request for the keyword specified. Each dictionary has, at least,
2468
                the following keys: "keyword", "min", "max"
2469
        :rtype: dict
2470
        """
2471
        specs_range = []
2472
        specification = self.getSpecification()
2473
        if specification:
2474
            specs_range = specification.getResultsRange()
2475
            specs_range = specs_range and specs_range or []
2476
2477
        # Override with AR's custom ranges
2478
        ar_range = self.Schema().getField("ResultsRange").get(self)
2479
        if not ar_range:
2480
            return specs_range
2481
2482
        # Remove those analysis ranges that neither min nor max are floatable
2483
        an_specs = [an for an in ar_range if
2484
                    api.is_floatable(an.get('min', None)) or
2485
                    api.is_floatable(an.get('max', None))]
2486
        # Want to know which are the analyses that needs to be overriden
2487
        keywords = map(lambda item: item.get('keyword'), an_specs)
2488
        # Get rid of those analyses to be overriden
2489
        out_specs = [sp for sp in specs_range if sp['keyword'] not in keywords]
2490
        # Add manually set ranges
2491
        out_specs.extend(an_specs)
2492
        return map(lambda spec: ResultsRangeDict(spec), out_specs)
2493
2494
    def getDatePublished(self):
2495
        """
2496
        Returns the transition date from the Analysis Request object
2497
        """
2498
        return getTransitionDate(self, 'publish', return_as_datetime=True)
2499
2500
    security.declarePublic('getSamplingDeviationTitle')
2501
2502
    def getSamplingDeviationTitle(self):
2503
        """
2504
        It works as a metacolumn.
2505
        """
2506
        sd = self.getSamplingDeviation()
2507
        if sd:
2508
            return sd.Title()
2509
        return ''
2510
2511
    security.declarePublic('getHazardous')
2512
2513
    def getHazardous(self):
2514
        """
2515
        It works as a metacolumn.
2516
        """
2517
        sample_type = self.getSampleType()
2518
        if sample_type:
2519
            return sample_type.getHazardous()
2520
        return False
2521
2522
    security.declarePublic('getContactURL')
2523
2524
    def getContactURL(self):
2525
        """
2526
        It works as a metacolumn.
2527
        """
2528
        contact = self.getContact()
2529
        if contact:
2530
            return contact.absolute_url_path()
2531
        return ''
2532
2533
    security.declarePublic('getSamplingWorkflowEnabled')
2534
2535
    def getSamplingWorkflowEnabled(self):
2536
        """
2537
        It works as a metacolumn.
2538
        """
2539
        sample = self.getSample()
2540
        if sample:
2541
            return sample.getSamplingWorkflowEnabled()
2542
        return ''
2543
2544
    def getSamplers(self):
2545
        return getUsers(self, ['Sampler', ])
2546
2547
    def getDepartments(self):
2548
        """Returns a list of the departments assigned to the Analyses
2549
        from this Analysis Request
2550
        """
2551
        bsc = getToolByName(self, 'bika_setup_catalog')
2552
        dept_uids = []
2553
        for an in self.getAnalyses():
2554
            deptuid = None
2555
            if hasattr(an, 'getDepartmentUID'):
2556
                deptuid = an.getDepartmentUID
2557
            if not deptuid and hasattr(an, 'getObject'):
2558
                deptuid = an.getObject().getDepartmentUID()
2559
            if deptuid:
2560
                dept_uids.append(deptuid)
2561
        brains = bsc(portal_type='Department', UID=dept_uids)
2562
        depts = [b.getObject() for b in brains]
2563
        return list(set(depts))
2564
2565
    def getDepartmentUIDs(self):
2566
        """Return a list of the UIDs of departments assigned to the Analyses
2567
        from this Analysis Request.
2568
        """
2569
        return [dept.UID() for dept in self.getDepartments()]
2570
2571
    def getResultsInterpretationByDepartment(self, department=None):
2572
        """Returns the results interpretation for this Analysis Request
2573
           and department. If department not set, returns the results
2574
           interpretation tagged as 'General'.
2575
2576
        :returns: a dict with the following keys:
2577
            {'uid': <department_uid> or 'general', 'richtext': <text/plain>}
2578
        """
2579
        uid = department.UID() if department else 'general'
2580
        rows = self.Schema()['ResultsInterpretationDepts'].get(self)
2581
        row = [row for row in rows if row.get('uid') == uid]
2582
        if len(row) > 0:
2583
            row = row[0]
2584
        elif uid == 'general' \
2585
                and hasattr(self, 'getResultsInterpretation') \
2586
                and self.getResultsInterpretation():
2587
            row = {'uid': uid, 'richtext': self.getResultsInterpretation()}
2588
        else:
2589
            row = {'uid': uid, 'richtext': ''}
2590
        return row
2591
2592
    def getAnalysisServiceSettings(self, uid):
2593
        """Returns a dictionary with the settings for the analysis service that
2594
        match with the uid provided.
2595
2596
        If there are no settings for the analysis service and
2597
        analysis requests:
2598
2599
        1. looks for settings in AR's ARTemplate. If found, returns the
2600
           settings for the AnalysisService set in the Template
2601
        2. If no settings found, looks in AR's ARProfile. If found, returns the
2602
           settings for the AnalysisService from the AR Profile. Otherwise,
2603
           returns a one entry dictionary with only the key 'uid'
2604
        """
2605
        sets = [s for s in self.getAnalysisServicesSettings()
2606
                if s.get('uid', '') == uid]
2607
2608
        # Created by using an ARTemplate?
2609
        if not sets and self.getTemplate():
2610
            adv = self.getTemplate().getAnalysisServiceSettings(uid)
2611
            sets = [adv] if 'hidden' in adv else []
2612
2613
        # Created by using an AR Profile?
2614
        if not sets and self.getProfiles():
2615
            adv = []
2616
            adv += [profile.getAnalysisServiceSettings(uid) for profile in
2617
                    self.getProfiles()]
2618
            sets = adv if 'hidden' in adv[0] else []
2619
2620
        return sets[0] if sets else {'uid': uid}
2621
2622
    def getPartitions(self):
2623
        """This functions returns the partitions from the analysis request's
2624
        analyses.
2625
2626
        :returns: a list with the full partition objects
2627
        """
2628
        partitions = []
2629
        for analysis in self.getAnalyses(full_objects=True):
2630
            if analysis.getSamplePartition() not in partitions:
2631
                partitions.append(analysis.getSamplePartition())
2632
        return partitions
2633
2634
    def getContainers(self):
2635
        """This functions returns the containers from the analysis request's
2636
        analyses
2637
2638
        :returns: a list with the full partition objects
2639
        """
2640
        partitions = self.getPartitions()
2641
        containers = []
2642
        for partition in partitions:
2643
            if partition.getContainer():
2644
                containers.append(partition.getContainer())
2645
        return containers
2646
2647 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...
2648
        """Checks if the analysis service that match with the uid provided must
2649
        be hidden in results. If no hidden assignment has been set for the
2650
        analysis in this request, returns the visibility set to the analysis
2651
        itself.
2652
2653
        Raise a TypeError if the uid is empty or None
2654
2655
        Raise a ValueError if there is no hidden assignment in this request or
2656
        no analysis service found for this uid.
2657
        """
2658
        if not uid:
2659
            raise TypeError('None type or empty uid')
2660
        sets = self.getAnalysisServiceSettings(uid)
2661
        if 'hidden' not in sets:
2662
            uc = getToolByName(self, 'uid_catalog')
2663
            serv = uc(UID=uid)
2664
            if serv and len(serv) == 1:
2665
                return serv[0].getObject().getRawHidden()
2666
            else:
2667
                raise ValueError('{} is not valid'.format(uid))
2668
        return sets.get('hidden', False)
2669
2670
    def getRejecter(self):
2671
        """If the Analysis Request has been rejected, returns the user who did the
2672
        rejection. If it was not rejected or the current user has not enough
2673
        privileges to access to this information, returns None.
2674
        """
2675
        wtool = getToolByName(self, 'portal_workflow')
2676
        mtool = getToolByName(self, 'portal_membership')
2677
        # noinspection PyBroadException
2678
        try:
2679
            review_history = wtool.getInfoFor(self, 'review_history')
2680
        except:  # noqa FIXME: remove blind except!
2681
            return None
2682
        for items in review_history:
2683
            action = items.get('action')
2684
            if action != 'reject':
2685
                continue
2686
            actor = items.get('actor')
2687
            return mtool.getMemberById(actor)
2688
        return None
2689
2690
    def getReceivedBy(self):
2691
        """
2692
        Returns the User who received the analysis request.
2693
        :returns: the user id
2694
        """
2695
        user = getTransitionUsers(self, 'receive', last_user=True)
2696
        return user[0] if user else ''
2697
2698
    def getDateVerified(self):
2699
        """
2700
        Returns the date of verification as a DateTime object.
2701
        """
2702
        return getTransitionDate(self, 'verify', return_as_datetime=True)
2703
2704
    @security.public
2705
    def getPrioritySortkey(self):
2706
        """Returns the key that will be used to sort the current Analysis
2707
        Request based on both its priority and creation date. On ASC sorting,
2708
        the oldest item with highest priority will be displayed.
2709
        :return: string used for sorting
2710
        """
2711
        priority = self.getPriority()
2712
        created_date = self.created().ISO8601()
2713
        return '%s.%s' % (priority, created_date)
2714
2715
    @security.public
2716
    def setPriority(self, value):
2717
        if not value:
2718
            value = self.Schema().getField('Priority').getDefault(self)
2719
        original_value = self.Schema().getField('Priority').get(self)
2720
        if original_value != value:
2721
            self.Schema().getField('Priority').set(self, value)
2722
            self._reindexAnalyses(['getPrioritySortkey'], True)
2723
2724
    @security.private
2725
    def _reindexAnalyses(self, idxs=None, update_metadata=False):
2726
        if not idxs and not update_metadata:
2727
            return
2728
        if not idxs:
2729
            idxs = []
2730
        analyses = self.getAnalyses()
2731
        catalog = getToolByName(self, CATALOG_ANALYSIS_LISTING)
2732
        for analysis in analyses:
2733
            analysis_obj = analysis.getObject()
2734
            catalog.reindexObject(analysis_obj, idxs=idxs, update_metadata=1)
2735
2736
    def _getCreatorFullName(self):
2737
        """
2738
        Returns the full name of this analysis request's creator.
2739
        """
2740
        return user_fullname(self, self.Creator())
2741
2742
    def _getCreatorEmail(self):
2743
        """
2744
        Returns the email of this analysis request's creator.
2745
        """
2746
        return user_email(self, self.Creator())
2747
2748
    def _getSamplerFullName(self):
2749
        """
2750
        Returns the full name's defined sampler.
2751
        """
2752
        return user_fullname(self, self.getSampler())
2753
2754
    def _getSamplerEmail(self):
2755
        """
2756
        Returns the email of this analysis request's sampler.
2757
        """
2758
        return user_email(self, self.Creator())
2759
2760 View Code Duplication
    def getObjectWorkflowStates(self):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
2761
        """
2762
        This method is used as a metacolumn.
2763
        Returns a dictionary with the workflow id as key and workflow state as
2764
        value.
2765
        :returns: {'review_state':'active',...}
2766
        """
2767
        workflow = getToolByName(self, 'portal_workflow')
2768
        states = {}
2769
        for w in workflow.getWorkflowsFor(self):
2770
            state = w._getWorkflowStateOf(self).id
2771
            states[w.state_var] = state
2772
        return states
2773
2774
    @security.public
2775
    def guard_to_be_preserved(self):
2776
        return guards.to_be_preserved(self)
2777
2778
    @security.public
2779
    def guard_unassign_transition(self):
2780
        return guards.unassign(self)
2781
2782
    @security.public
2783
    def guard_assign_transition(self):
2784
        return guards.assign(self)
2785
2786
    @security.public
2787
    def guard_receive_transition(self):
2788
        return guards.receive(self)
2789
2790
    @security.public
2791
    def guard_schedule_sampling_transition(self):
2792
        return guards.schedule_sampling(self)
2793
2794
    @security.public
2795
    def guard_prepublish_transition(self):
2796
        return guards.prepublish(self)
2797
2798
    def SearchableText(self):
2799
        """
2800
        Override searchable text logic based on the requirements.
2801
2802
        This method constructs a text blob which contains all full-text
2803
        searchable text for this content item.
2804
        https://docs.plone.org/develop/plone/searching_and_indexing/indexing.html#full-text-searching
2805
        """
2806
2807
        # Speed up string concatenation ops by using a buffer
2808
        entries = []
2809
2810
        # plain text fields we index from ourself,
2811
        # a list of accessor methods of the class
2812
        plain_text_fields = ("getId", )
2813
2814
        def read(acc):
2815
            """
2816
            Call a class accessor method to give a value for certain Archetypes
2817
            field.
2818
            """
2819
            try:
2820
                val = acc()
2821
            except Exception as e:
2822
                message = "Error getting the accessor parameter in " \
2823
                          "SearchableText from the Analysis Request Object " \
2824
                          "{}: {}".format(self.getId(), e.message)
2825
                logger.error(message)
2826
                val = ""
2827
2828
            if val is None:
2829
                val = ""
2830
2831
            return val
2832
2833
        # Concatenate plain text fields as they are
2834
        for f in plain_text_fields:
2835
            accessor = getattr(self, f)
2836
            value = read(accessor)
2837
            entries.append(value)
2838
2839
        # Adding HTML Fields to SearchableText can be uncommented if necessary
2840
        # transforms = getToolByName(self, 'portal_transforms')
2841
        #
2842
        # # Run HTML valued fields through text/plain conversion
2843
        # for f in html_fields:
2844
        #     accessor = getattr(self, f)
2845
        #     value = read(accessor)
2846
        #
2847
        #     if value != "":
2848
        #         stream = transforms.convertTo('text/plain', value,
2849
        #                                       mimetype='text/html')
2850
        #         value = stream.getData()
2851
        #
2852
        #     entries.append(value)
2853
2854
        # Plone accessor methods assume utf-8
2855
        def convertToUTF8(text):
2856
            if type(text) == unicode:
2857
                return text.encode("utf-8")
2858
            return text
2859
2860
        entries = [convertToUTF8(entry) for entry in entries]
2861
2862
        # Concatenate all strings to one text blob
2863
        return " ".join(entries)
2864
2865
    def getPriorityText(self):
2866
        """
2867
        This function looks up the priority text from priorities vocab
2868
        :returns: the priority text or ''
2869
        """
2870
        if self.getPriority():
2871
            return PRIORITIES.getValue(self.getPriority())
2872
        return ''
2873
2874
    def get_ARAttachment(self):
2875
        logger.warn("_ARAttachment is a virtual field used in AR Add. "
2876
                    "It can not hold an own value!")
2877
        return None
2878
2879
    def set_ARAttachment(self, value):
2880
        logger.warn("_ARAttachment is a virtual field used in AR Add. "
2881
                    "It can not hold an own value!")
2882
        return None
2883
2884
    def get_retest(self):
2885
        """Returns the Analysis Request automatically generated because of the
2886
        retraction of the current analysis request
2887
        """
2888
        relationship = "AnalysisRequestRetracted"
2889
        retest = self.getBackReferences(relationship=relationship)
2890
        if retest and len(retest) > 1:
2891
            logger.warn("More than one retest for {0}".format(self.getId()))
2892
        return retest and retest[0] or None
2893
2894
    def getAncestors(self, all_ancestors=True):
2895
        """Returns the ancestor(s) of this Analysis Request
2896
        param all_ancestors: include all ancestors, not only the parent
2897
        """
2898
        parent = self.getParentAnalysisRequest()
2899
        if not parent:
2900
            return list()
2901
        if not all_ancestors:
2902
            return [parent]
2903
        return [parent] + parent.getAncestors(all_ancestors=True)
2904
2905
    def isRootAncestor(self):
2906
        """Returns True if the AR is the root ancestor
2907
2908
        :returns: True if the AR has no more parents
2909
        """
2910
        parent = self.getParentAnalysisRequest()
2911
        if parent:
2912
            return False
2913
        return True
2914
2915
    def getDescendants(self, all_descendants=False):
2916
        """Returns the descendant Analysis Requests
2917
2918
        :param all_descendants: recursively include all descendants
2919
        """
2920
2921
        # N.B. full objects returned here from
2922
        #      `Products.Archetypes.Referenceable.getBRefs`
2923
        #      -> don't add this method into Metadata
2924
        children = self.getBackReferences(
2925
            "AnalysisRequestParentAnalysisRequest")
2926
2927
        descendants = []
2928
2929
        # recursively include all children
2930
        if all_descendants:
2931
            for child in children:
2932
                descendants.append(child)
2933
                descendants += child.getDescendants(all_descendants=True)
2934
        else:
2935
            descendants = children
2936
2937
        return descendants
2938
2939
    def getDescendantsUIDs(self, all_descendants=False):
2940
        """Returns the UIDs of the descendant Analysis Requests
2941
2942
        This method is used as metadata
2943
        """
2944
        descendants = self.getDescendants(all_descendants=all_descendants)
2945
        return map(api.get_uid, descendants)
2946
2947
    def isPartition(self):
2948
        """Returns true if this Analysis Request is a partition
2949
        """
2950
        return not self.isRootAncestor()
2951
2952
2953
registerType(AnalysisRequest, PROJECTNAME)
2954