Treasury endorsement/return of budgets
This commit is contained in:
parent
a0d6164d95
commit
69a7c052c1
@ -59,17 +59,13 @@
|
|||||||
{% if budgets_open %}
|
{% if budgets_open %}
|
||||||
<h2>Open budgets</h2>
|
<h2>Open budgets</h2>
|
||||||
|
|
||||||
{% for budget in budgets_open %}
|
{{ listbudgets(budgets_open) }}
|
||||||
{{ budget.name }}
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if budgets_closed %}
|
{% if budgets_closed %}
|
||||||
<h2>Closed budgets</h2>
|
<h2>Closed budgets</h2>
|
||||||
|
|
||||||
{% for budget in budgets_closed %}
|
{{ listbudgets(budgets_closed) }}
|
||||||
{{ budget.name }}
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
@ -29,9 +29,24 @@
|
|||||||
|
|
||||||
{% if revision.state == import('sstreasury.models').BudgetState.DRAFT.value or revision.state == import('sstreasury.models').BudgetState.RESUBMIT.value %}
|
{% 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>
|
<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>
|
<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 %}
|
{% else %}
|
||||||
<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>
|
<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 %}
|
{% endif %}
|
||||||
|
|
||||||
<input type="hidden" name="csrfmiddlewaretoken" value="{{ csrf_token }}">
|
<input type="hidden" name="csrfmiddlewaretoken" value="{{ csrf_token }}">
|
||||||
@ -126,6 +141,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<input type="hidden" name="csrfmiddlewaretoken" value="{{ csrf_token }}">
|
<input type="hidden" name="csrfmiddlewaretoken" value="{{ csrf_token }}">
|
||||||
<input class="ui primary button" type="submit" name="action" value="Comment">
|
<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>
|
</form>
|
||||||
|
|
||||||
<div class="ui feed">
|
<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 . import models
|
||||||
from ssmain.email import Emailer
|
from ssmain.email import Emailer
|
||||||
|
|
||||||
|
import functools
|
||||||
import itertools
|
import itertools
|
||||||
import json
|
import json
|
||||||
|
|
||||||
@ -44,12 +45,24 @@ def budget_list(request):
|
|||||||
for budget in models.Budget.objects.all():
|
for budget in models.Budget.objects.all():
|
||||||
revision = budget.budgetrevision_set.reverse()[0]
|
revision = budget.budgetrevision_set.reverse()[0]
|
||||||
state = models.BudgetState(revision.state)
|
state = models.BudgetState(revision.state)
|
||||||
if state in [models.BudgetState.DRAFT, models.BudgetState.RESUBMIT]:
|
|
||||||
budgets_action.append(revision)
|
if request.user.groups.filter(name='Treasury').exists():
|
||||||
elif state in [models.BudgetState.AWAIT_REVIEW, models.BudgetState.ENDORSED]:
|
if state == models.BudgetState.AWAIT_REVIEW:
|
||||||
budgets_open.append(revision)
|
budgets_action.append(revision)
|
||||||
|
elif state != models.BudgetState.APPROVED:
|
||||||
|
budgets_open.append(revision)
|
||||||
|
else:
|
||||||
|
budgets_closed.append(revision)
|
||||||
else:
|
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', {
|
return render(request, 'sstreasury/budget_list.html', {
|
||||||
'budgets_action': budgets_action,
|
'budgets_action': budgets_action,
|
||||||
@ -131,15 +144,46 @@ def budget_new(request):
|
|||||||
'contributors': request.user.email
|
'contributors': request.user.email
|
||||||
})
|
})
|
||||||
|
|
||||||
@login_required
|
def uses_budget(viewfunc):
|
||||||
def budget_edit(request, id):
|
@functools.wraps(viewfunc)
|
||||||
if request.method == 'POST':
|
def func(request, id):
|
||||||
budget = models.Budget.objects.get(id=id)
|
budget = models.Budget.objects.get(id=id)
|
||||||
revision = budget.budgetrevision_set.reverse()[0]
|
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 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
|
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':
|
if request.POST['submit'] == 'Delete':
|
||||||
budget.delete()
|
budget.delete()
|
||||||
return redirect(reverse('budget_list'))
|
return redirect(reverse('budget_list'))
|
||||||
@ -156,23 +200,18 @@ def budget_edit(request, id):
|
|||||||
else:
|
else:
|
||||||
return redirect(reverse('budget_edit', kwargs={'id': budget.id}))
|
return redirect(reverse('budget_edit', kwargs={'id': budget.id}))
|
||||||
else:
|
else:
|
||||||
budget = models.Budget.objects.get(id=id)
|
|
||||||
revision = budget.budgetrevision_set.reverse()[0]
|
|
||||||
|
|
||||||
return render(request, 'sstreasury/budget_edit.html', {
|
return render(request, 'sstreasury/budget_edit.html', {
|
||||||
'revision': revision,
|
'revision': revision,
|
||||||
'contributors': '\n'.join(revision.contributors.all().values_list('email', flat=True))
|
'contributors': '\n'.join(revision.contributors.all().values_list('email', flat=True))
|
||||||
})
|
})
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def budget_action(request, id):
|
@uses_budget
|
||||||
budget = models.Budget.objects.get(id=id)
|
@budget_editable
|
||||||
revision = budget.budgetrevision_set.reverse()[0]
|
def budget_action(request, budget, revision):
|
||||||
|
actions = request.POST['action'].split(',')
|
||||||
|
|
||||||
if request.user not in revision.contributors.all():
|
if 'Comment' in actions and request.POST.get('comment', None):
|
||||||
raise PermissionDenied
|
|
||||||
|
|
||||||
if request.POST['action'] == 'Comment':
|
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
comment = models.BudgetComment()
|
comment = models.BudgetComment()
|
||||||
comment.budget = budget
|
comment.budget = budget
|
||||||
@ -189,12 +228,11 @@ def budget_action(request, id):
|
|||||||
if user != request.user:
|
if user != request.user:
|
||||||
emailer.send_mail([user.email], 'New comment on budget: {}'.format(revision.name), 'sstreasury/email/commented.md', {'revision': revision, 'comment': comment})
|
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:
|
if revision.state != models.BudgetState.DRAFT.value and revision.state != models.BudgetState.RESUBMIT.value:
|
||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
|
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
# Copy revision
|
|
||||||
revision.copy()
|
revision.copy()
|
||||||
revision.author = request.user
|
revision.author = request.user
|
||||||
revision.time = timezone.now()
|
revision.time = timezone.now()
|
||||||
@ -204,15 +242,14 @@ def budget_action(request, id):
|
|||||||
|
|
||||||
emailer = Emailer()
|
emailer = Emailer()
|
||||||
for user in User.objects.filter(groups__name='Treasury'):
|
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():
|
for user in revision.contributors.all():
|
||||||
emailer.send_mail([user.email], 'Budget submitted: {}'.format(revision.name), 'sstreasury/email/submitted_drafter.md', {'revision': revision})
|
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:
|
if revision.state == models.BudgetState.DRAFT.value or revision.state == models.BudgetState.RESUBMIT.value or revision.state == models.BudgetState.APPROVED.value:
|
||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
|
|
||||||
# Copy revision
|
|
||||||
revision.copy()
|
revision.copy()
|
||||||
revision.author = request.user
|
revision.author = request.user
|
||||||
revision.time = timezone.now()
|
revision.time = timezone.now()
|
||||||
@ -220,4 +257,40 @@ def budget_action(request, id):
|
|||||||
revision.action = models.BudgetAction.UPDATE_STATE.value
|
revision.action = models.BudgetAction.UPDATE_STATE.value
|
||||||
revision.save()
|
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}))
|
return redirect(reverse('budget_view', kwargs={'id': budget.id}))
|
||||||
|
Loading…
Reference in New Issue
Block a user