workflows: add possibility to trigger global actions on finalized data (#33643)
This commit is contained in:
parent
cfb875a8a8
commit
234cd7999e
|
@ -3027,7 +3027,8 @@ def test_export_to_model_django_template(pub):
|
|||
new_content = zipfile.ZipFile(open(formdata.evolution[0].parts[2].filename)).read('content.xml')
|
||||
assert '>A <> name<' in new_content
|
||||
|
||||
def test_global_timeouts(pub):
|
||||
def test_global_timeouts(two_pubs):
|
||||
pub = two_pubs
|
||||
FormDef.wipe()
|
||||
Workflow.wipe()
|
||||
|
||||
|
@ -3133,8 +3134,43 @@ def test_global_timeouts(pub):
|
|||
assert formdef.data_class().get(formdata1.id).get_criticality_level_object().name == 'yellow'
|
||||
formdata1.store()
|
||||
|
||||
# check trigger is not run on finalized formdata
|
||||
formdata1.jump_status('finished')
|
||||
formdata1.evolution[-1].time = time.localtime(time.time()-4*86400)
|
||||
formdata1.store()
|
||||
trigger.anchor = 'creation'
|
||||
workflow.store()
|
||||
pub.apply_global_action_timeouts()
|
||||
assert formdef.data_class().get(formdata1.id).get_criticality_level_object().name == 'green'
|
||||
formdata1.store()
|
||||
|
||||
# check trigger is run on finalized formdata when anchor status is an
|
||||
# endpoint
|
||||
formdata1.jump_status('finished')
|
||||
formdata1.evolution[-1].last_jump_datetime = None
|
||||
formdata1.evolution[-1].time = time.localtime(time.time()-4*86400)
|
||||
formdata1.store()
|
||||
trigger.anchor = 'latest-arrival'
|
||||
trigger.anchor_status_latest = 'wf-finished'
|
||||
workflow.store()
|
||||
pub.apply_global_action_timeouts()
|
||||
assert formdef.data_class().get(formdata1.id).get_criticality_level_object().name == 'yellow'
|
||||
formdata1.store()
|
||||
|
||||
# check "finalized" anchor
|
||||
trigger.anchor = 'finalized'
|
||||
workflow.store()
|
||||
pub.apply_global_action_timeouts()
|
||||
assert formdef.data_class().get(formdata1.id).get_criticality_level_object().name == 'yellow'
|
||||
formdata1.store()
|
||||
|
||||
# use python expression as anchor
|
||||
# timestamp
|
||||
formdata1.jump_status('new')
|
||||
formdata1.evolution[-1].time = time.localtime(time.time()-4*86400)
|
||||
formdata1.evolution[-1].last_jump_datetime = None
|
||||
formdata1.store()
|
||||
|
||||
trigger.anchor = 'python'
|
||||
trigger.anchor_expression = repr(time.time())
|
||||
workflow.store()
|
||||
|
|
|
@ -31,7 +31,7 @@ from quixote import get_request, get_response, redirect
|
|||
|
||||
from qommon import _
|
||||
from qommon.misc import C_, get_as_datetime, file_digest, get_foreground_colour
|
||||
from qommon.storage import StorableObject, atomic_write
|
||||
from qommon.storage import StorableObject, atomic_write, NotEqual, Contains, Null
|
||||
from qommon.form import *
|
||||
from qommon.humantime import seconds2humanduration
|
||||
from qommon import emails, get_cfg, get_logger
|
||||
|
@ -384,6 +384,8 @@ class Workflow(StorableObject):
|
|||
return status
|
||||
|
||||
def get_status(self, id):
|
||||
if id.startswith('wf-'):
|
||||
id = id[3:]
|
||||
for status in self.possible_status:
|
||||
if status.id == id:
|
||||
return status
|
||||
|
@ -1010,6 +1012,7 @@ class WorkflowGlobalActionTimeoutTrigger(WorkflowGlobalActionTrigger):
|
|||
('creation', _('Creation')),
|
||||
('1st-arrival', _('First arrival in status')),
|
||||
('latest-arrival', _('Latest arrival in status')),
|
||||
('finalized', _('Arrival in final status')),
|
||||
('python', _('Python expression')),
|
||||
])
|
||||
|
||||
|
@ -1033,6 +1036,7 @@ class WorkflowGlobalActionTimeoutTrigger(WorkflowGlobalActionTrigger):
|
|||
|
||||
form.add(StringWidget, 'anchor_expression', title=_('Expression'), size=80,
|
||||
value=self.anchor_expression,
|
||||
hint=_('This will only apply to open forms.'),
|
||||
attrs={'data-dynamic-display-child-of': 'anchor',
|
||||
'data-dynamic-display-value': _('Python expression')})
|
||||
possible_status = [(None, _('Current Status'), None)]
|
||||
|
@ -1062,7 +1066,13 @@ class WorkflowGlobalActionTimeoutTrigger(WorkflowGlobalActionTrigger):
|
|||
|
||||
return form
|
||||
|
||||
def must_trigger(self, formdata):
|
||||
def must_trigger(self, formdata, endpoint_status_ids):
|
||||
if formdata.status in endpoint_status_ids:
|
||||
if not ((self.anchor == '1st-arrival' and self.anchor_status_first in endpoint_status_ids) or (
|
||||
self.anchor == 'latest-arrival' and self.anchor_status_latest in endpoint_status_ids) or (
|
||||
self.anchor == 'finalized')):
|
||||
# don't trigger on finalized formdata (unless explicit anchor point)
|
||||
return False
|
||||
anchor_date = None
|
||||
if self.anchor == 'creation':
|
||||
anchor_date = formdata.receipt_time
|
||||
|
@ -1078,6 +1088,15 @@ class WorkflowGlobalActionTimeoutTrigger(WorkflowGlobalActionTrigger):
|
|||
if evolution.status == anchor_status:
|
||||
anchor_date = evolution.last_jump_datetime or evolution.time
|
||||
break
|
||||
elif self.anchor == 'finalized':
|
||||
if formdata.status in endpoint_status_ids:
|
||||
for evolution in reversed(formdata.evolution):
|
||||
if not evolution.status:
|
||||
continue
|
||||
if evolution.status in endpoint_status_ids:
|
||||
anchor_date = evolution.time
|
||||
else:
|
||||
break
|
||||
elif self.anchor == 'python':
|
||||
variables = get_publisher().substitutions.get_context_variables()
|
||||
try:
|
||||
|
@ -1130,16 +1149,40 @@ class WorkflowGlobalActionTimeoutTrigger(WorkflowGlobalActionTrigger):
|
|||
if not triggers:
|
||||
return
|
||||
|
||||
formdefs = [x for x in FormDef.select() if x.workflow_id == workflow.id]
|
||||
not_endpoint_status = workflow.get_not_endpoint_status()
|
||||
not_endpoint_status_ids = ['wf-%s' % x.id for x in not_endpoint_status]
|
||||
endpoint_status = workflow.get_endpoint_status()
|
||||
endpoint_status_ids = ['wf-%s' % x.id for x in endpoint_status]
|
||||
|
||||
# check if triggers are defined relative to terminal status
|
||||
run_on_finalized = False
|
||||
for action, trigger in triggers:
|
||||
if trigger.anchor == 'finalized':
|
||||
run_on_finalized = True
|
||||
elif (trigger.anchor == 'creation' and
|
||||
workflow.possible_status and
|
||||
workflow.possible_status[0] in endpoint_status):
|
||||
run_on_finalized = True
|
||||
elif (trigger.anchor == '1st-arrival' and
|
||||
trigger.anchor_status_first and
|
||||
workflow.get_status(trigger.anchor_status_first) in endpoint_status):
|
||||
run_on_finalized = True
|
||||
elif (trigger.anchor == 'latest-arrival' and
|
||||
trigger.anchor_status_latest and
|
||||
workflow.get_status(trigger.anchor_status_latest) in endpoint_status):
|
||||
run_on_finalized = True
|
||||
|
||||
criterias = [NotEqual('status', 'draft'), Null('anonymised')]
|
||||
if not run_on_finalized:
|
||||
# limit to formdata that are not finalized
|
||||
criterias.append(Contains('status', not_endpoint_status_ids))
|
||||
|
||||
formdefs = [x for x in FormDef.select() if x.workflow_id == workflow.id]
|
||||
|
||||
for formdef in formdefs:
|
||||
open_formdata_ids = []
|
||||
data_class = formdef.data_class()
|
||||
for status in not_endpoint_status_ids:
|
||||
open_formdata_ids.extend(data_class.get_ids_with_indexed_value('status', status))
|
||||
for formdata in data_class.get_ids(open_formdata_ids, ignore_errors=True):
|
||||
for formdata in data_class.select(criterias, iterator=True):
|
||||
get_publisher().substitutions.reset()
|
||||
get_publisher().substitutions.feed(get_publisher())
|
||||
get_publisher().substitutions.feed(formdef)
|
||||
|
@ -1154,7 +1197,7 @@ class WorkflowGlobalActionTimeoutTrigger(WorkflowGlobalActionTrigger):
|
|||
for action, trigger in triggers:
|
||||
if trigger.id in seen_triggers:
|
||||
continue # already triggered
|
||||
if trigger.must_trigger(formdata):
|
||||
if trigger.must_trigger(formdata, endpoint_status_ids):
|
||||
if not formdata.evolution:
|
||||
continue
|
||||
formdata.evolution[-1].add_part(
|
||||
|
|
Loading…
Reference in New Issue