Passed
Push — master ( 5f146e...28e0c7 )
by Jordi
04:40
created

bika.lims.workflow.analysis.guards   F

Complexity

Total Complexity 76

Size/Duplication

Total Lines 313
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 76
eloc 153
dl 0
loc 313
rs 2.32
c 0
b 0
f 0

21 Functions

Rating   Name   Duplication   Size   Complexity  
A guard_initialize() 0 7 2
B guard_multi_verify() 0 30 8
A is_multi_verification_allowed() 0 5 1
A guard_retract() 0 9 2
A guard_unassign() 0 9 2
A was_transition_performed() 0 11 5
A guard_reinstate() 0 6 1
A is_consecutive_multi_verification_allowed() 0 5 1
F guard_submit() 0 40 15
A was_verified_by_current_user() 0 4 1
A user_has_super_roles() 0 7 2
A guard_cancel() 0 6 1
A current_user_was_last_verifier() 0 5 1
B guard_assign() 0 29 7
A was_submitted_by_current_user() 0 4 1
B is_transition_allowed_or_performed() 0 17 8
A user_can_manage_worksheets() 0 9 2
C guard_verify() 0 34 9
A guard_publish() 0 6 1
A is_transition_allowed() 0 11 5
A guard_reject() 0 5 1

How to fix   Complexity   

Complexity

Complex classes like bika.lims.workflow.analysis.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
# Copyright 2018 by it's authors.
6
# Some rights reserved. See LICENSE.rst, CONTRIBUTORS.rst.
7
8
from bika.lims import api
9
from bika.lims.interfaces import IWorksheet
10
from bika.lims.interfaces.analysis import IRequestAnalysis
11
from bika.lims import workflow as wf
12
13
14
def guard_initialize(analysis):
15
    """Return whether the transition "initialize" can be performed or not
16
    """
17
    request = analysis.getRequest()
18
    if request.getDateReceived():
19
        return True
20
    return False
21
22
23
def guard_assign(analysis):
24
    """Return whether the transition "assign" can be performed or not
25
    """
26
27
    # TODO: Refactor this funtion to a more generic place
28
    # only if the request was done from worksheet context.
29
    request = api.get_request()
30
    parents = request.get("PARENTS", [])
31
    portal_types_names = map(lambda p: getattr(p, "portal_type", None), parents)
32
    if "Worksheet" not in portal_types_names:
33
        # Check if the worksheet is declared in request explicitly
34
        ws_uid = request.get("ws_uid", "")
35
        obj = api.get_object_by_uid(ws_uid, None)
36
        if not IWorksheet.providedBy(obj):
37
            return False
38
39
    # Cannot assign if the Sample has not been received
40
    if not analysis.isSampleReceived():
41
        return False
42
43
    # Cannot assign if the analysis has a worksheet assigned already
44
    if analysis.getWorksheet():
45
        return False
46
47
    # Cannot assign if user does not have permissions to manage worksheets
48
    if not user_can_manage_worksheets():
49
        return False
50
51
    return True
52
53
54
def guard_unassign(analysis):
55
    """Return whether the transition "unassign" can be performed or not
56
    """
57
    # Cannot unassign if the analysis is not assigned to any worksheet
58
    if not analysis.getWorksheet():
59
        return False
60
61
    # Cannot unassign if user does not have permissions to manage worksheets
62
    return user_can_manage_worksheets()
63
64
65
def guard_cancel(analysis):
66
    """Return whether the transition "cancel" can be performed or not. Returns
67
    True only when the Analysis Request the analysis belongs to is in cancelled
68
    state. Otherwise, returns False.
69
    """
70
    return not api.is_active(analysis.getRequest())
71
72
73
def guard_reinstate(analysis):
74
    """Return whether the transition "reinstate" can be performed or not.
75
    Returns True only when the Analysis Request the analysis belongs to is in a
76
    non-cancelled state. Otherwise, returns False.
77
    """
78
    return api.is_active(analysis.getRequest())
79
80
81
def guard_submit(analysis):
82
    """Return whether the transition "submit" can be performed or not
83
    """
84
    # Cannot submit without a result
85
    if not analysis.getResult():
86
        return False
87
88
    # Cannot submit with interims without value
89
    for interim in analysis.getInterimFields():
90
        if not interim.get("value", ""):
91
            return False
92
93
    # Cannot submit if attachment not set, but is required
94
    if not analysis.getAttachment():
95
        if analysis.getAttachmentOption() == 'r':
96
            return False
97
98
    # Check if can submit based on the Analysis Request state
99
    if IRequestAnalysis.providedBy(analysis):
100
        point_of_capture = analysis.getPointOfCapture()
101
        # Cannot submit if the Sample has not been received
102
        if point_of_capture == "lab" and not analysis.isSampleReceived():
103
            return False
104
        # Cannot submit if the Sample has not been sampled
105
        if point_of_capture == "field" and not analysis.isSampleSampled():
106
            return False
107
108
    # Check if the current user can submit if is not assigned
109
    if not analysis.bika_setup.getAllowToSubmitNotAssigned():
110
        if not user_has_super_roles():
111
            # Cannot submit if unassigned
112
            if not analysis.getAnalyst():
113
                return False
114
            # Cannot submit if assigned analyst is not the current user
115
            if analysis.getAnalyst() != api.get_current_user().getId():
116
                return False
117
118
    # Cannot submit unless all dependencies are submitted or can be submitted
119
    dependencies = analysis.getDependencies()
120
    return is_transition_allowed_or_performed(dependencies, "submit")
121
122
123
def guard_multi_verify(analysis):
124
    """Return whether the transition "multi_verify" can be performed or not
125
    The transition multi_verify will only take place if multi-verification of
126
    results is enabled.
127
    """
128
    # Cannot multiverify if there is only one remaining verification
129
    remaining_verifications = analysis.getNumberOfRemainingVerifications()
130
    if remaining_verifications <= 1:
131
        return False
132
133
    # Cannot verify if the user submitted and self-verification is not allowed
134
    if was_submitted_by_current_user(analysis):
135
        if not analysis.isSelfVerificationEnabled():
136
            return False
137
138
    # Cannot verify if the user verified and multi verification is not allowed
139
    if was_verified_by_current_user(analysis):
140
        if not is_multi_verification_allowed(analysis):
141
            return False
142
143
    # Cannot verify if the user was last verifier and consecutive verification
144
    # by same user is not allowed
145
    if current_user_was_last_verifier(analysis):
146
        if not is_consecutive_multi_verification_allowed(analysis):
147
            return False
148
149
    # Cannot verify unless all dependencies are verified or can be verified
150
    dependencies = analysis.getDependencies()
151
    transitions = ["verify", "multi_verify"]
152
    return is_transition_allowed_or_performed(dependencies, transitions)
153
154
155
def guard_verify(analysis):
156
    """Return whether the transition "verify" can be performed or not
157
    """
158
    # Cannot verify if the number of remaining verifications is > 1
159
    remaining_verifications = analysis.getNumberOfRemainingVerifications()
160
    if remaining_verifications > 1:
161
        return False
162
163
    # Cannot verify if the user submitted and self-verification is not allowed
164
    if was_submitted_by_current_user(analysis):
165
        if not analysis.isSelfVerificationEnabled():
166
            return False
167
168
    # Cannot verify unless dependencies have been verified or can be verified
169
    if analysis.getNumberOfRequiredVerifications() <= 1:
170
        dependencies = analysis.getDependencies()
171
        return is_transition_allowed_or_performed(dependencies, "verify")
172
173
    # This analysis has multi-verification enabled
174
    # Cannot verify if the user verified and multi verification is not allowed
175
    if was_verified_by_current_user(analysis):
176
        if not is_multi_verification_allowed(analysis):
177
            return False
178
179
    # Cannot verify if the user was last verifier and consecutive verification
180
    # by same user is not allowed
181
    if current_user_was_last_verifier(analysis):
182
        if not is_consecutive_multi_verification_allowed(analysis):
183
            return False
184
185
    # Cannot verify unless all dependencies are verified or can be verified
186
    dependencies = analysis.getDependencies()
187
    transitions = ["verify", "multi_verify"]
188
    return is_transition_allowed_or_performed(dependencies, transitions)
189
190
191
def guard_retract(analysis):
192
    """ Return whether the transition "retract" can be performed or not
193
    """
194
    # Cannot retract if there are dependents that cannot be retracted
195
    if not is_transition_allowed(analysis.getDependents(), "retract"):
196
        return False
197
198
    # Cannot retract if all dependencies have been verified
199
    return not was_transition_performed(analysis.getDependencies(), "verify")
200
201
202
def guard_reject(analysis):
203
    """Return whether the transition "reject" can be performed or not
204
    """
205
    # Cannot reject if there are dependents that cannot be rejected
206
    return is_transition_allowed(analysis.getDependents(), "reject")
207
208
209
def guard_publish(analysis):
210
    """Return whether the transition "publish" can be performed or not. Returns
211
    True only when the Analysis Request the analysis belongs to is in published
212
    state. Otherwise, returns False.
213
    """
214
    return api.get_workflow_status_of(analysis.getRequest()) == "published"
215
216
217
def user_can_manage_worksheets():
218
    """Return whether the current user has privileges to manage worksheets
219
    """
220
    if not api.get_setup().getRestrictWorksheetManagement():
221
        # There is no restriction, everybody can manage worksheets
222
        return True
223
224
    # Only Labmanager and Manager roles can manage worksheets
225
    return user_has_super_roles()
226
227
228
def user_has_super_roles():
229
    """Return whether the current belongs to superuser roles
230
    """
231
    member = api.get_current_user()
232
    super_roles = ["LabManager", "Manager"]
233
    diff = filter(lambda role: role in super_roles, member.getRoles())
234
    return len(diff) > 0
235
236
237
def was_submitted_by_current_user(analysis):
238
    """Returns whether the analysis was submitted by current user or not
239
    """
240
    return analysis.getSubmittedBy() == api.get_current_user().getId()
241
242
243
def was_verified_by_current_user(analysis):
244
    """Returns whether the analysis was verified by current user or not
245
    """
246
    return api.get_current_user().getId() in analysis.getVerificators()
247
248
249
def current_user_was_last_verifier(analysis):
250
    """Returns whether the current user was the last verifier or not
251
    """
252
    verifiers = analysis.getVerificators()
253
    return verifiers and verifiers[:-1] == api.get_current_user().getId()
254
255
256
def is_consecutive_multi_verification_allowed(analysis):
257
    """Returns whether multiple verification and consecutive verification is
258
    allowed or not"""
259
    multi_type = api.get_setup().getTypeOfmultiVerification()
260
    return multi_type != "self_multi_not_cons"
261
262
263
def is_multi_verification_allowed(analysis):
264
    """Returns whether multi verification is allowed or not
265
    """
266
    multi_type = api.get_setup().getTypeOfmultiVerification()
267
    return multi_type != "self_multi_disabled"
268
269
270
def is_transition_allowed(analyses, transition_id):
271
    """Returns whether all analyses can be transitioned or not
272
    """
273
    if not analyses:
274
        return True
275
    if not isinstance(analyses, list):
276
        return is_transition_allowed([analyses], transition_id)
277
    for analysis in analyses:
278
        if not wf.isTransitionAllowed(analysis, transition_id):
279
            return False
280
    return True
281
282
283
def was_transition_performed(analyses, transition_id):
284
    """Returns whether all analyses were transitioned or not
285
    """
286
    if not analyses:
287
        return False
288
    if not isinstance(analyses, list):
289
        return was_transition_performed([analyses], transition_id)
290
    for analysis in analyses:
291
        if not wf.wasTransitionPerformed(analysis, transition_id):
292
            return False
293
    return True
294
295
296
def is_transition_allowed_or_performed(analyses, transition_ids):
297
    """Return whether all analyses can be transitioned or all them were
298
    transitioned.
299
    """
300
    if not analyses:
301
        return True
302
    if not isinstance(analyses, list):
303
        return is_transition_allowed_or_performed([analyses], transition_ids)
304
    if not isinstance(transition_ids, list):
305
        return is_transition_allowed_or_performed(analyses, [transition_ids])
306
307
    for transition_id in transition_ids:
308
        for analysis in analyses:
309
            if not wf.isTransitionAllowed(analysis, transition_id):
310
                if not wf.wasTransitionPerformed(analysis, transition_id):
311
                    return False
312
    return True
313