Allow cancelling of task attributions #22006

This commit is contained in:
Nicolas Demonte 2019-01-18 16:14:49 +01:00
parent 76eff071dd
commit 7a7cdae5e6
8 changed files with 158 additions and 0 deletions

View File

@ -0,0 +1,103 @@
import z3c.form
from Products.CMFCore.utils import getToolByName
from attribute_task import find_nontask
from collective.task import _
from pfwbged.policy.subscribers.document import email_notification_of_canceled_subtask
from plone import api
from plone.supermodel import model
from z3c.form import button
from z3c.form.browser.checkbox import CheckBoxFieldWidget
from z3c.form.field import Fields
from z3c.form.interfaces import HIDDEN_MODE
from zope import schema
from zope.i18nmessageid import MessageFactory
from zope.interface import directlyProvides
from zope.interface import implements
from zope.schema.interfaces import IContextSourceBinder
from zope.schema.vocabulary import SimpleVocabulary
PMF = MessageFactory('plone')
def get_principal(principal_id):
principal = api.user.get(principal_id)
if not principal:
principal = api.group.get(principal_id)
return principal
def responsibles_vocabulary(context):
acl_users = getToolByName(context, 'acl_users')
terms = []
subtasks = context.listFolderContents()
responsible_ids = [subtask.responsible[0] for subtask in subtasks]
for responsible_id in responsible_ids:
group = acl_users.getGroupById(responsible_id)
user = acl_users.getUserById(responsible_id)
if group:
responsible_name = group.getProperty('title')
elif user:
responsible_name = user.getProperty('fullname')
else:
responsible_name = responsible_id
terms.append(SimpleVocabulary.createTerm(responsible_id, str(responsible_id), responsible_name))
return SimpleVocabulary(terms)
directlyProvides(responsibles_vocabulary, IContextSourceBinder)
class ITaskRecipients(model.Schema):
""""""
responsible = schema.List(
value_type=schema.Choice(
source=responsibles_vocabulary,
),
)
class CancelTaskAttribution(z3c.form.form.Form):
"""Cancel (delete) one or more subtasks
"""
implements(z3c.form.interfaces.IFieldsForm)
fields = Fields(ITaskRecipients)
fields["responsible"].widgetFactory = CheckBoxFieldWidget
label = _(u'Cancel attribution(s)')
description = _(u'Please select attributions to cancel. The responsibles will be notified by mail.')
ignoreContext = True
@button.buttonAndHandler(_('Apply'), name='apply')
def handleApply(self, action):
data, errors = self.extractData()
responsibles = data.get('responsible')
if not responsibles:
return
for subtask in self.context.listFolderContents():
responsible = subtask.responsible[0]
if responsible in responsibles:
# remove Editor on document
document = find_nontask(subtask)
local_roles = document.get_local_roles_for_userid(responsible)
leftover_roles = set(local_roles).difference(['Editor'])
if leftover_roles:
# set leftover roles
document.manage_setLocalRoles(responsible, leftover_roles)
else:
# delete local roles for user
document.manage_delLocalRoles([responsible])
# remove relevant subtask
self.context.manage_delObjects(subtask.id)
# notify responsible by mail it's removed
email_notification_of_canceled_subtask(subtask)
self.request.response.redirect(find_nontask(self.context).absolute_url())

View File

@ -19,6 +19,14 @@
permission="zope2.View"
/>
<browser:page
name="cancel_attribution"
for="collective.task.content.task.ITask"
class=".cancel_attribution.CancelTaskAttribution"
permission="zope2.View"
/>
<browser:page
for="..interfaces.IBaseTask"
name="view"

View File

@ -30,6 +30,14 @@ msgstr ""
msgid "Addressee"
msgstr ""
#: ../browser/cancel_attribution.py:76
msgid "Apply"
msgstr ""
#: ../browser/cancel_attribution.py:71
msgid "Cancel attribution(s)"
msgstr ""
#: ../browser/tasks-portlet.pt:21
#: ../interfaces.py:26
msgid "Deadline"
@ -55,6 +63,10 @@ msgstr ""
msgid "Opinion"
msgstr ""
#: ../browser/cancel_attribution.py:72
msgid "Please select attributions to cancel. The responsibles will be notified by mail."
msgstr ""
#: ../behaviors.py:14
msgid "Target"
msgstr ""

View File

@ -31,6 +31,14 @@ msgstr "Destinataire"
msgid "Addressees"
msgstr "Destinataires"
#: ../browser/cancel_attribution.py:76
msgid "Apply"
msgstr "Appliquer"
#: ../browser/cancel_attribution.py:71
msgid "Cancel attribution(s)"
msgstr "Annuler les attributions"
#: ../browser/tasks-portlet.pt:21
#: ../interfaces.py:74
msgid "Deadline"
@ -58,6 +66,10 @@ msgstr "Note"
msgid "Opinion"
msgstr "Demande d'avis"
#: ../browser/cancel_attribution.py:72
msgid "Please select attributions to cancel. The responsibles will be notified by mail."
msgstr "Veuillez sélectionner les attributions à annuler. Les destinaires en seront informés par email."
#: ../behaviors.py:14
msgid "Target"
msgstr "Cible"

View File

@ -42,6 +42,10 @@ msgstr "Attribuer la tâche à"
msgid "Attributed"
msgstr "Attribué"
#: ../profiles/default/workflows/task_workflow/definition.xml
msgid "Cancel attribution(s)"
msgstr "Annuler les attributions"
#: ../profiles/default/registry.xml
msgid "Dates"
msgstr "Dates"

View File

@ -45,6 +45,10 @@ msgstr ""
msgid "Attributed"
msgstr ""
#: ../profiles/default/workflows/task_workflow/definition.xml
msgid "Cancel attribution(s)"
msgstr ""
#: ../profiles/default/registry.xml
msgid "Deadline"
msgstr ""

View File

@ -23,6 +23,7 @@
<state state_id="attributed" title="Attributed" i18n:attributes="title">
<exit-transition transition_id="subtask-abandoned"/>
<exit-transition transition_id="subtask-done"/>
<exit-transition transition_id="cancel-attribution"/>
<permission-map name="View" acquired="False">
<permission-role>Manager</permission-role>
<permission-role>Reviewer</permission-role>
@ -149,6 +150,12 @@
<guard-role>Editor</guard-role>
</guard>
</transition>
<transition transition_id="cancel-attribution" title="Cancel attribution(s)" new_state="todo" trigger="USER" before_script="" after_script="">
<action url="%(content_url)s/@@cancel_attribution?workflow_action=cancel-attribution" category="workflow" icon="" i18n:translate="">Cancel attribution(s)</action>
<guard>
<guard-role>Editor</guard-role>
</guard>
</transition>
<worklist worklist_id="reviewer_queue" title="">
<description>Reviewer tasks</description>
<action url="%(portal_url)s/search?review_state=pending" category="global" icon="">Pending (%(count)d)</action>

View File

@ -47,6 +47,14 @@ def task_changed_state(context, event):
pass
parent.reindexObject(idxs=['review_state'])
elif event.new_state.id == 'abandoned':
# don't transition parent task if other subtasks are still live
sibling_subtasks = set(parent.listFolderContents()).difference([context])
if any([
task for task in sibling_subtasks
if api.content.get_state(task) not in ('abandoned', 'done')
]):
return
with api.env.adopt_user('admin'):
try:
api.content.transition(obj=parent, transition='subtask-abandoned')