Implement sending urgent reminder emails to all members of the applicable committee for an endorsed budget
This commit is contained in:
parent
490faa8147
commit
8a3a09d8ab
@ -259,73 +259,80 @@
|
||||
{% if revision.state == import('sstreasury.models').BudgetState.ENDORSED.value %}
|
||||
<h2>Committee voting</h2>
|
||||
|
||||
<p>{{ dict(settings.AVAILABLE_APPROVERS)[revision.approver][1] }} votes in favour are required for approval.</p>
|
||||
|
||||
<form class="ui three column grid" action="{{ url('budget_action', kwargs={'id': revision.budget.id}) }}" method="POST">
|
||||
<form action="{{ url('budget_action', kwargs={'id': revision.budget.id}) }}" method="POST">
|
||||
<p>
|
||||
{{ dict(settings.AVAILABLE_APPROVERS)[revision.approver][1] }} votes in favour are required for approval.
|
||||
{% if is_latest and request.user.groups.filter(name='Executive').exists() %}
|
||||
<button style="margin-left: 1em;" class="ui small primary labeled icon button" type="submit" name="action" value="SendVotingReminders"><i class="envelope icon"></i>Send urgent reminder emails</button>
|
||||
{% endif %}
|
||||
</p>
|
||||
|
||||
<div class="ui three column grid">
|
||||
<div class="column">
|
||||
<div class="ui fluid card">
|
||||
<div class="content">
|
||||
<div class="header">In favour ({{ revision.budgetvote_set.filter(is_current=True, vote_type=import('sstreasury.models').BudgetVoteType.IN_FAVOUR.value).count() }})</div>
|
||||
{% if revision.budgetvote_set.filter(is_current=True, vote_type=import('sstreasury.models').BudgetVoteType.IN_FAVOUR.value).exists() %}
|
||||
<div class="description">
|
||||
<ul style="margin-bottom:0">
|
||||
{% for vote in revision.budgetvote_set.filter(is_current=True, vote_type=import('sstreasury.models').BudgetVoteType.IN_FAVOUR.value) %}
|
||||
<li>{{ vote.voter.first_name }} {{ vote.voter.last_name }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if is_latest and revision.can_vote(request.user) %}
|
||||
<button class="ui bottom attached positive button" type="submit" name="action" value="VoteInFavour">
|
||||
Vote in favour
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="column">
|
||||
<div class="ui fluid card">
|
||||
<div class="content">
|
||||
<div class="header">Against ({{ revision.budgetvote_set.filter(is_current=True, vote_type=import('sstreasury.models').BudgetVoteType.AGAINST.value).count() }})</div>
|
||||
{% if revision.budgetvote_set.filter(is_current=True, vote_type=import('sstreasury.models').BudgetVoteType.AGAINST.value).exists() %}
|
||||
<div class="description">
|
||||
<ul style="margin-bottom:0">
|
||||
{% for vote in revision.budgetvote_set.filter(is_current=True, vote_type=import('sstreasury.models').BudgetVoteType.AGAINST.value) %}
|
||||
<li>{{ vote.voter.first_name }} {{ vote.voter.last_name }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if is_latest and revision.can_vote(request.user) %}
|
||||
<button class="ui bottom attached negative button" type="submit" name="action" value="VoteAgainst">
|
||||
Vote against
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="column">
|
||||
<div class="ui fluid card">
|
||||
<div class="content">
|
||||
<div class="header">Abstentions ({{ revision.budgetvote_set.filter(is_current=True, vote_type=import('sstreasury.models').BudgetVoteType.ABSTAIN.value).count() }})</div>
|
||||
{% if revision.budgetvote_set.filter(is_current=True, vote_type=import('sstreasury.models').BudgetVoteType.ABSTAIN.value).exists() %}
|
||||
<div class="description">
|
||||
<ul style="margin-bottom:0">
|
||||
{% for vote in revision.budgetvote_set.filter(is_current=True, vote_type=import('sstreasury.models').BudgetVoteType.ABSTAIN.value) %}
|
||||
<li>{{ vote.voter.first_name }} {{ vote.voter.last_name }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if is_latest and revision.can_vote(request.user) %}
|
||||
<button class="ui bottom attached secondary button" type="submit" name="action" value="VoteAbstain">
|
||||
Abstain
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" name="csrfmiddlewaretoken" value="{{ csrf_token }}">
|
||||
<div class="column">
|
||||
<div class="ui fluid card">
|
||||
<div class="content">
|
||||
<div class="header">In favour ({{ revision.budgetvote_set.filter(is_current=True, vote_type=import('sstreasury.models').BudgetVoteType.IN_FAVOUR.value).count() }})</div>
|
||||
{% if revision.budgetvote_set.filter(is_current=True, vote_type=import('sstreasury.models').BudgetVoteType.IN_FAVOUR.value).exists() %}
|
||||
<div class="description">
|
||||
<ul style="margin-bottom:0">
|
||||
{% for vote in revision.budgetvote_set.filter(is_current=True, vote_type=import('sstreasury.models').BudgetVoteType.IN_FAVOUR.value) %}
|
||||
<li>{{ vote.voter.first_name }} {{ vote.voter.last_name }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if is_latest and revision.can_vote(request.user) %}
|
||||
<button class="ui bottom attached positive button" type="submit" name="action" value="VoteInFavour">
|
||||
Vote in favour
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="column">
|
||||
<div class="ui fluid card">
|
||||
<div class="content">
|
||||
<div class="header">Against ({{ revision.budgetvote_set.filter(is_current=True, vote_type=import('sstreasury.models').BudgetVoteType.AGAINST.value).count() }})</div>
|
||||
{% if revision.budgetvote_set.filter(is_current=True, vote_type=import('sstreasury.models').BudgetVoteType.AGAINST.value).exists() %}
|
||||
<div class="description">
|
||||
<ul style="margin-bottom:0">
|
||||
{% for vote in revision.budgetvote_set.filter(is_current=True, vote_type=import('sstreasury.models').BudgetVoteType.AGAINST.value) %}
|
||||
<li>{{ vote.voter.first_name }} {{ vote.voter.last_name }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if is_latest and revision.can_vote(request.user) %}
|
||||
<button class="ui bottom attached negative button" type="submit" name="action" value="VoteAgainst">
|
||||
Vote against
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="column">
|
||||
<div class="ui fluid card">
|
||||
<div class="content">
|
||||
<div class="header">Abstentions ({{ revision.budgetvote_set.filter(is_current=True, vote_type=import('sstreasury.models').BudgetVoteType.ABSTAIN.value).count() }})</div>
|
||||
{% if revision.budgetvote_set.filter(is_current=True, vote_type=import('sstreasury.models').BudgetVoteType.ABSTAIN.value).exists() %}
|
||||
<div class="description">
|
||||
<ul style="margin-bottom:0">
|
||||
{% for vote in revision.budgetvote_set.filter(is_current=True, vote_type=import('sstreasury.models').BudgetVoteType.ABSTAIN.value) %}
|
||||
<li>{{ vote.voter.first_name }} {{ vote.voter.last_name }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if is_latest and revision.can_vote(request.user) %}
|
||||
<button class="ui bottom attached secondary button" type="submit" name="action" value="VoteAbstain">
|
||||
Abstain
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
|
@ -0,0 +1,3 @@
|
||||
This is an urgent reminder from {{ requester.first_name }} {{ requester.last_name }} to vote on a budget titled *{{ revision.name }}* (BU-{{ revision.budget.id }}), which has been endorsed by Treasury and is awaiting committee review.
|
||||
|
||||
{{ baseurl }}{{ url('budget_view', kwargs={'id': revision.budget.id}) }}
|
@ -477,6 +477,20 @@ def budget_action(request, budget, revision):
|
||||
with transaction.atomic():
|
||||
revision.update_state(request.user, models.BudgetState.CANCELLED)
|
||||
|
||||
if 'SendVotingReminders' in actions:
|
||||
if revision.state != models.BudgetState.ENDORSED.value:
|
||||
raise PermissionDenied
|
||||
if not request.user.groups.filter(name='Executive').exists():
|
||||
# TODO: Make this group configurable
|
||||
raise PermissionDenied
|
||||
|
||||
# Send emails
|
||||
emailer = Emailer()
|
||||
for user in User.objects.filter(groups__name=revision.approver):
|
||||
# Email only if not voted yet
|
||||
if not revision.budgetvote_set.filter(voter=user).exists():
|
||||
emailer.send_mail([user.email], 'URGENT action required: {} (BU-{})'.format(revision.name, budget.id), 'sstreasury/email/budget_vote_reminder.md', {'revision': revision, 'requester': request.user})
|
||||
|
||||
if 'VoteInFavour' in actions or 'VoteAgainst' in actions or 'VoteAbstain' in actions:
|
||||
if not revision.can_vote(request.user):
|
||||
raise PermissionDenied
|
||||
@ -489,7 +503,7 @@ def budget_action(request, budget, revision):
|
||||
vote_type = models.BudgetVoteType.ABSTAIN
|
||||
|
||||
# Already exists?
|
||||
if revision.budgetvote_set.filter(is_current=True, voter=request.user, vote_type=vote_type.value):
|
||||
if revision.budgetvote_set.filter(is_current=True, voter=request.user, vote_type=vote_type.value).exists():
|
||||
# No need to create new vote
|
||||
pass
|
||||
else:
|
||||
|
Loading…
Reference in New Issue
Block a user