Passed
Push — master ( d6b140...72cad7 )
by Ramon
05:21
created

guard_verify()   D

Complexity

Conditions 13

Size

Total Lines 37
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 22
dl 0
loc 37
rs 4.2
c 0
b 0
f 0
cc 13
nop 1

How to fix   Complexity   

Complexity

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