Allow cancelling of task attributions #22006
This commit is contained in:
parent
76eff071dd
commit
7a7cdae5e6
|
@ -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())
|
|
@ -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"
|
||||
|
|
|
@ -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 ""
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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 ""
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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')
|
||||
|
|
Reference in New Issue