Passed
Push — master ( 743380...0e02ba )
by Ramon
05:40
created

bika.lims.workflow.analysis.events   A

Complexity

Total Complexity 36

Size/Duplication

Total Lines 244
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 36
eloc 103
dl 0
loc 244
rs 9.52
c 0
b 0
f 0

15 Functions

Rating   Name   Duplication   Size   Complexity  
A after_assign() 0 5 1
A after_verify() 0 25 4
A after_reject() 0 13 2
A after_submit() 0 26 4
A after_cancel() 0 6 1
A after_unassign() 0 8 1
A before_unassign() 0 10 3
A after_retract() 0 35 3
A before_reject() 0 10 3
A after_reinstate() 0 4 1
A after_publish() 0 4 1
A remove_analysis_from_worksheet() 0 23 4
A cascade_to_dependents() 0 6 2
A reindex_request() 0 15 4
A promote_to_dependencies() 0 6 2
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 transaction
9
from Products.CMFCore.utils import getToolByName
10
11
from bika.lims import api
12
from bika.lims.interfaces import IDuplicateAnalysis
13
from bika.lims.interfaces.analysis import IRequestAnalysis
14
from bika.lims.utils.analysis import create_analysis
15
from bika.lims.workflow import doActionFor, push_reindex_to_actions_pool
16
from bika.lims.workflow import skip
17
18
19
def after_assign(analysis):
20
    """Function triggered after an 'assign' transition for the analysis passed
21
    in is performed.
22
    """
23
    reindex_request(analysis)
24
25
26
def before_unassign(analysis):
27
    """Function triggered before 'unassign' transition takes place
28
    """
29
    worksheet = analysis.getWorksheet()
30
    if not worksheet:
31
        return
32
33
    # Removal of a routine analysis causes the removal of their duplicates
34
    for dup in worksheet.get_duplicates_for(analysis):
35
        doActionFor(dup, "unassign")
36
37
38
def before_reject(analysis):
39
    """Function triggered before 'unassign' transition takes place
40
    """
41
    worksheet = analysis.getWorksheet()
42
    if not worksheet:
43
        return
44
45
    # Rejection of a routine analysis causes the removal of their duplicates
46
    for dup in worksheet.get_duplicates_for(analysis):
47
        doActionFor(dup, "unassign")
48
49
50
def after_unassign(analysis):
51
    """Function triggered after an 'unassign' transition for the analysis passed
52
    in is performed.
53
    """
54
    # Remove from the worksheet
55
    remove_analysis_from_worksheet(analysis)
56
    # Reindex the Analysis Request
57
    reindex_request(analysis)
58
59
60
def after_cancel(analysis):
61
    """Function triggered after a "cancel" transition is performed. Removes the
62
    cancelled analysis from the worksheet, if any.
63
    """
64
    # Remove from the worksheet
65
    remove_analysis_from_worksheet(analysis)
66
67
68
def after_reinstate(analysis):
69
    """Function triggered after a "reinstate" transition is performed.
70
    """
71
    pass
72
73
74
def after_submit(analysis):
75
    """Method triggered after a 'submit' transition for the analysis passed in
76
    is performed. Promotes the submit transition to the Worksheet to which the
77
    analysis belongs to. Note that for the worksheet there is already a guard
78
    that assures the transition to the worksheet will only be performed if all
79
    analyses within the worksheet have already been transitioned.
80
    This function is called automatically by
81
    bika.lims.workfow.AfterTransitionEventHandler
82
    """
83
    # Promote to analyses this analysis depends on
84
    promote_to_dependencies(analysis, "submit")
85
86
    # TODO: REFLEX TO REMOVE
87
    # Do all the reflex rules process
88
    if IRequestAnalysis.providedBy(analysis):
89
        analysis._reflex_rule_process('submit')
90
91
    # Promote transition to worksheet
92
    ws = analysis.getWorksheet()
93
    if ws:
94
        doActionFor(ws, 'submit')
95
96
    # Promote transition to Analysis Request
97
    if IRequestAnalysis.providedBy(analysis):
98
        doActionFor(analysis.getRequest(), 'submit')
99
        reindex_request(analysis)
100
101
102
def after_retract(analysis):
103
    """Function triggered after a 'retract' transition for the analysis passed
104
    in is performed. The analysis transitions to "retracted" state and a new
105
    copy of the analysis is created. The copy initial state is "unassigned",
106
    unless the the retracted analysis was assigned to a worksheet. In such case,
107
    the copy is transitioned to 'assigned' state too
108
    """
109
    # Rename the analysis to make way for it's successor.
110
    # Support multiple retractions by renaming to *-0, *-1, etc
111
    parent = analysis.aq_parent
112
    keyword = analysis.getKeyword()
113
    analyses = filter(lambda an: an.getKeyword() == keyword,
114
                      parent.objectValues("Analysis"))
115
116
    # Rename the retracted analysis
117
    # https://docs.plone.org/develop/plone/content/rename.html
118
    # _verifyObjectPaste permission check must be cancelled
119
    parent._verifyObjectPaste = str
120
    retracted_id = '{}-{}'.format(keyword, len(analyses))
121
    # Make sure all persistent objects have _p_jar attribute
122
    transaction.savepoint(optimistic=True)
123
    parent.manage_renameObject(analysis.getId(), retracted_id)
124
    delattr(parent, '_verifyObjectPaste')
125
126
    # Create a copy of the retracted analysis
127
    analysis_uid = api.get_uid(analysis)
128
    new_analysis = create_analysis(parent, analysis, RetestOf=analysis_uid)
129
130
    # Assign the new analysis to this same worksheet, if any.
131
    worksheet = analysis.getWorksheet()
132
    if worksheet:
133
        worksheet.addAnalysis(new_analysis)
134
135
    # Retract our dependents (analyses that depend on this analysis)
136
    cascade_to_dependents(analysis, "retract")
137
138
139
def after_reject(analysis):
140
    """Function triggered after the "reject" transition for the analysis passed
141
    in is performed."""
142
    # Remove from the worksheet
143
    remove_analysis_from_worksheet(analysis)
144
145
    # Reject our dependents (analyses that depend on this analysis)
146
    cascade_to_dependents(analysis, "reject")
147
148
    # Try to rollback the Analysis Request (all analyses rejected)
149
    if IRequestAnalysis.providedBy(analysis):
150
        doActionFor(analysis.getRequest(), "rollback_to_receive")
151
        reindex_request(analysis)
152
153
154
def after_verify(analysis):
155
    """
156
    Method triggered after a 'verify' transition for the analysis passed in
157
    is performed. Promotes the transition to the Analysis Request and to
158
    Worksheet (if the analysis is assigned to any)
159
    This function is called automatically by
160
    bika.lims.workfow.AfterTransitionEventHandler
161
    """
162
    # Promote to analyses this analysis depends on
163
    promote_to_dependencies(analysis, "verify")
164
165
    # TODO: REFLEX TO REMOVE
166
    # Do all the reflex rules process
167
    if IRequestAnalysis.providedBy(analysis):
168
        analysis._reflex_rule_process('verify')
169
170
    # Promote transition to worksheet
171
    ws = analysis.getWorksheet()
172
    if ws:
173
        doActionFor(ws, 'verify')
174
175
    # Promote transition to Analysis Request
176
    if IRequestAnalysis.providedBy(analysis):
177
        doActionFor(analysis.getRequest(), 'verify')
178
        reindex_request(analysis)
179
180
181
def after_publish(analysis):
182
    """Function triggered after a "publish" transition is performed.
183
    """
184
    pass
185
186
187
# TODO Workflow - Analysis - revisit reindexing of ancestors
188
def reindex_request(analysis, idxs=None):
189
    """Reindex the Analysis Request the analysis belongs to, as well as the
190
    ancestors recursively
191
    """
192
    if not IRequestAnalysis.providedBy(analysis) or \
193
            IDuplicateAnalysis.providedBy(analysis):
194
        # Analysis not directly bound to an Analysis Request. Do nothing
195
        return
196
197
    n_idxs = ['assigned_state', 'getDueDate']
198
    n_idxs = idxs and list(set(idxs + n_idxs)) or n_idxs
199
    request = analysis.getRequest()
200
    ancestors = [request] + request.getAncestors(all_ancestors=True)
201
    for ancestor in ancestors:
202
        push_reindex_to_actions_pool(ancestor, n_idxs)
203
204
205
def remove_analysis_from_worksheet(analysis):
206
    """Removes the analysis passed in from the worksheet, if assigned to any
207
    """
208
    worksheet = analysis.getWorksheet()
209
    if not worksheet:
210
        return
211
212
    analyses = filter(lambda an: an != analysis, worksheet.getAnalyses())
213
    worksheet.setAnalyses(analyses)
214
    worksheet.purgeLayout()
215
    if analyses:
216
        # Maybe this analysis was the only one that was not yet submitted or
217
        # verified, so try to submit or verify the Worksheet to be aligned
218
        # with the current states of the analyses it contains.
219
        doActionFor(worksheet, "submit")
220
        doActionFor(worksheet, "verify")
221
    else:
222
        # We've removed all analyses. Rollback to "open"
223
        doActionFor(worksheet, "rollback_to_open")
224
225
    # Reindex the Worksheet
226
    idxs = ["getAnalysesUIDs", "getDepartmentUIDs"]
227
    push_reindex_to_actions_pool(worksheet, idxs=idxs)
228
229
230
def cascade_to_dependents(analysis, transition_id):
231
    """Cascades the transition to dependent analyses (those that depend on the
232
    analysis passed in), if any
233
    """
234
    for dependent in analysis.getDependents():
235
        doActionFor(dependent, transition_id)
236
237
238
def promote_to_dependencies(analysis, transition_id):
239
    """Promotes the transition to the analyses this analysis depends on
240
    (dependencies), if any
241
    """
242
    for dependency in analysis.getDependencies():
243
        doActionFor(dependency, transition_id)
244