Passed
Push — master ( d8e2ec...90ae0b )
by Jordi
10:07 queued 04:19
created

build.bika.lims.content.analysisrequest   F

Complexity

Total Complexity 205

Size/Duplication

Total Lines 2336
Duplicated Lines 1.5 %

Importance

Changes 0
Metric Value
wmc 205
eloc 1587
dl 35
loc 2336
rs 0.8
c 0
b 0
f 0

80 Methods

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

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