Treasury endorsement/return of budgets
This commit is contained in:
parent
a0d6164d95
commit
69a7c052c1
@ -59,17 +59,13 @@
|
||||
{% if budgets_open %}
|
||||
<h2>Open budgets</h2>
|
||||
|
||||
{% for budget in budgets_open %}
|
||||
{{ budget.name }}
|
||||
{% endfor %}
|
||||
{{ listbudgets(budgets_open) }}
|
||||
{% endif %}
|
||||
|
||||
{% if budgets_closed %}
|
||||
<h2>Closed budgets</h2>
|
||||
|
||||
{% for budget in budgets_closed %}
|
||||
{{ budget.name }}
|
||||
{% endfor %}
|
||||
{{ listbudgets(budgets_closed) }}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
|
@ -29,9 +29,24 @@
|
||||
|
||||
{% if revision.state == import('sstreasury.models').BudgetState.DRAFT.value or revision.state == import('sstreasury.models').BudgetState.RESUBMIT.value %}
|
||||
<button class="ui mini labeled primary icon button" type="submit" name="action" value="Submit" style="margin-left: 1em;" onclick="return confirm('Are you sure you want to submit this budget for Treasury approval? You will not be able to make any additional changes without withdrawing the budget.');"><i class="paper plane icon"></i> Submit</button>
|
||||
{% elif revision.state == import('sstreasury.models').BudgetState.AWAIT_REVIEW.value and request.user.groups.filter(name='Treasury').exists() %}
|
||||
<button class="ui mini labeled positive icon button" type="submit" name="action" value="Endorse" style="margin-left: 1em;" onclick="return confirm('Are you sure you want to give this budget Treasury endorsement?');"><i class="check icon"></i> Endorse</button>
|
||||
|
||||
<button class="ui mini labeled basic negative icon button" type="submit" name="action" value="Return" onclick="return confirm('Are you sure you want to refuse this budget Treasury endorsement and return it for re-drafting?');"><i class="undo icon"></i> Return for re-drafting</button>
|
||||
{% elif revision.state == import('sstreasury.models').BudgetState.ENDORSED.value and request.user.groups.filter(name='Secretary').exists() %}
|
||||
{# TODO #}
|
||||
{% elif revision.state == import('sstreasury.models').BudgetState.APPROVED.value %}
|
||||
{# Blank #}
|
||||
{% else %}
|
||||
<button class="ui mini labeled basic negative icon button" type="submit" name="action" value="Withdraw" style="margin-left: 1em;" onclick="return confirm('Are you sure you want to withdraw this budget from being considered for approval? The budget will be reverted to a draft.');"><i class="undo icon"></i> Withdraw</button>
|
||||
{% endif %}
|
||||
|
||||
{% if revision.state == import('sstreasury.models').BudgetState.DRAFT.value or revision.state == import('sstreasury.models').BudgetState.RESUBMIT.value or (revision.state != import('sstreasury.models').BudgetState.APPROVED.value and (request.user.groups.filter(name='Treasury').exists() or request.user.groups.filter(name='Secretary').exists())) %}
|
||||
<a class="ui mini labeled right floated icon button" href="{{ url('budget_edit', kwargs={'id': revision.budget.id}) }}"><i class="edit icon"></i> Edit</a>
|
||||
{% elif revision.state != import('sstreasury.models').BudgetState.APPROVED.value %}
|
||||
<button class="ui mini labeled basic red icon button" type="submit" name="action" value="Withdraw" style="margin-left: 1em;" onclick="return confirm('Are you sure you want to withdraw this budget from being considered for approval? The budget will be reverted to a draft.');"><i class="undo icon"></i> Withdraw</button>
|
||||
{% else %}
|
||||
<div class="ui message">
|
||||
<p>This budget has been submitted and is now awaiting approval. If you wish to edit this budget, you must first withdraw it. This will revert the budget to a draft.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<input type="hidden" name="csrfmiddlewaretoken" value="{{ csrf_token }}">
|
||||
@ -126,6 +141,12 @@
|
||||
</div>
|
||||
<input type="hidden" name="csrfmiddlewaretoken" value="{{ csrf_token }}">
|
||||
<input class="ui primary button" type="submit" name="action" value="Comment">
|
||||
|
||||
{% if revision.state == import('sstreasury.models').BudgetState.AWAIT_REVIEW.value and request.user.groups.filter(name='Treasury').exists() %}
|
||||
<button class="ui labeled positive icon button" type="submit" name="action" value="Comment,Endorse" onclick="return confirm('Are you sure you want to give this budget Treasury endorsement?');"><i class="check icon"></i> Comment and endorse</button>
|
||||
|
||||
<button class="ui labeled basic negative icon button" type="submit" name="action" value="Comment,Return" onclick="return confirm('Are you sure you want to refuse this budget Treasury endorsement and return it for re-drafting?');"><i class="undo icon"></i> Comment and return for re-drafting</button>
|
||||
{% endif %}
|
||||
</form>
|
||||
|
||||
<div class="ui feed">
|
||||
|
3
sstreasury/jinja2/sstreasury/email/endorsed.md
Normal file
3
sstreasury/jinja2/sstreasury/email/endorsed.md
Normal file
@ -0,0 +1,3 @@
|
||||
Your budget titled *{{ revision.name }}* has been reviewed and endorsed by Treasury, and referred to the committee. The committee will determine whether or not to approve the budget at its next meeting.
|
||||
|
||||
{{ baseurl }}{{ url('budget_view', kwargs={'id': revision.budget.id}) }}
|
3
sstreasury/jinja2/sstreasury/email/returned.md
Normal file
3
sstreasury/jinja2/sstreasury/email/returned.md
Normal file
@ -0,0 +1,3 @@
|
||||
Your budget titled *{{ revision.name }}* has been reviewed by Treasury and returned to you for re-drafting. You should make any requested changes and resubmit the budget.
|
||||
|
||||
{{ baseurl }}{{ url('budget_view', kwargs={'id': revision.budget.id}) }}
|
@ -28,6 +28,7 @@ from django.views import generic
|
||||
from . import models
|
||||
from ssmain.email import Emailer
|
||||
|
||||
import functools
|
||||
import itertools
|
||||
import json
|
||||
|
||||
@ -44,12 +45,24 @@ def budget_list(request):
|
||||
for budget in models.Budget.objects.all():
|
||||
revision = budget.budgetrevision_set.reverse()[0]
|
||||
state = models.BudgetState(revision.state)
|
||||
if state in [models.BudgetState.DRAFT, models.BudgetState.RESUBMIT]:
|
||||
budgets_action.append(revision)
|
||||
elif state in [models.BudgetState.AWAIT_REVIEW, models.BudgetState.ENDORSED]:
|
||||
budgets_open.append(revision)
|
||||
|
||||
if request.user.groups.filter(name='Treasury').exists():
|
||||
if state == models.BudgetState.AWAIT_REVIEW:
|
||||
budgets_action.append(revision)
|
||||
elif state != models.BudgetState.APPROVED:
|
||||
budgets_open.append(revision)
|
||||
else:
|
||||
budgets_closed.append(revision)
|
||||
else:
|
||||
budgets_closed.append(revision)
|
||||
if request.user not in revision.contributors.all():
|
||||
continue
|
||||
|
||||
if state in [models.BudgetState.DRAFT, models.BudgetState.RESUBMIT]:
|
||||
budgets_action.append(revision)
|
||||
elif state in [models.BudgetState.AWAIT_REVIEW, models.BudgetState.ENDORSED]:
|
||||
budgets_open.append(revision)
|
||||
else:
|
||||
budgets_closed.append(revision)
|
||||
|
||||
return render(request, 'sstreasury/budget_list.html', {
|
||||
'budgets_action': budgets_action,
|
||||
@ -131,15 +144,46 @@ def budget_new(request):
|
||||
'contributors': request.user.email
|
||||
})
|
||||
|
||||
@login_required
|
||||
def budget_edit(request, id):
|
||||
if request.method == 'POST':
|
||||
def uses_budget(viewfunc):
|
||||
@functools.wraps(viewfunc)
|
||||
def func(request, id):
|
||||
budget = models.Budget.objects.get(id=id)
|
||||
revision = budget.budgetrevision_set.reverse()[0]
|
||||
|
||||
return viewfunc(request, budget, revision)
|
||||
return func
|
||||
|
||||
def budget_viewable(viewfunc):
|
||||
@functools.wraps(viewfunc)
|
||||
def func(request, budget, revision):
|
||||
if request.user not in revision.contributors.all():
|
||||
if not request.user.groups.filter(name='Treasury').exists():
|
||||
raise PermissionDenied
|
||||
|
||||
return viewfunc(request, budget, revision)
|
||||
return func
|
||||
|
||||
def budget_editable(viewfunc):
|
||||
@functools.wraps(viewfunc)
|
||||
def func(request, budget, revision):
|
||||
if revision.state == models.BudgetState.APPROVED.value:
|
||||
raise PermissionDenied
|
||||
|
||||
if revision.state != models.BudgetState.DRAFT.value and revision.state != models.BudgetState.RESUBMIT.value:
|
||||
if not request.user.groups.filter(name='Treasury').exists():
|
||||
raise PermissionDenied
|
||||
|
||||
if request.user not in revision.contributors.all():
|
||||
if not request.user.groups.filter(name='Treasury').exists():
|
||||
raise PermissionDenied
|
||||
|
||||
return viewfunc(request, budget, revision)
|
||||
return func
|
||||
|
||||
@login_required
|
||||
@uses_budget
|
||||
@budget_editable
|
||||
def budget_edit(request, budget, revision):
|
||||
if request.method == 'POST':
|
||||
if request.POST['submit'] == 'Delete':
|
||||
budget.delete()
|
||||
return redirect(reverse('budget_list'))
|
||||
@ -156,23 +200,18 @@ def budget_edit(request, id):
|
||||
else:
|
||||
return redirect(reverse('budget_edit', kwargs={'id': budget.id}))
|
||||
else:
|
||||
budget = models.Budget.objects.get(id=id)
|
||||
revision = budget.budgetrevision_set.reverse()[0]
|
||||
|
||||
return render(request, 'sstreasury/budget_edit.html', {
|
||||
'revision': revision,
|
||||
'contributors': '\n'.join(revision.contributors.all().values_list('email', flat=True))
|
||||
})
|
||||
|
||||
@login_required
|
||||
def budget_action(request, id):
|
||||
budget = models.Budget.objects.get(id=id)
|
||||
revision = budget.budgetrevision_set.reverse()[0]
|
||||
@uses_budget
|
||||
@budget_editable
|
||||
def budget_action(request, budget, revision):
|
||||
actions = request.POST['action'].split(',')
|
||||
|
||||
if request.user not in revision.contributors.all():
|
||||
raise PermissionDenied
|
||||
|
||||
if request.POST['action'] == 'Comment':
|
||||
if 'Comment' in actions and request.POST.get('comment', None):
|
||||
with transaction.atomic():
|
||||
comment = models.BudgetComment()
|
||||
comment.budget = budget
|
||||
@ -189,12 +228,11 @@ def budget_action(request, id):
|
||||
if user != request.user:
|
||||
emailer.send_mail([user.email], 'New comment on budget: {}'.format(revision.name), 'sstreasury/email/commented.md', {'revision': revision, 'comment': comment})
|
||||
|
||||
if request.POST['action'] == 'Submit':
|
||||
if 'Submit' in actions:
|
||||
if revision.state != models.BudgetState.DRAFT.value and revision.state != models.BudgetState.RESUBMIT.value:
|
||||
raise PermissionDenied
|
||||
|
||||
with transaction.atomic():
|
||||
# Copy revision
|
||||
revision.copy()
|
||||
revision.author = request.user
|
||||
revision.time = timezone.now()
|
||||
@ -204,15 +242,14 @@ def budget_action(request, id):
|
||||
|
||||
emailer = Emailer()
|
||||
for user in User.objects.filter(groups__name='Treasury'):
|
||||
emailer.send_mail([user.email], 'Budget submitted: {}'.format(revision.name), 'sstreasury/email/submitted_treasurer.md', {'revision': revision})
|
||||
emailer.send_mail([user.email], 'Action required: Budget submitted: {}'.format(revision.name), 'sstreasury/email/submitted_treasurer.md', {'revision': revision})
|
||||
for user in revision.contributors.all():
|
||||
emailer.send_mail([user.email], 'Budget submitted: {}'.format(revision.name), 'sstreasury/email/submitted_drafter.md', {'revision': revision})
|
||||
|
||||
if request.POST['action'] == 'Withdraw':
|
||||
if 'Withdraw' in actions:
|
||||
if revision.state == models.BudgetState.DRAFT.value or revision.state == models.BudgetState.RESUBMIT.value or revision.state == models.BudgetState.APPROVED.value:
|
||||
raise PermissionDenied
|
||||
|
||||
# Copy revision
|
||||
revision.copy()
|
||||
revision.author = request.user
|
||||
revision.time = timezone.now()
|
||||
@ -220,4 +257,40 @@ def budget_action(request, id):
|
||||
revision.action = models.BudgetAction.UPDATE_STATE.value
|
||||
revision.save()
|
||||
|
||||
if 'Endorse' in actions:
|
||||
if revision.state != models.BudgetState.AWAIT_REVIEW.value:
|
||||
raise PermissionDenied
|
||||
if not request.user.groups.filter(name='Treasury').exists():
|
||||
raise PermissionDenied
|
||||
|
||||
with transaction.atomic():
|
||||
revision.copy()
|
||||
revision.author = request.user
|
||||
revision.time = timezone.now()
|
||||
revision.state = models.BudgetState.ENDORSED.value
|
||||
revision.action = models.BudgetAction.UPDATE_STATE.value
|
||||
revision.save()
|
||||
|
||||
emailer = Emailer()
|
||||
for user in revision.contributors.all():
|
||||
emailer.send_mail([user.email], 'Budget endorsed, awaiting committee approval: {}'.format(revision.name), 'sstreasury/email/endorsed.md', {'revision': revision})
|
||||
|
||||
if 'Return' in actions:
|
||||
if revision.state != models.BudgetState.AWAIT_REVIEW.value:
|
||||
raise PermissionDenied
|
||||
if not request.user.groups.filter(name='Treasury').exists():
|
||||
raise PermissionDenied
|
||||
|
||||
with transaction.atomic():
|
||||
revision.copy()
|
||||
revision.author = request.user
|
||||
revision.time = timezone.now()
|
||||
revision.state = models.BudgetState.RESUBMIT.value
|
||||
revision.action = models.BudgetAction.UPDATE_STATE.value
|
||||
revision.save()
|
||||
|
||||
emailer = Emailer()
|
||||
for user in revision.contributors.all():
|
||||
emailer.send_mail([user.email], 'Action required: Budget returned for re-drafting: {}'.format(revision.name), 'sstreasury/email/returned.md', {'revision': revision})
|
||||
|
||||
return redirect(reverse('budget_view', kwargs={'id': budget.id}))
|
||||
|
Loading…
Reference in New Issue
Block a user