Skip to content

Commit ff3bfe9

Browse files
Abdu-moustafaelioschmutz
authored andcommitted
Add auto-close-tasks param to task transitions on dossier closure
1 parent c5f486c commit ff3bfe9

4 files changed

Lines changed: 108 additions & 5 deletions

File tree

changes/TI-2000-2.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Provide the auto_close_tasks parameter on the task transition action when closing a dossier. [amo]

opengever/api/tests/test_dossier_workflow.py

Lines changed: 82 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
from datetime import datetime
2+
from ftw.builder import Builder
3+
from ftw.builder import create
24
from ftw.testbrowser import browsing
35
from ftw.testing import freeze
46
from opengever.dossier.resolve import LockingResolveManager
57
from opengever.testing import IntegrationTestCase
68
from plone import api
9+
import json
710

811

912
class TestDossierWorkflowRESTAPITransitions(IntegrationTestCase):
@@ -21,12 +24,14 @@ def assert_state(self, expected_state, obj):
2124
self.assertEquals(expected_state,
2225
api.content.get_state(obj))
2326

24-
def api_transition(self, obj, transition, browser):
27+
def api_transition(self, obj, transition, browser, data=None):
2528
url = '/'.join((obj.absolute_url(), '@workflow', transition))
2629
browser.open(
2730
url,
28-
headers={'Accept': 'application/json'},
29-
method='POST')
31+
headers=self.api_headers,
32+
method='POST',
33+
data=data
34+
)
3035

3136
@browsing
3237
def test_resolve_via_restapi(self, browser):
@@ -199,3 +204,77 @@ def test_archive_offered_via_restapi_is_forbidden(self, browser):
199204
{u'message': u"Invalid transition 'dossier-transition-archive'.\nValid transitions are:\n",
200205
u'type': u'Bad Request'}},
201206
browser.json)
207+
208+
@browsing
209+
def test_resolve_dossier_auto_close_tasks(self, browser):
210+
211+
self.login(self.secretariat_user, browser)
212+
213+
open_task = create(
214+
Builder('task')
215+
.within(self.resolvable_subdossier)
216+
.in_state('task-state-open')
217+
.titled(u'Task 1')
218+
.having(
219+
issuer=self.secretariat_user.id,
220+
responsible=self.secretariat_user.id,
221+
responsible_client='fa',
222+
)
223+
)
224+
in_progress_task = create(
225+
Builder('task')
226+
.within(self.resolvable_subdossier)
227+
.in_state('task-state-in-progress')
228+
.titled(u'Task 2')
229+
.having(
230+
issuer=self.secretariat_user.id,
231+
responsible=self.secretariat_user.id,
232+
responsible_client='fa',
233+
task_type='approval',
234+
)
235+
)
236+
resolved_task = create(
237+
Builder('task')
238+
.within(self.resolvable_subdossier)
239+
.in_state('task-state-resolved')
240+
.titled(u'Task 3')
241+
.having(
242+
issuer=self.secretariat_user.id,
243+
responsible=self.secretariat_user.id,
244+
responsible_client='fa',
245+
)
246+
)
247+
with freeze(datetime(2018, 4, 30)):
248+
with browser.expect_http_error():
249+
self.api_transition(self.resolvable_subdossier, 'dossier-transition-resolve', browser)
250+
self.assertEqual(400, browser.status_code)
251+
self.assertDictEqual(
252+
{
253+
u'error': {u'message': u'', u'errors': [u'not all task are closed'], u'type': u'PreconditionsViolated'}
254+
},
255+
browser.json)
256+
257+
# dossier and tasks state did not change
258+
self.assert_state('dossier-state-active', self.resolvable_subdossier)
259+
260+
self.assert_state('task-state-open', open_task)
261+
self.assert_state('task-state-in-progress', in_progress_task)
262+
self.assert_state('task-state-resolved', resolved_task)
263+
264+
with freeze(datetime(2018, 4, 30)):
265+
266+
self.api_transition(
267+
self.resolvable_subdossier,
268+
'dossier-transition-resolve',
269+
browser,
270+
data=json.dumps({"auto_close_tasks": True})
271+
)
272+
273+
self.assertEqual(200, browser.status_code)
274+
275+
# dossier and tasks states were changed
276+
self.assert_state('dossier-state-resolved', self.resolvable_subdossier)
277+
278+
self.assert_state('task-state-cancelled', open_task)
279+
self.assert_state('task-state-tested-and-closed', in_progress_task)
280+
self.assert_state('task-state-tested-and-closed', resolved_task)

opengever/api/transition.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,14 +238,15 @@ def perform_custom_transition(self):
238238
# For now we also extract these, but we don't do anything with them
239239
# in the case of resolving a dossier.
240240
comment = data.get('comment', '')
241+
auto_close_tasks = data.get('auto_close_tasks', False)
241242
publication_dates = self.parse_publication_dates(data)
242243
args = [self.context], comment, publication_dates
243244

244245
if adapter and data:
245246
data = adapter.deserialize(data)
246247

247248
if self.transition == 'dossier-transition-resolve':
248-
self.resolve_dossier(*args, **data)
249+
self.resolve_dossier(*args, auto_close_tasks=auto_close_tasks, **data)
249250
elif self.transition == 'dossier-transition-activate':
250251
self.activate_dossier(*args)
251252
elif self.transition == 'dossier-transition-deactivate':

opengever/dossier/resolve.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,11 @@ def resolve(self, **transition_params):
154154
# by acquring a lock, resolving, and then releasing the lock
155155
try:
156156
resolve_lock.acquire(commit=True)
157+
auto_close_tasks = transition_params.get("auto_close_tasks", False)
158+
if auto_close_tasks:
159+
pending_tasks = self._get_pending_tasks()
160+
self._auto_close_tasks(pending_tasks)
161+
157162
result = self.execute_recursive_resolve(**transition_params)
158163

159164
# We need to commit here so that a possible ConflictError already
@@ -187,6 +192,23 @@ def resolve(self, **transition_params):
187192
# this 'finally' block.
188193
resolve_lock.release(commit=True)
189194

195+
def _auto_close_tasks(self, tasks):
196+
"""Auto-close all pending tasks."""
197+
for brain in tasks:
198+
task = brain.getObject()
199+
task.force_finish_task()
200+
201+
def _get_pending_tasks(self):
202+
"""Get all pending tasks in dossier."""
203+
catalog = api.portal.get_tool('portal_catalog')
204+
path = '/'.join(self.context.getPhysicalPath())
205+
return catalog.searchResults(
206+
path=path,
207+
object_provides=ITask.__identifier__,
208+
is_subtask=False,
209+
review_state=['task-state-open', 'task-state-in-progress', 'task-state-resolved']
210+
)
211+
190212
def execute_recursive_resolve(self, **transition_params):
191213
self.resolver.raise_on_failed_preconditions()
192214
if is_archive_form_needed(self.context):
@@ -301,7 +323,7 @@ def are_enddates_valid(self):
301323
self.enddates_valid = True
302324
return errors
303325

304-
def resolve(self, end_date=None, **kwargs):
326+
def resolve(self, end_date=None, auto_close_tasks=False, **kwargs):
305327
if not self.enddates_valid or not self.preconditions_fulfilled:
306328
raise TypeError
307329

0 commit comments

Comments
 (0)