Passed
Push — master ( 6d6a8c...6374c8 )
by Ramon
05:17
created

bika.lims.content.analysisrequest   F

Complexity

Total Complexity 222

Size/Duplication

Total Lines 2928
Duplicated Lines 1.2 %

Importance

Changes 0
Metric Value
wmc 222
eloc 2148
dl 35
loc 2928
rs 0.8
c 0
b 0
f 0

85 Methods

Rating   Name   Duplication   Size   Complexity  
A AnalysisRequest.getAnalysisService() 0 6 2
A AnalysisRequest.getClient() 0 6 3
A AnalysisRequest.getProfilesTitle() 0 2 1
A AnalysisRequest.setBatch() 0 6 2
A AnalysisRequest.getBatch() 0 8 2
A AnalysisRequest._renameAfterCreation() 0 3 1
A AnalysisRequest.setPublicationSpecification() 0 4 1
A AnalysisRequest.getAnalysts() 0 8 3
A AnalysisRequest._getCatalogTool() 0 3 1
A AnalysisRequest.sortable_title() 0 5 1
A AnalysisRequest.getDefaultMemberDiscount() 0 9 3
A AnalysisRequest.getDistrict() 0 3 1
A AnalysisRequest.Description() 0 4 1
A AnalysisRequest.Title() 0 3 1
A AnalysisRequest.getClientPath() 0 2 1
A AnalysisRequest.getBatchUID() 0 5 2
A AnalysisRequest.getProvince() 0 3 1
B AnalysisRequest.getBillableItems() 0 31 8
A AnalysisRequest.getSamplingWorkflowEnabled() 0 8 2
B AnalysisRequest.addARAttachment() 0 44 6
A AnalysisRequest.getVATAmount() 0 13 2
A AnalysisRequest.getDiscountAmount() 0 10 2
A AnalysisRequest.getSubtotal() 0 4 1
A AnalysisRequest.getDueDate() 0 5 2
A AnalysisRequest.getHazardous() 0 8 2
A AnalysisRequest.printInvoice() 0 6 1
A AnalysisRequest.getResultsRange() 0 38 5
A AnalysisRequest.getSubtotalTotalPrice() 0 4 1
B AnalysisRequest.delARAttachment() 0 23 5
A AnalysisRequest.getSamplingRoundUID() 0 9 2
A AnalysisRequest.getVerifiersIDs() 0 9 2
A AnalysisRequest.getAnalysesNum() 0 19 5
A AnalysisRequest.current_date() 0 5 1
B AnalysisRequest.getServicesAndProfiles() 0 33 6
B AnalysisRequest.getPrinted() 0 22 7
A AnalysisRequest.getContactUIDForUser() 0 11 2
A AnalysisRequest.getContactURL() 0 8 2
A AnalysisRequest.getManagers() 0 15 4
A AnalysisRequest.getVerifiers() 0 12 3
A AnalysisRequest.isInvalid() 0 5 1
B AnalysisRequest.getVerifier() 0 29 7
C AnalysisRequest.getQCAnalyses() 0 53 10
A AnalysisRequest.printLastReport() 0 12 5
A AnalysisRequest.getLate() 0 10 4
A AnalysisRequest.getSubtotalVATAmount() 0 4 1
A AnalysisRequest.issueInvoice() 0 40 4
A AnalysisRequest.getSamplers() 0 2 1
A AnalysisRequest.getTotalPrice() 0 9 1
A AnalysisRequest.getStorageLocationTitle() 0 8 2
A AnalysisRequest.getSamplingDeviationTitle() 0 8 2
B AnalysisRequest.getResponsible() 0 37 6
A AnalysisRequest.getDatePublished() 0 5 1
A AnalysisRequest.getPartitions() 0 11 3
A AnalysisRequest.getObjectWorkflowStates() 13 13 2
A AnalysisRequest.guard_receive_transition() 0 3 1
A AnalysisRequest.guard_schedule_sampling_transition() 0 3 1
A AnalysisRequest._reindexAnalyses() 0 11 5
B AnalysisRequest.getAnalysisServiceSettings() 0 29 8
A AnalysisRequest.guard_prepublish_transition() 0 3 1
A AnalysisRequest.isAnalysisServiceHidden() 22 22 5
A AnalysisRequest.getDateVerified() 0 5 1
A AnalysisRequest.guard_unassign_transition() 0 3 1
A AnalysisRequest.getPriorityText() 0 8 2
A AnalysisRequest.isRootAncestor() 0 9 2
A AnalysisRequest._getCreatorEmail() 0 5 1
A AnalysisRequest.get_retest() 0 9 3
A AnalysisRequest.getDescendantsUIDs() 0 7 1
A AnalysisRequest.getDescendants() 0 23 3
A AnalysisRequest.getPrioritySortkey() 0 10 1
A AnalysisRequest._getSamplerEmail() 0 5 1
A AnalysisRequest._getSamplerFullName() 0 5 1
A AnalysisRequest.get_ARAttachment() 0 4 1
A AnalysisRequest.getRejecter() 0 19 4
A AnalysisRequest.guard_to_be_preserved() 0 3 1
A AnalysisRequest.getContainers() 0 12 3
A AnalysisRequest.isPartition() 0 4 1
A AnalysisRequest.guard_assign_transition() 0 3 1
A AnalysisRequest.setPriority() 0 8 3
A AnalysisRequest.set_ARAttachment() 0 4 1
A AnalysisRequest.getReceivedBy() 0 7 2
A AnalysisRequest._getCreatorFullName() 0 5 1
B AnalysisRequest.getResultsInterpretationByDepartment() 0 20 6
B AnalysisRequest.SearchableText() 0 66 5
A AnalysisRequest.getAncestors() 0 10 3
A AnalysisRequest.getDepartments() 0 10 4

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complexity

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like bika.lims.content.analysisrequest often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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': 'invisible',
278
                'view': 'invisible',
279
                'add': 'edit',
280
                'header_table': 'invisible',
281
                'sample_registered': {
282
                    'view': 'invisible', 'edit': 'invisible', 'add': 'edit'},
283
                'to_be_sampled': {'view': 'invisible', 'edit': 'invisible'},
284
                'scheduled_sampling': {'view': 'invisible', 'edit': 'invisible'},
285
                'sampled': {'view': 'invisible', 'edit': 'invisible'},
286
                'to_be_preserved': {'view': 'invisible', 'edit': 'invisible'},
287
                'sample_due': {'view': 'invisible', 'edit': 'invisible'},
288
                'sample_received': {'view': 'invisible', 'edit': 'invisible'},
289
                'attachment_due': {'view': 'invisible', 'edit': 'invisible'},
290
                'to_be_verified': {'view': 'invisible', 'edit': 'invisible'},
291
                'verified': {'view': 'invisible', 'edit': 'invisible'},
292
                'published': {'view': 'invisible', 'edit': 'invisible'},
293
                'invalid': {'view': 'invisible', 'edit': 'invisible'},
294
                'rejected': {'view': 'invisible', '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', 'cancelled']:
1913
                continue
1914
            if review_state == 'to_be_verified':
1915
                an_nums[3] += 1
1916
            elif review_state in ['published', 'verified']:
1917
                an_nums[0] += 1
1918
            else:
1919
                an_nums[2] += 1
1920
            an_nums[1] += 1
1921
        return an_nums
1922
1923
    @security.public
1924
    def getResponsible(self):
1925
        """Return all manager info of responsible departments
1926
        """
1927
        managers = {}
1928
        for department in self.getDepartments():
1929
            manager = department.getManager()
1930
            if manager is None:
1931
                continue
1932
            manager_id = manager.getId()
1933
            if manager_id not in managers:
1934
                managers[manager_id] = {}
1935
                managers[manager_id]['salutation'] = safe_unicode(
1936
                    manager.getSalutation())
1937
                managers[manager_id]['name'] = safe_unicode(
1938
                    manager.getFullname())
1939
                managers[manager_id]['email'] = safe_unicode(
1940
                    manager.getEmailAddress())
1941
                managers[manager_id]['phone'] = safe_unicode(
1942
                    manager.getBusinessPhone())
1943
                managers[manager_id]['job_title'] = safe_unicode(
1944
                    manager.getJobTitle())
1945
                if manager.getSignature():
1946
                    managers[manager_id]['signature'] = \
1947
                        '{}/Signature'.format(manager.absolute_url())
1948
                else:
1949
                    managers[manager_id]['signature'] = False
1950
                managers[manager_id]['departments'] = ''
1951
            mngr_dept = managers[manager_id]['departments']
1952
            if mngr_dept:
1953
                mngr_dept += ', '
1954
            mngr_dept += safe_unicode(department.Title())
1955
            managers[manager_id]['departments'] = mngr_dept
1956
        mngr_keys = managers.keys()
1957
        mngr_info = {'ids': mngr_keys, 'dict': managers}
1958
1959
        return mngr_info
1960
1961
    @security.public
1962
    def getManagers(self):
1963
        """Return all managers of responsible departments
1964
        """
1965
        manager_ids = []
1966
        manager_list = []
1967
        for department in self.getDepartments():
1968
            manager = department.getManager()
1969
            if manager is None:
1970
                continue
1971
            manager_id = manager.getId()
1972
            if manager_id not in manager_ids:
1973
                manager_ids.append(manager_id)
1974
                manager_list.append(manager)
1975
        return manager_list
1976
1977
    def getDueDate(self):
1978
        """Returns the earliest due date of the analyses this Analysis Request
1979
        contains."""
1980
        due_dates = map(lambda an: an.getDueDate, self.getAnalyses())
1981
        return due_dates and min(due_dates) or None
1982
1983
    security.declareProtected(View, 'getLate')
1984
1985
    def getLate(self):
1986
        """Return True if there is at least one late analysis in this Request
1987
        """
1988
        for analysis in self.getAnalyses():
1989
            if analysis.review_state == "retracted":
1990
                continue
1991
            analysis_obj = api.get_object(analysis)
1992
            if analysis_obj.isLateAnalysis():
1993
                return True
1994
        return False
1995
1996
    def getPrinted(self):
1997
        """ returns "0", "1" or "2" to indicate Printed state.
1998
            0 -> Never printed.
1999
            1 -> Printed after last publish
2000
            2 -> Printed but republished afterwards.
2001
        """
2002
        workflow = getToolByName(self, 'portal_workflow')
2003
        review_state = workflow.getInfoFor(self, 'review_state', '')
2004
        if review_state not in ['published']:
2005
            return "0"
2006
        report_list = sorted(self.objectValues('ARReport'),
2007
                             key=lambda report: report.getDatePublished())
2008
        if not report_list:
2009
            return "0"
2010
        last_report = report_list[-1]
2011
        if last_report.getDatePrinted():
2012
            return "1"
2013
        else:
2014
            for report in report_list:
2015
                if report.getDatePrinted():
2016
                    return "2"
2017
        return "0"
2018
2019
    def printLastReport(self):
2020
        """Setting Printed Time of the last report, so its Printed value will be 1
2021
        """
2022
        workflow = getToolByName(self, 'portal_workflow')
2023
        review_state = workflow.getInfoFor(self, 'review_state', '')
2024
        if review_state not in ['published']:
2025
            return
2026
        last_report = sorted(self.objectValues('ARReport'),
2027
                             key=lambda report: report.getDatePublished())[-1]
2028
        if last_report and not last_report.getDatePrinted():
2029
            last_report.setDatePrinted(DateTime())
2030
            self.reindexObject(idxs=['getPrinted'])
2031
2032
    security.declareProtected(View, 'getBillableItems')
2033
2034
    def getBillableItems(self):
2035
        """Returns the items to be billed
2036
        """
2037
        def get_keywords_set(profiles):
2038
            keys = list()
2039
            for profile in profiles:
2040
                keys += map(lambda s: s.getKeyword(), profile.getService())
2041
            return set(keys)
2042
2043
        # Profiles with a fixed price, regardless of their analyses
2044
        profiles = self.getProfiles()
2045
        billable_items = filter(lambda pr: pr.getUseAnalysisProfilePrice(),
2046
                                 profiles)
2047
        # Profiles w/o a fixed price. The price is the sum of the individual
2048
        # price for each analysis
2049
        non_billable = filter(lambda p: p not in billable_items, profiles)
2050
        billable_keys = get_keywords_set(non_billable) - \
2051
                        get_keywords_set(billable_items)
2052
2053
        # Get the analyses to be billed
2054
        exclude_rs = ['retracted', 'rejected']
2055
        for analysis in self.getAnalyses(cancellation_state="active"):
2056
            if analysis.review_state in exclude_rs:
2057
                continue
2058
            if analysis.getKeyword not in billable_keys:
2059
                continue
2060
            billable_items.append(api.get_object(analysis))
2061
2062
        # Return the analyses that need to be billed individually, together with
2063
        # the profiles with a fixed price
2064
        return billable_items
2065
2066
    # TODO Cleanup - Remove this function, only used in invoice and too complex
2067
    def getServicesAndProfiles(self):
2068
        """This function gets all analysis services and all profiles and removes
2069
        the services belonging to a profile.
2070
2071
        :returns: a tuple of three lists, where the first list contains the
2072
        analyses and the second list the profiles.
2073
        The third contains the analyses objects used by the profiles.
2074
        """
2075
        # profile_analyses contains the profile's analyses (analysis !=
2076
        # service") objects to obtain
2077
        # the correct price later
2078
        exclude_rs = ['retracted', 'rejected']
2079
        analyses = filter(lambda an: an.review_state not in exclude_rs,
2080
                          self.getAnalyses(cancellation_state='active'))
2081
        analyses = map(api.get_object, analyses)
2082
        profiles = self.getProfiles()
2083
2084
        # Get the service keys from all profiles
2085
        profiles_keys = list()
2086
        for profile in profiles:
2087
            profile_keys = map(lambda s: s.getKeyword(), profile.getService())
2088
            profiles_keys.extend(profile_keys)
2089
2090
        # Extract the analyses which service is present in at least one profile
2091
        # and those not present (orphan)
2092
        profile_analyses = list()
2093
        orphan_analyses = list()
2094
        for an in analyses:
2095
            if an.getKeyword() in profiles_keys:
2096
                profile_analyses.append(an)
2097
            else:
2098
                orphan_analyses.append(an)
2099
        return analyses, profiles, profile_analyses
2100
2101
    security.declareProtected(View, 'getSubtotal')
2102
2103
    def getSubtotal(self):
2104
        """Compute Subtotal (without member discount and without vat)
2105
        """
2106
        return sum([Decimal(obj.getPrice()) for obj in self.getBillableItems()])
2107
2108
    security.declareProtected(View, 'getSubtotalVATAmount')
2109
2110
    def getSubtotalVATAmount(self):
2111
        """Compute VAT amount without member discount
2112
        """
2113
        return sum([Decimal(o.getVATAmount()) for o in self.getBillableItems()])
2114
2115
    security.declareProtected(View, 'getSubtotalTotalPrice')
2116
2117
    def getSubtotalTotalPrice(self):
2118
        """Compute the price with VAT but no member discount
2119
        """
2120
        return self.getSubtotal() + self.getSubtotalVATAmount()
2121
2122
    security.declareProtected(View, 'getDiscountAmount')
2123
2124
    def getDiscountAmount(self):
2125
        """It computes and returns the analysis service's discount amount
2126
        without VAT
2127
        """
2128
        has_client_discount = self.aq_parent.getMemberDiscountApplies()
2129
        if has_client_discount:
2130
            discount = Decimal(self.getDefaultMemberDiscount())
2131
            return Decimal(self.getSubtotal() * discount / 100)
2132
        else:
2133
            return 0
2134
2135
    def getVATAmount(self):
2136
        """It computes the VAT amount from (subtotal-discount.)*VAT/100, but
2137
        each analysis has its own VAT!
2138
2139
        :returns: the analysis request VAT amount with the discount
2140
        """
2141
        has_client_discount = self.aq_parent.getMemberDiscountApplies()
2142
        VATAmount = self.getSubtotalVATAmount()
2143
        if has_client_discount:
2144
            discount = Decimal(self.getDefaultMemberDiscount())
2145
            return Decimal((1 - discount / 100) * VATAmount)
2146
        else:
2147
            return VATAmount
2148
2149
    security.declareProtected(View, 'getTotalPrice')
2150
2151
    def getTotalPrice(self):
2152
        """It gets the discounted price from analyses and profiles to obtain the
2153
        total value with the VAT and the discount applied
2154
2155
        :returns: analysis request's total price including VATs and discounts
2156
        """
2157
        price = (self.getSubtotal() - self.getDiscountAmount() +
2158
                 self.getVATAmount())
2159
        return price
2160
2161
    getTotal = getTotalPrice
2162
2163
    security.declareProtected(ManageInvoices, 'issueInvoice')
2164
2165
    # noinspection PyUnusedLocal
2166
    def issueInvoice(self, REQUEST=None, RESPONSE=None):
2167
        """Issue invoice
2168
        """
2169
        # check for an adhoc invoice batch for this month
2170
        # noinspection PyCallingNonCallable
2171
        now = DateTime()
2172
        batch_month = now.strftime('%b %Y')
2173
        batch_title = '%s - %s' % (batch_month, 'ad hoc')
2174
        invoice_batch = None
2175
        for b_proxy in self.portal_catalog(portal_type='InvoiceBatch',
2176
                                           Title=batch_title):
2177
            invoice_batch = b_proxy.getObject()
2178
        if not invoice_batch:
2179
            # noinspection PyCallingNonCallable
2180
            first_day = DateTime(now.year(), now.month(), 1)
2181
            start_of_month = first_day.earliestTime()
2182
            last_day = first_day + 31
2183
            # noinspection PyUnresolvedReferences
2184
            while last_day.month() != now.month():
2185
                last_day -= 1
2186
            # noinspection PyUnresolvedReferences
2187
            end_of_month = last_day.latestTime()
2188
2189
            invoices = self.invoices
2190
            batch_id = invoices.generateUniqueId('InvoiceBatch')
2191
            invoice_batch = _createObjectByType("InvoiceBatch", invoices,
2192
                                                batch_id)
2193
            invoice_batch.edit(
2194
                title=batch_title,
2195
                BatchStartDate=start_of_month,
2196
                BatchEndDate=end_of_month,
2197
            )
2198
            invoice_batch.processForm()
2199
2200
        client_uid = self.getClientUID()
2201
        # Get the created invoice
2202
        invoice = invoice_batch.createInvoice(client_uid, [self, ])
2203
        invoice.setAnalysisRequest(self)
2204
        # Set the created invoice in the schema
2205
        self.Schema()['Invoice'].set(self, invoice)
2206
2207
    security.declarePublic('printInvoice')
2208
2209
    # noinspection PyUnusedLocal
2210
    def printInvoice(self, REQUEST=None, RESPONSE=None):
2211
        """Print invoice
2212
        """
2213
        invoice = self.getInvoice()
2214
        invoice_url = invoice.absolute_url()
2215
        RESPONSE.redirect('{}/invoice_print'.format(invoice_url))
2216
2217
    @deprecated("addARAttachment will be removed in senaite.core 1.3.0")
2218
    def addARAttachment(self, REQUEST=None, RESPONSE=None):
2219
        """Add the file as an attachment
2220
        """
2221
        workflow = getToolByName(self, 'portal_workflow')
2222
2223
        this_file = self.REQUEST.form['AttachmentFile_file']
2224
        if 'Analysis' in self.REQUEST.form:
2225
            analysis_uid = self.REQUEST.form['Analysis']
2226
        else:
2227
            analysis_uid = None
2228
2229
        attachmentid = self.generateUniqueId('Attachment')
2230
        attachment = _createObjectByType("Attachment", self.aq_parent,
2231
                                         attachmentid)
2232
        attachment.edit(
2233
            AttachmentFile=this_file,
2234
            AttachmentType=self.REQUEST.form.get('AttachmentType', ''),
2235
            AttachmentKeys=self.REQUEST.form['AttachmentKeys'])
2236
        attachment.processForm()
2237
        attachment.reindexObject()
2238
2239
        if analysis_uid:
2240
            tool = getToolByName(self, REFERENCE_CATALOG)
2241
            analysis = tool.lookupObject(analysis_uid)
2242
            others = analysis.getAttachment()
2243
            attachments = []
2244
            for other in others:
2245
                attachments.append(other.UID())
2246
            attachments.append(attachment.UID())
2247
            analysis.setAttachment(attachments)
2248
        else:
2249
            others = self.getAttachment()
2250
            attachments = []
2251
            for other in others:
2252
                attachments.append(other.UID())
2253
            attachments.append(attachment.UID())
2254
2255
            self.setAttachment(attachments)
2256
2257
        if REQUEST['HTTP_REFERER'].endswith('manage_results'):
2258
            RESPONSE.redirect('{}/manage_results'.format(self.absolute_url()))
2259
        else:
2260
            RESPONSE.redirect(self.absolute_url())
2261
2262
    @deprecated("delARAttachment will be removed in senaite.core 1.3.0")
2263
    def delARAttachment(self, REQUEST=None, RESPONSE=None):
2264
        """Delete the attachment
2265
        """
2266
        tool = getToolByName(self, REFERENCE_CATALOG)
2267
        if 'Attachment' in self.REQUEST.form:
2268
            attachment_uid = self.REQUEST.form['Attachment']
2269
            attachment = tool.lookupObject(attachment_uid)
2270
            parent_r = attachment.getRequest()
2271
            parent_a = attachment.getAnalysis()
2272
2273
            parent = parent_a if parent_a else parent_r
2274
            others = parent.getAttachment()
2275
            attachments = []
2276
            for other in others:
2277
                if not other.UID() == attachment_uid:
2278
                    attachments.append(other.UID())
2279
            parent.setAttachment(attachments)
2280
            client = attachment.aq_parent
2281
            ids = [attachment.getId(), ]
2282
            BaseFolder.manage_delObjects(client, ids, REQUEST)
2283
2284
        RESPONSE.redirect(self.REQUEST.get_header('referer'))
2285
2286
    security.declarePublic('getVerifier')
2287
2288
    @deprecated("Use getVerifiers instead")
2289
    def getVerifier(self):
2290
        """Returns the user that verified the whole Analysis Request. Since the
2291
        verification is done automatically as soon as all the analyses it
2292
        contains are verified, this function returns the user that verified the
2293
        last analysis pending.
2294
        """
2295
        wtool = getToolByName(self, 'portal_workflow')
2296
        mtool = getToolByName(self, 'portal_membership')
2297
2298
        verifier = None
2299
        # noinspection PyBroadException
2300
        try:
2301
            review_history = wtool.getInfoFor(self, 'review_history')
2302
        except:  # noqa FIXME: remove blind except!
2303
            return 'access denied'
2304
2305
        if not review_history:
2306
            return 'no history'
2307
        for items in review_history:
2308
            action = items.get('action')
2309
            if action != 'verify':
2310
                continue
2311
            actor = items.get('actor')
2312
            member = mtool.getMemberById(actor)
2313
            verifier = member.getProperty('fullname')
2314
            if verifier is None or verifier == '':
2315
                verifier = actor
2316
        return verifier
2317
2318
    @security.public
2319
    def getVerifiersIDs(self):
2320
        """Returns the ids from users that have verified at least one analysis
2321
        from this Analysis Request
2322
        """
2323
        verifiers_ids = list()
2324
        for brain in self.getAnalyses():
2325
            verifiers_ids += brain.getVerificators
2326
        return list(set(verifiers_ids))
2327
2328
    @security.public
2329
    def getVerifiers(self):
2330
        """Returns the list of lab contacts that have verified at least one
2331
        analysis from this Analysis Request
2332
        """
2333
        contacts = list()
2334
        for verifier in self.getVerifiersIDs():
2335
            user = api.get_user(verifier)
2336
            contact = api.get_user_contact(user, ["LabContact"])
2337
            if contact:
2338
                contacts.append(contact)
2339
        return contacts
2340
2341
    security.declarePublic('getContactUIDForUser')
2342
2343
    def getContactUIDForUser(self):
2344
        """get the UID of the contact associated with the authenticated user
2345
        """
2346
        mt = getToolByName(self, 'portal_membership')
2347
        user = mt.getAuthenticatedMember()
2348
        user_id = user.getUserName()
2349
        pc = getToolByName(self, 'portal_catalog')
2350
        r = pc(portal_type='Contact',
2351
               getUsername=user_id)
2352
        if len(r) == 1:
2353
            return r[0].UID
2354
2355
    security.declarePublic('current_date')
2356
2357
    def current_date(self):
2358
        """return current date
2359
        """
2360
        # noinspection PyCallingNonCallable
2361
        return DateTime()
2362
2363
    def getQCAnalyses(self, qctype=None, review_state=None):
2364
        """return the QC analyses performed in the worksheet in which, at
2365
        least, one sample of this AR is present.
2366
2367
        Depending on qctype value, returns the analyses of:
2368
2369
            - 'b': all Blank Reference Samples used in related worksheet/s
2370
            - 'c': all Control Reference Samples used in related worksheet/s
2371
            - 'd': duplicates only for samples contained in this AR
2372
2373
        If qctype==None, returns all type of qc analyses mentioned above
2374
        """
2375
        qcanalyses = []
2376
        suids = []
2377
        ans = self.getAnalyses()
2378
        wf = getToolByName(self, 'portal_workflow')
2379
        for an in ans:
2380
            an = an.getObject()
2381
            if an.getServiceUID() not in suids:
2382
                suids.append(an.getServiceUID())
2383
2384
        def valid_dup(wan):
2385
            if wan.portal_type == 'ReferenceAnalysis':
2386
                return False
2387
            an_state = wf.getInfoFor(wan, 'review_state')
2388
            return \
2389
                wan.portal_type == 'DuplicateAnalysis' \
2390
                and wan.getRequestID() == self.id \
2391
                and (review_state is None or an_state in review_state)
2392
2393
        def valid_ref(wan):
2394
            if wan.portal_type != 'ReferenceAnalysis':
2395
                return False
2396
            an_state = wf.getInfoFor(wan, 'review_state')
2397
            an_reftype = wan.getReferenceType()
2398
            return wan.getServiceUID() in suids \
2399
                and wan not in qcanalyses \
2400
                and (qctype is None or an_reftype == qctype) \
2401
                and (review_state is None or an_state in review_state)
2402
2403
        for an in ans:
2404
            an = an.getObject()
2405
            ws = an.getWorksheet()
2406
            if not ws:
2407
                continue
2408
            was = ws.getAnalyses()
2409
            for wa in was:
2410
                if valid_dup(wa):
2411
                    qcanalyses.append(wa)
2412
                elif valid_ref(wa):
2413
                    qcanalyses.append(wa)
2414
2415
        return qcanalyses
2416
2417
    def isInvalid(self):
2418
        """return if the Analysis Request has been invalidated
2419
        """
2420
        workflow = getToolByName(self, 'portal_workflow')
2421
        return workflow.getInfoFor(self, 'review_state') == 'invalid'
2422
2423
    def getSamplingRoundUID(self):
2424
        """Obtains the sampling round UID
2425
        :returns: UID
2426
        """
2427
        sr = self.getSamplingRound()
2428
        if sr:
2429
            return sr.UID()
2430
        else:
2431
            return ''
2432
2433
    def getStorageLocationTitle(self):
2434
        """ A method for AR listing catalog metadata
2435
        :return: Title of Storage Location
2436
        """
2437
        sl = self.getStorageLocation()
2438
        if sl:
2439
            return sl.Title()
2440
        return ''
2441
2442
    @security.public
2443
    def getResultsRange(self):
2444
        """Returns the valid result ranges for the analyses this Analysis
2445
        Request contains.
2446
2447
        By default uses the result ranges defined in the Analysis Specification
2448
        set in "Specification" field if any. Values manually set through
2449
        `ResultsRange` field for any given analysis keyword have priority over
2450
        the result ranges defined in "Specification" field.
2451
2452
        :return: A list of dictionaries, where each dictionary defines the
2453
            result range to use for any analysis contained in this Analysis
2454
            Request for the keyword specified. Each dictionary has, at least,
2455
                the following keys: "keyword", "min", "max"
2456
        :rtype: dict
2457
        """
2458
        specs_range = []
2459
        specification = self.getSpecification()
2460
        if specification:
2461
            specs_range = specification.getResultsRange()
2462
            specs_range = specs_range and specs_range or []
2463
2464
        # Override with AR's custom ranges
2465
        ar_range = self.Schema().getField("ResultsRange").get(self)
2466
        if not ar_range:
2467
            return specs_range
2468
2469
        # Remove those analysis ranges that neither min nor max are floatable
2470
        an_specs = [an for an in ar_range if
2471
                    api.is_floatable(an.get('min', None)) or
2472
                    api.is_floatable(an.get('max', None))]
2473
        # Want to know which are the analyses that needs to be overriden
2474
        keywords = map(lambda item: item.get('keyword'), an_specs)
2475
        # Get rid of those analyses to be overriden
2476
        out_specs = [sp for sp in specs_range if sp['keyword'] not in keywords]
2477
        # Add manually set ranges
2478
        out_specs.extend(an_specs)
2479
        return map(lambda spec: ResultsRangeDict(spec), out_specs)
2480
2481
    def getDatePublished(self):
2482
        """
2483
        Returns the transition date from the Analysis Request object
2484
        """
2485
        return getTransitionDate(self, 'publish', return_as_datetime=True)
2486
2487
    security.declarePublic('getSamplingDeviationTitle')
2488
2489
    def getSamplingDeviationTitle(self):
2490
        """
2491
        It works as a metacolumn.
2492
        """
2493
        sd = self.getSamplingDeviation()
2494
        if sd:
2495
            return sd.Title()
2496
        return ''
2497
2498
    security.declarePublic('getHazardous')
2499
2500
    def getHazardous(self):
2501
        """
2502
        It works as a metacolumn.
2503
        """
2504
        sample_type = self.getSampleType()
2505
        if sample_type:
2506
            return sample_type.getHazardous()
2507
        return False
2508
2509
    security.declarePublic('getContactURL')
2510
2511
    def getContactURL(self):
2512
        """
2513
        It works as a metacolumn.
2514
        """
2515
        contact = self.getContact()
2516
        if contact:
2517
            return contact.absolute_url_path()
2518
        return ''
2519
2520
    security.declarePublic('getSamplingWorkflowEnabled')
2521
2522
    def getSamplingWorkflowEnabled(self):
2523
        """
2524
        It works as a metacolumn.
2525
        """
2526
        sample = self.getSample()
2527
        if sample:
2528
            return sample.getSamplingWorkflowEnabled()
2529
        return ''
2530
2531
    def getSamplers(self):
2532
        return getUsers(self, ['Sampler', ])
2533
2534
    def getDepartments(self):
2535
        """Returns a list of the departments assigned to the Analyses
2536
        from this Analysis Request
2537
        """
2538
        departments = list()
2539
        for analysis in self.getAnalyses(full_objects=True):
2540
            department = analysis.getDepartment()
2541
            if department and department not in departments:
2542
                departments.append(department)
2543
        return departments
2544
2545
    def getResultsInterpretationByDepartment(self, department=None):
2546
        """Returns the results interpretation for this Analysis Request
2547
           and department. If department not set, returns the results
2548
           interpretation tagged as 'General'.
2549
2550
        :returns: a dict with the following keys:
2551
            {'uid': <department_uid> or 'general', 'richtext': <text/plain>}
2552
        """
2553
        uid = department.UID() if department else 'general'
2554
        rows = self.Schema()['ResultsInterpretationDepts'].get(self)
2555
        row = [row for row in rows if row.get('uid') == uid]
2556
        if len(row) > 0:
2557
            row = row[0]
2558
        elif uid == 'general' \
2559
                and hasattr(self, 'getResultsInterpretation') \
2560
                and self.getResultsInterpretation():
2561
            row = {'uid': uid, 'richtext': self.getResultsInterpretation()}
2562
        else:
2563
            row = {'uid': uid, 'richtext': ''}
2564
        return row
2565
2566
    def getAnalysisServiceSettings(self, uid):
2567
        """Returns a dictionary with the settings for the analysis service that
2568
        match with the uid provided.
2569
2570
        If there are no settings for the analysis service and
2571
        analysis requests:
2572
2573
        1. looks for settings in AR's ARTemplate. If found, returns the
2574
           settings for the AnalysisService set in the Template
2575
        2. If no settings found, looks in AR's ARProfile. If found, returns the
2576
           settings for the AnalysisService from the AR Profile. Otherwise,
2577
           returns a one entry dictionary with only the key 'uid'
2578
        """
2579
        sets = [s for s in self.getAnalysisServicesSettings()
2580
                if s.get('uid', '') == uid]
2581
2582
        # Created by using an ARTemplate?
2583
        if not sets and self.getTemplate():
2584
            adv = self.getTemplate().getAnalysisServiceSettings(uid)
2585
            sets = [adv] if 'hidden' in adv else []
2586
2587
        # Created by using an AR Profile?
2588
        if not sets and self.getProfiles():
2589
            adv = []
2590
            adv += [profile.getAnalysisServiceSettings(uid) for profile in
2591
                    self.getProfiles()]
2592
            sets = adv if 'hidden' in adv[0] else []
2593
2594
        return sets[0] if sets else {'uid': uid}
2595
2596
    def getPartitions(self):
2597
        """This functions returns the partitions from the analysis request's
2598
        analyses.
2599
2600
        :returns: a list with the full partition objects
2601
        """
2602
        partitions = []
2603
        for analysis in self.getAnalyses(full_objects=True):
2604
            if analysis.getSamplePartition() not in partitions:
2605
                partitions.append(analysis.getSamplePartition())
2606
        return partitions
2607
2608
    def getContainers(self):
2609
        """This functions returns the containers from the analysis request's
2610
        analyses
2611
2612
        :returns: a list with the full partition objects
2613
        """
2614
        partitions = self.getPartitions()
2615
        containers = []
2616
        for partition in partitions:
2617
            if partition.getContainer():
2618
                containers.append(partition.getContainer())
2619
        return containers
2620
2621 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...
2622
        """Checks if the analysis service that match with the uid provided must
2623
        be hidden in results. If no hidden assignment has been set for the
2624
        analysis in this request, returns the visibility set to the analysis
2625
        itself.
2626
2627
        Raise a TypeError if the uid is empty or None
2628
2629
        Raise a ValueError if there is no hidden assignment in this request or
2630
        no analysis service found for this uid.
2631
        """
2632
        if not uid:
2633
            raise TypeError('None type or empty uid')
2634
        sets = self.getAnalysisServiceSettings(uid)
2635
        if 'hidden' not in sets:
2636
            uc = getToolByName(self, 'uid_catalog')
2637
            serv = uc(UID=uid)
2638
            if serv and len(serv) == 1:
2639
                return serv[0].getObject().getRawHidden()
2640
            else:
2641
                raise ValueError('{} is not valid'.format(uid))
2642
        return sets.get('hidden', False)
2643
2644
    def getRejecter(self):
2645
        """If the Analysis Request has been rejected, returns the user who did the
2646
        rejection. If it was not rejected or the current user has not enough
2647
        privileges to access to this information, returns None.
2648
        """
2649
        wtool = getToolByName(self, 'portal_workflow')
2650
        mtool = getToolByName(self, 'portal_membership')
2651
        # noinspection PyBroadException
2652
        try:
2653
            review_history = wtool.getInfoFor(self, 'review_history')
2654
        except:  # noqa FIXME: remove blind except!
2655
            return None
2656
        for items in review_history:
2657
            action = items.get('action')
2658
            if action != 'reject':
2659
                continue
2660
            actor = items.get('actor')
2661
            return mtool.getMemberById(actor)
2662
        return None
2663
2664
    def getReceivedBy(self):
2665
        """
2666
        Returns the User who received the analysis request.
2667
        :returns: the user id
2668
        """
2669
        user = getTransitionUsers(self, 'receive', last_user=True)
2670
        return user[0] if user else ''
2671
2672
    def getDateVerified(self):
2673
        """
2674
        Returns the date of verification as a DateTime object.
2675
        """
2676
        return getTransitionDate(self, 'verify', return_as_datetime=True)
2677
2678
    @security.public
2679
    def getPrioritySortkey(self):
2680
        """Returns the key that will be used to sort the current Analysis
2681
        Request based on both its priority and creation date. On ASC sorting,
2682
        the oldest item with highest priority will be displayed.
2683
        :return: string used for sorting
2684
        """
2685
        priority = self.getPriority()
2686
        created_date = self.created().ISO8601()
2687
        return '%s.%s' % (priority, created_date)
2688
2689
    @security.public
2690
    def setPriority(self, value):
2691
        if not value:
2692
            value = self.Schema().getField('Priority').getDefault(self)
2693
        original_value = self.Schema().getField('Priority').get(self)
2694
        if original_value != value:
2695
            self.Schema().getField('Priority').set(self, value)
2696
            self._reindexAnalyses(['getPrioritySortkey'], True)
2697
2698
    @security.private
2699
    def _reindexAnalyses(self, idxs=None, update_metadata=False):
2700
        if not idxs and not update_metadata:
2701
            return
2702
        if not idxs:
2703
            idxs = []
2704
        analyses = self.getAnalyses()
2705
        catalog = getToolByName(self, CATALOG_ANALYSIS_LISTING)
2706
        for analysis in analyses:
2707
            analysis_obj = analysis.getObject()
2708
            catalog.reindexObject(analysis_obj, idxs=idxs, update_metadata=1)
2709
2710
    def _getCreatorFullName(self):
2711
        """
2712
        Returns the full name of this analysis request's creator.
2713
        """
2714
        return user_fullname(self, self.Creator())
2715
2716
    def _getCreatorEmail(self):
2717
        """
2718
        Returns the email of this analysis request's creator.
2719
        """
2720
        return user_email(self, self.Creator())
2721
2722
    def _getSamplerFullName(self):
2723
        """
2724
        Returns the full name's defined sampler.
2725
        """
2726
        return user_fullname(self, self.getSampler())
2727
2728
    def _getSamplerEmail(self):
2729
        """
2730
        Returns the email of this analysis request's sampler.
2731
        """
2732
        return user_email(self, self.Creator())
2733
2734 View Code Duplication
    def getObjectWorkflowStates(self):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
2735
        """
2736
        This method is used as a metacolumn.
2737
        Returns a dictionary with the workflow id as key and workflow state as
2738
        value.
2739
        :returns: {'review_state':'active',...}
2740
        """
2741
        workflow = getToolByName(self, 'portal_workflow')
2742
        states = {}
2743
        for w in workflow.getWorkflowsFor(self):
2744
            state = w._getWorkflowStateOf(self).id
2745
            states[w.state_var] = state
2746
        return states
2747
2748
    @security.public
2749
    def guard_to_be_preserved(self):
2750
        return guards.to_be_preserved(self)
2751
2752
    @security.public
2753
    def guard_unassign_transition(self):
2754
        return guards.unassign(self)
2755
2756
    @security.public
2757
    def guard_assign_transition(self):
2758
        return guards.assign(self)
2759
2760
    @security.public
2761
    def guard_receive_transition(self):
2762
        return guards.receive(self)
2763
2764
    @security.public
2765
    def guard_schedule_sampling_transition(self):
2766
        return guards.schedule_sampling(self)
2767
2768
    @security.public
2769
    def guard_prepublish_transition(self):
2770
        return guards.prepublish(self)
2771
2772
    def SearchableText(self):
2773
        """
2774
        Override searchable text logic based on the requirements.
2775
2776
        This method constructs a text blob which contains all full-text
2777
        searchable text for this content item.
2778
        https://docs.plone.org/develop/plone/searching_and_indexing/indexing.html#full-text-searching
2779
        """
2780
2781
        # Speed up string concatenation ops by using a buffer
2782
        entries = []
2783
2784
        # plain text fields we index from ourself,
2785
        # a list of accessor methods of the class
2786
        plain_text_fields = ("getId", )
2787
2788
        def read(acc):
2789
            """
2790
            Call a class accessor method to give a value for certain Archetypes
2791
            field.
2792
            """
2793
            try:
2794
                val = acc()
2795
            except Exception as e:
2796
                message = "Error getting the accessor parameter in " \
2797
                          "SearchableText from the Analysis Request Object " \
2798
                          "{}: {}".format(self.getId(), e.message)
2799
                logger.error(message)
2800
                val = ""
2801
2802
            if val is None:
2803
                val = ""
2804
2805
            return val
2806
2807
        # Concatenate plain text fields as they are
2808
        for f in plain_text_fields:
2809
            accessor = getattr(self, f)
2810
            value = read(accessor)
2811
            entries.append(value)
2812
2813
        # Adding HTML Fields to SearchableText can be uncommented if necessary
2814
        # transforms = getToolByName(self, 'portal_transforms')
2815
        #
2816
        # # Run HTML valued fields through text/plain conversion
2817
        # for f in html_fields:
2818
        #     accessor = getattr(self, f)
2819
        #     value = read(accessor)
2820
        #
2821
        #     if value != "":
2822
        #         stream = transforms.convertTo('text/plain', value,
2823
        #                                       mimetype='text/html')
2824
        #         value = stream.getData()
2825
        #
2826
        #     entries.append(value)
2827
2828
        # Plone accessor methods assume utf-8
2829
        def convertToUTF8(text):
2830
            if type(text) == unicode:
2831
                return text.encode("utf-8")
2832
            return text
2833
2834
        entries = [convertToUTF8(entry) for entry in entries]
2835
2836
        # Concatenate all strings to one text blob
2837
        return " ".join(entries)
2838
2839
    def getPriorityText(self):
2840
        """
2841
        This function looks up the priority text from priorities vocab
2842
        :returns: the priority text or ''
2843
        """
2844
        if self.getPriority():
2845
            return PRIORITIES.getValue(self.getPriority())
2846
        return ''
2847
2848
    def get_ARAttachment(self):
2849
        logger.warn("_ARAttachment is a virtual field used in AR Add. "
2850
                    "It can not hold an own value!")
2851
        return None
2852
2853
    def set_ARAttachment(self, value):
2854
        logger.warn("_ARAttachment is a virtual field used in AR Add. "
2855
                    "It can not hold an own value!")
2856
        return None
2857
2858
    def get_retest(self):
2859
        """Returns the Analysis Request automatically generated because of the
2860
        retraction of the current analysis request
2861
        """
2862
        relationship = "AnalysisRequestRetracted"
2863
        retest = self.getBackReferences(relationship=relationship)
2864
        if retest and len(retest) > 1:
2865
            logger.warn("More than one retest for {0}".format(self.getId()))
2866
        return retest and retest[0] or None
2867
2868
    def getAncestors(self, all_ancestors=True):
2869
        """Returns the ancestor(s) of this Analysis Request
2870
        param all_ancestors: include all ancestors, not only the parent
2871
        """
2872
        parent = self.getParentAnalysisRequest()
2873
        if not parent:
2874
            return list()
2875
        if not all_ancestors:
2876
            return [parent]
2877
        return [parent] + parent.getAncestors(all_ancestors=True)
2878
2879
    def isRootAncestor(self):
2880
        """Returns True if the AR is the root ancestor
2881
2882
        :returns: True if the AR has no more parents
2883
        """
2884
        parent = self.getParentAnalysisRequest()
2885
        if parent:
2886
            return False
2887
        return True
2888
2889
    def getDescendants(self, all_descendants=False):
2890
        """Returns the descendant Analysis Requests
2891
2892
        :param all_descendants: recursively include all descendants
2893
        """
2894
2895
        # N.B. full objects returned here from
2896
        #      `Products.Archetypes.Referenceable.getBRefs`
2897
        #      -> don't add this method into Metadata
2898
        children = self.getBackReferences(
2899
            "AnalysisRequestParentAnalysisRequest")
2900
2901
        descendants = []
2902
2903
        # recursively include all children
2904
        if all_descendants:
2905
            for child in children:
2906
                descendants.append(child)
2907
                descendants += child.getDescendants(all_descendants=True)
2908
        else:
2909
            descendants = children
2910
2911
        return descendants
2912
2913
    def getDescendantsUIDs(self, all_descendants=False):
2914
        """Returns the UIDs of the descendant Analysis Requests
2915
2916
        This method is used as metadata
2917
        """
2918
        descendants = self.getDescendants(all_descendants=all_descendants)
2919
        return map(api.get_uid, descendants)
2920
2921
    def isPartition(self):
2922
        """Returns true if this Analysis Request is a partition
2923
        """
2924
        return not self.isRootAncestor()
2925
2926
2927
registerType(AnalysisRequest, PROJECTNAME)
2928