Allow return to initial state of a validation request #22008

This commit is contained in:
Nicolas Demonte 2019-01-31 11:58:25 +01:00
parent abd293f19c
commit 6f94857196
8 changed files with 182 additions and 2 deletions

View File

@ -107,6 +107,40 @@ class CanValidateOrRefuse(grok.View):
return False
class CanCancelRefusal(grok.View):
grok.name("can_cancel_refusal")
grok.context(IDmsFile)
grok.require('zope2.View')
def render(self):
workflow = api.portal.get_tool('portal_workflow')
with api.env.adopt_user('admin'):
review_history = workflow.getInfoFor(self.context, 'review_history')
if not review_history:
return False
last_transition = review_history[-1]
return last_transition.get('action') == 'refuse' and \
last_transition.get('actor') == api.user.get_current().id
class CanCancelValidation(grok.View):
grok.name("can_cancel_validation")
grok.context(IDmsFile)
grok.require('zope2.View')
def render(self):
workflow = api.portal.get_tool('portal_workflow')
with api.env.adopt_user('admin'):
review_history = workflow.getInfoFor(self.context, 'review_history')
if not review_history:
return False
last_transition = review_history[-1]
return last_transition.get('action') == 'validate' and \
last_transition.get('actor') == api.user.get_current().id
class CanBeTrashedDmsFile(grok.View):
""""""
grok.name('can_be_trashed')

View File

@ -27,7 +27,15 @@ msgstr "L'élément ${title} a été supprimé."
msgid "${title} is locked and cannot be deleted."
msgstr "L'élément ${title} est verrouillé et ne peut être supprimé."
#: ../subscribers/document.py:430
#: ../subscribers/document.py:417
msgid "A previously refused version has returned to waiting validation"
msgstr "Un document précédemment refusé est revenu en attente de validation"
#: ../subscribers/document.py:415
msgid "A previously validated version has returned to waiting validation"
msgstr "Un document précédemment validé est revenu en attente de validation"
#: ../subscribers/document.py:495
msgid "A validation request has been refused"
msgstr "Une demande de validation a été refusée."
@ -86,10 +94,18 @@ msgstr "Visible par"
msgid "Cancel"
msgstr "Annuler"
#: ../menu.py:58
msgid "Cancel refusal of ${version}"
msgstr "Annuler le refus de la version ${version}"
#: ../menu.py:56
msgid "Cancel validate and finish ${version}"
msgstr "Annuler la validation et finalisation de la version ${version}"
#: ../menu.py:57
msgid "Cancel validation of ${version}"
msgstr "Annuler la validation de la version ${version}"
#: ../browser/refuse.py:33
#: ../browser/send_by_email.py:35
#: ../browser/to_process.py:25

View File

@ -61,6 +61,14 @@ msgstr "Retour en à traiter"
msgid "Back to writing"
msgstr "Retour à la rédaction"
#: ../profiles/default/workflows/versionnote_workflow/definition.xml
msgid "Cancel refusal"
msgstr "Annuler le refus"
#: ../profiles/default/workflows/versionnote_workflow/definition.xml
msgid "Cancel validation"
msgstr "Annuler la validation"
#: ../profiles/default/workflows/incomingapfmail_workflow/definition.xml
#: ../profiles/default/workflows/incomingmail_workflow/definition.xml
#: ../profiles/default/workflows/pfwbgeddocument_workflow/definition.xml

View File

@ -29,7 +29,15 @@ msgstr ""
msgid "${title} is locked and cannot be deleted."
msgstr ""
#: ../subscribers/document.py:430
#: ../subscribers/document.py:417
msgid "A previously refused version has returned to waiting validation"
msgstr ""
#: ../subscribers/document.py:415
msgid "A previously validated version has returned to waiting validation"
msgstr ""
#: ../subscribers/document.py:495
msgid "A validation request has been refused"
msgstr ""
@ -88,10 +96,18 @@ msgstr ""
msgid "Cancel"
msgstr ""
#: ../menu.py:58
msgid "Cancel refusal of ${version}"
msgstr ""
#: ../menu.py:56
msgid "Cancel validate and finish ${version}"
msgstr ""
#: ../menu.py:57
msgid "Cancel validation of ${version}"
msgstr ""
#: ../browser/refuse.py:33
#: ../browser/send_by_email.py:35
#: ../browser/to_process.py:25

View File

@ -62,6 +62,14 @@ msgstr ""
msgid "Back to writing"
msgstr ""
#: ../profiles/default/workflows/versionnote_workflow/definition.xml
msgid "Cancel refusal"
msgstr ""
#: ../profiles/default/workflows/versionnote_workflow/definition.xml
msgid "Cancel validation"
msgstr ""
#: ../profiles/default/workflows/incomingapfmail_workflow/definition.xml
#: ../profiles/default/workflows/incomingmail_workflow/definition.xml
#: ../profiles/default/workflows/pfwbgeddocument_workflow/definition.xml

View File

@ -54,6 +54,8 @@ dmsfile_wfactions_mapping = {'ask_opinion': _(u"Ask opinion about version ${vers
'send_with_docbow': _(u"Send version ${version} with PES"),
'restore_from_trash': _(u"Restore version ${version}"),
'back_to_draft': _(u"Cancel validate and finish ${version}"),
'cancel-validation': _(u"Cancel validation of ${version}"),
'cancel-refusal': _(u"Cancel refusal of ${version}"),
}
@ -232,6 +234,21 @@ class CustomMenu(menu.WorkflowMenu):
title = IGNORE_(title, mapping={'version': version})
cssClass += ' version-action version-id-%s' % context.id
cssClass += ' version-action-%s' % action['id']
# limit cancelling actions to a version's rightful owner
# guards are bypassed for Managers, so call them at least once
action_guards = {
'cancel-refusal': 'can_cancel_refusal',
'cancel-validation': 'can_cancel_validation',
}
if action['id'] in action_guards:
view = queryMultiAdapter(
(context, request),
name=action_guards.get(action['id'])
)
if view and not view.render():
action['allowed'] = False
else:
title = action['title']

View File

@ -16,6 +16,7 @@
<exit-transition transition_id="submit"/>
<exit-transition transition_id="ask_opinion"/>
<exit-transition transition_id="obsolete"/>
<exit-transition transition_id="cancel-refusal"/>
<permission-map name="View" acquired="False">
<permission-role>Editor</permission-role>
<permission-role>Manager</permission-role>
@ -109,6 +110,7 @@
<exit-transition transition_id="finish"/>
<exit-transition transition_id="obsolete"/>
<exit-transition transition_id="submit"/>
<exit-transition transition_id="cancel-validation"/>
<permission-map name="View" acquired="False">
<permission-role>Editor</permission-role>
<permission-role>Manager</permission-role>
@ -203,6 +205,18 @@
<guard-expression>here/@@can_validate_or_refuse</guard-expression>
</guard>
</transition>
<transition transition_id="cancel-validation" title="Cancel validation" new_state="pending" trigger="USER" before_script="" after_script="" i18n:attributes="title">
<action url="" category="workflow" icon="" i18n:translate="">Cancel validation</action>
<guard>
<guard-expression>here/@@can_cancel_validation</guard-expression>
</guard>
</transition>
<transition transition_id="cancel-refusal" title="Cancel refusal" new_state="pending" trigger="USER" before_script="" after_script="" i18n:attributes="title">
<action url="" category="workflow" icon="" i18n:translate="">Cancel validation</action>
<guard>
<guard-expression>here/@@can_cancel_refusal</guard-expression>
</guard>
</transition>
<variable variable_id="action" for_catalog="False" for_status="True" update_always="True">
<description>Previous transition</description>
<default>

View File

@ -100,6 +100,16 @@ def change_validation_state(context, event):
if api.content.get_state(validation) == 'todo':
api.content.transition(validation, 'validate')
validation.reindexObject(idxs=['review_state'])
elif event.transition.id == 'cancel-validation':
for ref in catalog.findRelations(query):
validation = ref.from_object
api.content.transition(validation, 'cancel-validation')
validation.reindexObject(idxs=['review_state'])
elif event.transition.id == 'cancel-refusal':
for ref in catalog.findRelations(query):
validation = ref.from_object
api.content.transition(validation, 'cancel-refusal')
validation.reindexObject(idxs=['review_state'])
@grok.subscribe(IDmsFile, IObjectWillBeRemovedEvent)
@ -397,6 +407,63 @@ def email_notification_of_tasks(context, event):
job = async.queueJob(email_notification_of_tasks_sync, **kwargs)
@grok.subscribe(IValidation, IAfterTransitionEvent)
def email_notification_of_validation_reversal(context, event):
"""Notify a validation requester when their previously validated
(or refused) request has returned to pending state"""
if not event.transition:
return
elif event.transition.id == 'cancel-validation':
comment = translate(_('A previously validated version has returned to waiting validation'), context=context.REQUEST)
elif event.transition.id == 'cancel-refusal':
comment = translate(_('A previously refused version has returned to waiting validation'), context=context.REQUEST)
else:
return
# go up in the acquisition chain to find the document
document = None
for obj in aq_chain(context):
obj = aq_parent(obj)
if IDmsDocument.providedBy(obj):
document = obj
break
if not document:
return
email_enquirer = None
for enquirer in (context.enquirer or []):
member = context.portal_membership.getMemberById(enquirer)
if member:
email_enquirer = member.getProperty('email', None)
if email_enquirer:
break
if not email_enquirer:
return
email_from = api.user.get_current().email or api.portal.get().getProperty('email_from_address') or 'admin@localhost'
subject = '%s - %s' % (context.title, document.title)
body = comment + \
'\n\n' + \
translate(_('Title: %s'), context=context.REQUEST) % context.title + \
'\n\n' + \
translate(_('Document: %s'), context=context.REQUEST) % document.title + \
'\n\n' + \
translate(_('Document Address: %s'), context=context.REQUEST) % document.absolute_url()
body += '\n\n\n-- \n' + translate(_('Sent by GED'))
body = body.encode('utf-8')
try:
context.MailHost.send(body, email_enquirer, email_from, subject, charset='utf-8')
except Exception as e:
# do not abort transaction in case of email error
log = logging.getLogger('pfwbged.policy')
log.exception(e)
@grok.subscribe(IValidation, IAfterTransitionEvent)
def email_notification_of_refused_task(context, event):
if event.new_state.id != 'refused':