Passed
Pull Request — 2.x (#1772)
by Jordi
04:32
created

after_restore()   B

Complexity

Conditions 5

Size

Total Lines 34
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 19
dl 0
loc 34
rs 8.9833
c 0
b 0
f 0
cc 5
nop 1
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.api.snapshot import pause_snapshots_for
23
from bika.lims.api.snapshot import resume_snapshots_for
24
from bika.lims.interfaces import IAnalysisRequestPartition
25
from bika.lims.interfaces import IDetachedPartition
26
from bika.lims.interfaces import IReceived
27
from bika.lims.interfaces import IVerified
28
from bika.lims.utils import changeWorkflowState
29
from bika.lims.utils.analysisrequest import create_retest
30
from bika.lims.workflow import doActionFor as do_action_for
31
from bika.lims.workflow.analysisrequest import do_action_to_analyses
32
from bika.lims.workflow.analysisrequest import do_action_to_ancestors
33
from bika.lims.workflow.analysisrequest import do_action_to_descendants
34
from DateTime import DateTime
35
from Products.CMFCore.WorkflowCore import WorkflowException
36
from senaite.core.workflow import SAMPLE_WORKFLOW
37
from zope.interface import alsoProvides
38
from zope.interface import noLongerProvides
39
40
41
def before_sample(analysis_request):
42
    """Method triggered before "sample" transition for the Analysis Request
43
    passed in is performed
44
    """
45
    if not analysis_request.getDateSampled():
46
        analysis_request.setDateSampled(DateTime())
47
    if not analysis_request.getSampler():
48
        analysis_request.setSampler(api.get_current_user().id)
49
50
51
def after_no_sampling_workflow(analysis_request):
52
    """Function triggered after "no_sampling_workflow transition for the
53
    Analysis Request passed in is performed
54
    """
55
    setup = api.get_setup()
56
    if setup.getAutoreceiveSamples():
57
        # Auto-receive samples is enabled. Note transition to "received" state
58
        # will only take place if the current user has enough privileges (this
59
        # is handled by do_action_for already).
60
        do_action_for(analysis_request, "receive")
61
62
63
def after_reject(analysis_request):
64
    """Method triggered after a 'reject' transition for the Analysis Request
65
    passed in is performed. Cascades the transition to descendants (partitions)
66
    and analyses as well. If "notify on rejection" setting is enabled, sends a
67
    notification to the client contact.
68
    """
69
    do_action_to_descendants(analysis_request, "reject")
70
    do_action_to_analyses(analysis_request, "reject")
71
72
73
def after_retract(analysis_request):
74
    """Method triggered after a 'retract' transition for the Analysis Request
75
    passed in is performed. Cascades the transition to descendants (partitions)
76
    and analyses as well.
77
    """
78
    do_action_to_descendants(analysis_request, "retract")
79
    do_action_to_analyses(analysis_request, "retract")
80
81
82
def after_invalidate(obj):
83
    """Function triggered after 'invalidate' transition for the Analysis
84
    Request passed in is performed. Creates a retest
85
    """
86
    create_retest(obj)
87
88
89
def after_submit(analysis_request):
90
    """Function called after a 'submit' transition is triggered. Promotes the
91
    submission of ancestors and descendants (partitions).
92
    """
93
    # This transition is not meant to be triggered manually by the user, rather
94
    # promoted by analyses. Hence, there is no need to cascade the transition
95
    # to the analyses the analysis request contains.
96
    do_action_to_ancestors(analysis_request, "submit")
97
    do_action_to_descendants(analysis_request, "submit")
98
99
100
def after_verify(analysis_request):
101
    """Method triggered after a 'verify' transition for the Analysis Request
102
    passed in is performed. Promotes the 'verify' transition to ancestors,
103
    descendants and to the analyses that belong to the analysis request.
104
    """
105
    # Mark this analysis request as IReceived
106
    alsoProvides(analysis_request, IVerified)
107
108
    # This transition is not meant to be triggered manually by the user, rather
109
    # promoted by analyses. Hence, there is no need to cascade the transition
110
    # to the analyses the analysis request contains.
111
    do_action_to_ancestors(analysis_request, "verify")
112
    do_action_to_descendants(analysis_request, "verify")
113
114
115
def after_prepublish(analysis_request):
116
    """Method triggered after a 'prepublish' transition for the Analysis
117
    Request passed in is performed. Performs the 'publish' transition to the
118
    descendant partitions.
119
120
    Also see: https://github.com/senaite/senaite.core/pull/1428
121
    """
122
    do_action_to_descendants(analysis_request, "publish")
123
124
125
def after_publish(analysis_request):
126
    """Method triggered after an 'publish' transition for the Analysis Request
127
    passed in is performed. Performs the 'publish' transition Publishes the
128
    descendant partitions and all analyses associated to the analysis request
129
    as well.
130
    """
131
    do_action_to_descendants(analysis_request, "publish")
132
    do_action_to_analyses(analysis_request, "publish")
133
134
135
def after_reinstate(analysis_request):
136
    """Method triggered after a 'reinstate' transition for the Analysis Request
137
    passed in is performed. Sets its status to the last status before it was
138
    cancelled. Reinstates the descendant partitions and all the analyses
139
    associated to the analysis request as well.
140
    """
141
    do_action_to_descendants(analysis_request, "reinstate")
142
    do_action_to_analyses(analysis_request, "reinstate")
143
144
    # Force the transition to previous state before the request was cancelled
145
    skip = ["cancelled"]
146
    prev = api.get_previous_worfklow_status_of(analysis_request, skip=skip)
147
    changeWorkflowState(analysis_request, SAMPLE_WORKFLOW, prev,
148
                        action="reinstate")
149
    analysis_request.reindexObject()
150
151
152
def after_cancel(analysis_request):
153
    """Method triggered after a 'cancel' transition for the Analysis Request
154
    passed in is performed. Cascades this transition to its analyses and
155
    partitions.
156
    """
157
    do_action_to_descendants(analysis_request, "cancel")
158
    do_action_to_analyses(analysis_request, "cancel")
159
160
161
def after_receive(analysis_request):
162
    """Method triggered after "receive" transition for the Analysis Request
163
    passed in is performed
164
    """
165
    # Mark this analysis request as IReceived
166
    alsoProvides(analysis_request, IReceived)
167
168
    analysis_request.setDateReceived(DateTime())
169
    do_action_to_analyses(analysis_request, "initialize")
170
171
172
def after_sample(analysis_request):
173
    """Method triggered after "sample" transition for the Analysis Request
174
    passed in is performed
175
    """
176
    analysis_request.setDateSampled(DateTime())
177
178
179
def after_rollback_to_receive(analysis_request):
180
    """Function triggered after "rollback to receive" transition is performed
181
    """
182
    if IVerified.providedBy(analysis_request):
183
        noLongerProvides(analysis_request, IVerified)
184
185
186
def after_detach(analysis_request):
187
    """Function triggered after "detach" transition is performed
188
    """
189
    # Unbind the sample from its parent (the primary)
190
    parent = analysis_request.getParentAnalysisRequest()
191
    analysis_request.setParentAnalysisRequest(None)
192
193
    # Assign the primary from which the sample has been detached
194
    analysis_request.setDetachedFrom(parent)
195
196
    # This sample is no longer a partition
197
    noLongerProvides(analysis_request, IAnalysisRequestPartition)
198
199
    # And we mark the sample with IDetachedPartition
200
    alsoProvides(analysis_request, IDetachedPartition)
201
202
    # Reindex both the parent and the detached one
203
    analysis_request.reindexObject()
204
    parent.reindexObject()
205
206
    # And the analyses too. aranalysesfield relies on a search against the
207
    # catalog to return the analyses: calling `getAnalyses` to the parent
208
    # will return all them, so no need to do the same with the detached
209
    analyses = parent.getAnalyses(full_objects=True)
210
    map(lambda an: an.reindexObject(), analyses)
211
212
213
def after_dispatch(sample):
214
    """Event triggered after "dispatch" transition takes place for a given sample
215
    """
216
    primary = sample.getParentAnalysisRequest()
217
218
    def get_last_wf_comment(obj):
219
        entry = api.get_review_history(obj)[0]
220
        return entry.get("comments", "")
221
222
    def dispatch(obj, comment=""):
223
        wf = api.get_tool("portal_workflow")
224
        try:
225
            wf.doActionFor(obj, "dispatch", comment=comment)
226
            return True
227
        except WorkflowException:
228
            return False
229
230
    if not primary:
231
        # propagate to transitions
232
        partitions = sample.getDescendants(all_descendants=False)
233
        for partition in partitions:
234
            comment = get_last_wf_comment(sample)
235
            dispatch(partition, comment)
236
        return
237
238
    # Return when primary sample is already dispatched
239
    if api.get_workflow_status_of(primary) == "dispatched":
240
        return
241
242
    # Dipsatch primary sample when all partitions are dispatched
243
    parts = primary.getDescendants()
244
    # Partitions in some statuses won't be considered
245
    skip = ["dispatched", "cancelled", "retracted", "rejected"]
246
    parts = filter(lambda part: api.get_review_status(part) not in skip, parts)
247
    if len(parts) == 0:
248
        # There are no partitions left, transition the primary
249
        comment = get_last_wf_comment(sample)
250
        dispatch(primary, comment)
251
252
253
def after_restore(sample):
254
    """Event triggered after "restore" transition takes place for a sample
255
    """
256
257
    # Transition the sample to the state before it was stored
258
    previous_state = api.get_previous_worfklow_status_of(
259
        sample, skip=["dispatched"], default="sample_due")
260
261
    # Note: we pause the snapshots here because events are fired next
262
    pause_snapshots_for(sample)
263
    changeWorkflowState(sample, SAMPLE_WORKFLOW, previous_state)
264
    resume_snapshots_for(sample)
265
266
    # Reindex the sample
267
    sample.reindexObject()
268
269
    # If the sample is a partition, try to promote to the primary
270
    primary = sample.getParentAnalysisRequest()
271
    if not primary:
272
        # propagate to transitions
273
        partitions = sample.getDescendants(all_descendants=False)
274
        for partition in partitions:
275
            do_action_for(partition, "restore")
276
        return
277
278
    # Return when primary sample is not dispatched
279
    if api.get_workflow_status_of(primary) != "dispatched":
280
        return
281
282
    # Restore primary sample if all its partitions have been restored
283
    parts = primary.getDescendants()
284
    states = map(api.get_workflow_status_of, parts)
285
    if "dispatched" not in states:
286
        do_action_for(primary, "restore")
287