Passed
Push — 2.x ( da17b1...a43b53 )
by Jordi
06:56
created

bika.lims.workflow.analysisrequest.guards   B

Complexity

Total Complexity 45

Size/Duplication

Total Lines 250
Duplicated Lines 20.8 %

Importance

Changes 0
Metric Value
wmc 45
eloc 106
dl 52
loc 250
rs 8.8
c 0
b 0
f 0

17 Functions

Rating   Name   Duplication   Size   Complexity  
A guard_dispatch() 0 9 3
A guard_prepublish() 0 14 4
A guard_to_be_sampled() 0 5 1
A guard_no_sampling_workflow() 0 5 1
A guard_cancel() 0 22 5
B guard_verify() 26 26 6
A guard_restore() 0 4 1
A guard_detach() 0 6 1
A guard_sample() 0 10 3
A guard_publish() 0 10 2
A guard_rollback_to_receive() 0 17 4
A guard_reinstate() 0 11 3
A guard_schedule_sampling() 0 5 1
A guard_reject() 0 5 1
A guard_create_partitions() 0 9 2
B guard_submit() 26 26 6
A guard_multi_results() 0 4 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 bika.lims.workflow.analysisrequest.guards 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
# SENAITE.CORE is free software: you can redistribute it and/or modify it under
6
# the terms of the GNU General Public License as published by the Free Software
7
# Foundation, version 2.
8
#
9
# This program is distributed in the hope that it will be useful, but WITHOUT
10
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
12
# details.
13
#
14
# You should have received a copy of the GNU General Public License along with
15
# this program; if not, write to the Free Software Foundation, Inc., 51
16
# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17
#
18
# Copyright 2018-2021 by it's authors.
19
# Some rights reserved, see README and LICENSE.
20
21
from bika.lims import api
22
from bika.lims.interfaces import IInternalUse
23
from bika.lims.interfaces import IVerified
24
from bika.lims.workflow import isTransitionAllowed
25
26
# States to be omitted in regular transitions
27
ANALYSIS_DETACHED_STATES = ['cancelled', 'rejected', 'retracted']
28
29
30
def guard_no_sampling_workflow(analysis_request):
31
    """Returns whether the transition "no_sampling_workflow" can be performed
32
    or not. Returns True when Sampling Workflow is not enabled in setup
33
    """
34
    return not analysis_request.getSamplingRequired()
35
36
37
def guard_to_be_sampled(analysis_request):
38
    """Returns whether the transition "to_be_sampled" can be performed or not.
39
    Returns True if Sampling Workflow is enabled for the analysis request
40
    """
41
    return analysis_request.getSamplingRequired()
42
43
44
def guard_schedule_sampling(analysis_request):
45
    """Return whether the transition "schedule_sampling" can be performed or not
46
    Returns True only when the schedule sampling workflow is enabled in setup.
47
    """
48
    return analysis_request.bika_setup.getScheduleSamplingEnabled()
49
50
51
def guard_create_partitions(analysis_request):
52
    """Returns true if partitions can be created using the analysis request
53
    passed in as the source.
54
    """
55
    if analysis_request.isPartition():
56
        # Do not allow the creation of partitions from partitions
57
        return False
58
59
    return True
60
61
62 View Code Duplication
def guard_submit(analysis_request):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
63
    """Return whether the transition "submit" can be performed or not.
64
    Returns True if there is at least one analysis in a non-detached state and
65
    all analyses in a non-detached analyses have been submitted.
66
    """
67
    # Discard detached analyses
68
    analyses = analysis_request.getAnalyses(full_objects=True)
69
    analyses = filter(lambda an: api.get_workflow_status_of(an) not in
70
                                 ANALYSIS_DETACHED_STATES, analyses)
71
72
    # If not all analyses are for internal use, rely on "regular" analyses
73
    internals = map(IInternalUse.providedBy, analyses)
74
    omit_internals = not all(internals)
75
76
    analyses_ready = False
77
    for analysis in analyses:
78
        # Omit analyses for internal use
79
        if omit_internals and IInternalUse.providedBy(analysis):
80
            continue
81
82
        analysis_status = api.get_workflow_status_of(analysis)
83
        if analysis_status in ['assigned', 'unassigned', 'registered']:
84
            return False
85
86
        analyses_ready = True
87
    return analyses_ready
88
89
90 View Code Duplication
def guard_verify(analysis_request):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
91
    """Returns whether the transition "verify" can be performed or not.
92
    Returns True if at there is at least one analysis in a non-dettached state
93
    and all analyses in a non-detached state are in "verified" state.
94
    """
95
    # Discard detached analyses
96
    analyses = analysis_request.getAnalyses(full_objects=True)
97
    analyses = filter(lambda an: api.get_workflow_status_of(an) not in
98
                                 ANALYSIS_DETACHED_STATES, analyses)
99
100
    # If not all analyses are for internal use, rely on "regular" analyses
101
    internals = map(IInternalUse.providedBy, analyses)
102
    omit_internals = not all(internals)
103
104
    analyses_ready = False
105
    for analysis in analyses:
106
        # Omit analyses for internal use
107
        if omit_internals and IInternalUse.providedBy(analysis):
108
            continue
109
110
        # All analyses must be in verified (or further) status
111
        if not IVerified.providedBy(analysis):
112
            return False
113
114
        analyses_ready = True
115
    return analyses_ready
116
117
118
def guard_prepublish(analysis_request):
119
    """Returns whether 'prepublish' transition can be perform or not. Returns
120
    True if the analysis request has at least one analysis in 'verified' or in
121
    'to_be_verified' status. Otherwise, return False
122
    """
123
    if IInternalUse.providedBy(analysis_request):
124
        return False
125
126
    valid_states = ['verified', 'to_be_verified']
127
    for analysis in analysis_request.getAnalyses():
128
        analysis = api.get_object(analysis)
129
        if api.get_workflow_status_of(analysis) in valid_states:
130
            return True
131
    return False
132
133
134
def guard_publish(analysis_request):
135
    """Returns whether the transition "publish" can be performed or not.
136
    Returns True if the analysis request is not labeled for internal use or if
137
    at least one of the contained analyses is not for internal use
138
    """
139
    if IInternalUse.providedBy(analysis_request):
140
        return False
141
    # Note we return True without checking anything else because this
142
    # transition is only available when sample is in verified status
143
    return True
144
145
146
def guard_rollback_to_receive(analysis_request):
147
    """Return whether 'rollback_to_receive' transition can be performed or not.
148
    Returns True if the analysis request has at least one analysis in 'assigned'
149
    or 'unassigned' status. Otherwise, returns False
150
    """
151
    skipped = 0
152
    valid_states = ['unassigned', 'assigned']
153
    skip_states = ['retracted', 'rejected']
154
    analyses = analysis_request.getAnalyses()
155
    for analysis in analyses:
156
        analysis = api.get_object(analysis)
157
        status = api.get_workflow_status_of(analysis)
158
        if status in valid_states:
159
            return True
160
        elif status in skip_states:
161
            skipped += 1
162
    return len(analyses) == skipped
163
164
165
def guard_cancel(analysis_request):
166
    """Returns whether 'cancel' transition can be performed or not. Returns
167
    True only if all analyses are in "unassigned" status
168
    """
169
    # Ask to partitions
170
    for partition in analysis_request.getDescendants(all_descendants=False):
171
        if not isTransitionAllowed(partition, "cancel"):
172
            return False
173
174
    # Look through analyses. We've checked the partitions already, so there is
175
    # no need to look through analyses from partitions again, but through the
176
    # analyses directly bound to the current Analysis Request.
177
    cancellable_states = ["unassigned", "registered"]
178
179
    # also consider the detached states as cancellable
180
    cancellable_states += ANALYSIS_DETACHED_STATES
181
182
    for analysis in analysis_request.objectValues("Analysis"):
183
        if api.get_workflow_status_of(analysis) not in cancellable_states:
184
            return False
185
186
    return True
187
188
189
def guard_reinstate(analysis_request):
190
    """Returns whether 'reinstate" transition can be performed or not. Returns
191
    True only if this is not a partition or the parent analysis request can be
192
    reinstated or is not in a cancelled state
193
    """
194
    parent = analysis_request.getParentAnalysisRequest()
195
    if not parent:
196
        return True
197
    if api.get_workflow_status_of(parent) != "cancelled":
198
        return True
199
    return isTransitionAllowed(parent, "reinstate")
200
201
202
def guard_sample(analysis_request):
203
    """Returns whether 'sample' transition can be performed or not. Returns
204
    True only if the analysis request has the DateSampled and Sampler set or if
205
    the user belongs to the Samplers group
206
    """
207
    if analysis_request.getDateSampled() and analysis_request.getSampler():
208
        return True
209
210
    current_user = api.get_current_user()
211
    return "Sampler" in current_user.getRolesInContext(analysis_request)
212
213
214
def guard_reject(analysis_request):
215
    """Returns whether 'reject' transition can be performed or not. Returns
216
    True only if setup's isRejectionWorkflowEnabled is True
217
    """
218
    return analysis_request.bika_setup.isRejectionWorkflowEnabled()
219
220
221
def guard_detach(analysis_request):
222
    """Returns whether 'detach' transition can be performed or not. Returns True
223
    only if the sample passed in is a partition
224
    """
225
    # Detach transition can only be done to partitions
226
    return analysis_request.isPartition()
227
228
229
def guard_dispatch(sample):
230
    """Checks if the dispatch transition is allowed
231
232
    We prevent dispatching when one analysis is assigned to a worksheet.
233
    """
234
    for analysis in sample.getAnalyses():
235
        if api.get_workflow_status_of(analysis) == "assigned":
236
            return False
237
    return True
238
239
240
def guard_restore(sample):
241
    """Checks if the restore transition is allowed
242
    """
243
    return True
244
245
246
def guard_multi_results(sample):
247
    """Checks if the multi results action is allowed
248
    """
249
    return True
250