Centralise access control logic
This commit is contained in:
parent
b70ef9b8f6
commit
c4eb45514f
@ -27,36 +27,30 @@
|
|||||||
<span class="ui header">Status: {{ revision.get_state_display() }}</span>
|
<span class="ui header">Status: {{ revision.get_state_display() }}</span>
|
||||||
|
|
||||||
{% if is_latest %}
|
{% if is_latest %}
|
||||||
{% if revision.state == import('sstreasury.models').BudgetState.DRAFT.value or revision.state == import('sstreasury.models').BudgetState.RESUBMIT.value %}
|
{% if revision.can_submit(request.user) %}
|
||||||
<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>
|
||||||
|
{% endif %}
|
||||||
{#{% if request.user.groups.filter(name='Secretary').exists() %}
|
{% if revision.can_endorse(request.user) %}
|
||||||
<button class="ui mini labeled positive icon button" type="submit" name="action" value="Approve" onclick="return confirm('Are you sure you want to mark this budget as committee-approved?');"><i class="check icon"></i> Approve</button>
|
|
||||||
|
|
||||||
<button class="ui mini labeled basic negative icon button" type="submit" name="action" value="CmteReturn" onclick="return confirm('Are you sure you want to refuse this budget committee approval and return it for re-drafting?');"><i class="undo icon"></i> Return for re-drafting</button>
|
|
||||||
{% endif %}#}
|
|
||||||
{% 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 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>
|
<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.APPROVED.value and request.user.groups.filter(name='Secretary').exists() %}
|
{% endif %}
|
||||||
|
{% if revision.can_approve(request.user) %}
|
||||||
<button class="ui mini labeled positive icon button" type="submit" name="action" value="Approve" style="margin-left: 1em;" onclick="return confirm('Are you sure you want to mark this budget as committee-approved?');"><i class="check icon"></i> Approve</button>
|
<button class="ui mini labeled positive icon button" type="submit" name="action" value="Approve" style="margin-left: 1em;" onclick="return confirm('Are you sure you want to mark this budget as committee-approved?');"><i class="check icon"></i> Approve</button>
|
||||||
|
|
||||||
<button class="ui mini labeled basic negative icon button" type="submit" name="action" value="CmteReturn" onclick="return confirm('Are you sure you want to refuse this budget committee approval and return it for re-drafting?');"><i class="undo icon"></i> Return for re-drafting</button>
|
<button class="ui mini labeled basic negative icon button" type="submit" name="action" value="CmteReturn" onclick="return confirm('Are you sure you want to refuse this budget committee approval and return it for re-drafting?');"><i class="undo icon"></i> Return for re-drafting</button>
|
||||||
{% elif revision.state == import('sstreasury.models').BudgetState.APPROVED.value %}
|
{% endif %}
|
||||||
{# Blank #}
|
{% if revision.can_withdraw(request.user) %}
|
||||||
{% 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>
|
<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 %}
|
{% 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())) %}
|
{% if revision.can_edit(request.user) %}
|
||||||
<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>
|
||||||
<a class="ui mini labeled right floated icon button" href="{{ url('budget_print', kwargs={'id': revision.budget.id}) }}" target="_blank"><i class="print icon"></i> Print</a>
|
{% endif %}
|
||||||
{% elif revision.state == import('sstreasury.models').BudgetState.APPROVED.value %}
|
|
||||||
<a class="ui mini labeled right floated icon button" href="{{ url('budget_print', kwargs={'id': revision.budget.id}) }}" target="_blank"><i class="print icon"></i> Print</a>
|
<a class="ui mini labeled right floated icon button" href="{{ url('budget_print', kwargs={'id': revision.budget.id}) }}" target="_blank"><i class="print icon"></i> Print</a>
|
||||||
{% else %}
|
|
||||||
<a class="ui mini labeled right floated icon button" href="{{ url('budget_print', kwargs={'id': revision.budget.id}) }}" target="_blank"><i class="print icon"></i> Print</a>
|
{% if not revision.can_edit(request.user) and revision.can_withdraw(request.user) %}
|
||||||
|
|
||||||
<div class="ui message">
|
<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>
|
<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>
|
</div>
|
||||||
|
@ -26,26 +26,25 @@
|
|||||||
<form class="ui form" action="{{ url('claim_action', kwargs={'id': claim.id}) }}" method="POST">
|
<form class="ui form" action="{{ url('claim_action', kwargs={'id': claim.id}) }}" method="POST">
|
||||||
<span class="ui header">Status: {{ claim.get_state_display() }}</span>
|
<span class="ui header">Status: {{ claim.get_state_display() }}</span>
|
||||||
|
|
||||||
{% if claim.state == import('sstreasury.models').ClaimState.DRAFT.value or claim.state == import('sstreasury.models').ClaimState.RESUBMIT.value %}
|
{% if claim.can_submit(request.user) %}
|
||||||
<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 claim for Treasury approval? You will not be able to make any additional changes without withdrawing the claim.');"><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 claim for Treasury approval? You will not be able to make any additional changes without withdrawing the claim.');"><i class="paper plane icon"></i> Submit</button>
|
||||||
{% elif claim.state == import('sstreasury.models').ClaimState.AWAIT_REVIEW.value and request.user.groups.filter(name='Treasury').exists() %}
|
{% endif %}
|
||||||
|
{% if claim.can_approve(request.user) %}
|
||||||
<button class="ui mini labeled positive icon button" type="submit" name="action" value="Approve" style="margin-left: 1em;" onclick="return confirm('Are you sure you want to approve this claim?');"><i class="check icon"></i> Approve</button>
|
<button class="ui mini labeled positive icon button" type="submit" name="action" value="Approve" style="margin-left: 1em;" onclick="return confirm('Are you sure you want to approve this claim?');"><i class="check icon"></i> Approve</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 claim and return it for re-drafting?');"><i class="undo icon"></i> Return for re-drafting</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 claim and return it for re-drafting?');"><i class="undo icon"></i> Return for re-drafting</button>
|
||||||
{% elif claim.state == import('sstreasury.models').ClaimState.APPROVED.value or claim.state == import('sstreasury.models').ClaimState.PAID.value %}
|
{% endif %}
|
||||||
{# Blank #}
|
{% if claim.can_withdraw(request.user) %}
|
||||||
{% 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>
|
<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 %}
|
{% endif %}
|
||||||
|
|
||||||
{% if claim.state == import('sstreasury.models').ClaimState.DRAFT.value or claim.state == import('sstreasury.models').ClaimState.RESUBMIT.value or (claim.state != import('sstreasury.models').ClaimState.APPROVED.value and claim.state != import('sstreasury.models').ClaimState.PAID.value and request.user.groups.filter(name='Treasury').exists()) %}
|
{% if claim.can_edit(request.user) %}
|
||||||
<a class="ui mini labeled right floated icon button" href="{{ url('claim_edit', kwargs={'id': claim.id}) }}"><i class="edit icon"></i> Edit</a>
|
<a class="ui mini labeled right floated icon button" href="{{ url('claim_edit', kwargs={'id': claim.id}) }}"><i class="edit icon"></i> Edit</a>
|
||||||
<a class="ui mini labeled right floated icon button" href="{{ url('claim_print', kwargs={'id': claim.id}) }}" target="_blank"><i class="print icon"></i> Print</a>
|
{% endif %}
|
||||||
{% elif claim.state == import('sstreasury.models').ClaimState.APPROVED.value or claim.state == import('sstreasury.models').ClaimState.PAID.value %}
|
|
||||||
<a class="ui mini labeled right floated icon button" href="{{ url('claim_print', kwargs={'id': claim.id}) }}" target="_blank"><i class="print icon"></i> Print</a>
|
<a class="ui mini labeled right floated icon button" href="{{ url('claim_print', kwargs={'id': claim.id}) }}" target="_blank"><i class="print icon"></i> Print</a>
|
||||||
{% else %}
|
|
||||||
<a class="ui mini labeled right floated icon button" href="{{ url('claim_print', kwargs={'id': claim.id}) }}" target="_blank"><i class="print icon"></i> Print</a>
|
{% if not claim.can_edit(request.user) and claim.can_withdraw(request.user) %}
|
||||||
|
|
||||||
<div class="ui message">
|
<div class="ui message">
|
||||||
<p>This claim has been submitted and is now awaiting processing. If you wish to edit this claim, you must first withdraw it. This will revert the claim to a draft.</p>
|
<p>This claim has been submitted and is now awaiting processing. If you wish to edit this claim, you must first withdraw it. This will revert the claim to a draft.</p>
|
||||||
</div>
|
</div>
|
||||||
@ -111,7 +110,7 @@
|
|||||||
<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 claim.state == import('sstreasury.models').ClaimState.AWAIT_REVIEW.value and request.user.groups.filter(name='Treasury').exists() %}
|
{% if claim.can_approve(request.user) %}
|
||||||
<button class="ui right floated labeled basic negative icon button" type="submit" name="action" value="Comment,Return" onclick="return confirm('Are you sure you want to refuse this claim and return it for re-drafting?');"><i class="undo icon"></i> Comment and return for re-drafting</button>
|
<button class="ui right floated labeled basic negative icon button" type="submit" name="action" value="Comment,Return" onclick="return confirm('Are you sure you want to refuse this claim and return it for re-drafting?');"><i class="undo icon"></i> Comment and return for re-drafting</button>
|
||||||
|
|
||||||
<button class="ui right floated labeled positive icon button" type="submit" name="action" value="Comment,Approve" onclick="return confirm('Are you sure you want to approve this claim?');"><i class="check icon"></i> Comment and approve</button>
|
<button class="ui right floated labeled positive icon button" type="submit" name="action" value="Comment,Approve" onclick="return confirm('Are you sure you want to approve this claim?');"><i class="check icon"></i> Comment and approve</button>
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.utils import timezone
|
||||||
from jsonfield import JSONField
|
from jsonfield import JSONField
|
||||||
|
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
@ -74,12 +75,25 @@ class BudgetRevision(models.Model):
|
|||||||
|
|
||||||
action = models.IntegerField(choices=[(v.value, v.description) for v in BudgetAction])
|
action = models.IntegerField(choices=[(v.value, v.description) for v in BudgetAction])
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ['id']
|
||||||
|
|
||||||
def copy(self):
|
def copy(self):
|
||||||
contributors = list(self.contributors.all())
|
contributors = list(self.contributors.all())
|
||||||
self.pk, self.id = None, None
|
self.pk, self.id = None, None
|
||||||
self.save()
|
self.save()
|
||||||
self.contributors.add(*contributors)
|
self.contributors.add(*contributors)
|
||||||
|
|
||||||
|
def update_state(self, user, state):
|
||||||
|
self.copy()
|
||||||
|
self.author = user
|
||||||
|
self.time = timezone.now()
|
||||||
|
self.state = state.value
|
||||||
|
self.action = BudgetAction.UPDATE_STATE.value
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
# Access control
|
||||||
|
|
||||||
def can_view(self, user):
|
def can_view(self, user):
|
||||||
if user == self.author:
|
if user == self.author:
|
||||||
return True
|
return True
|
||||||
@ -87,12 +101,74 @@ class BudgetRevision(models.Model):
|
|||||||
return True
|
return True
|
||||||
if user.groups.filter(name='Treasury').exists():
|
if user.groups.filter(name='Treasury').exists():
|
||||||
return True
|
return True
|
||||||
if (self.state == BudgetState.ENDORSED or self.state == BudgetState.APPROVED) and user.groups.filter(name='Committee').exists():
|
if (self.state == BudgetState.ENDORSED.value or self.state == BudgetState.APPROVED.value) and user.groups.filter(name='Committee').exists():
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def can_edit(self, user):
|
||||||
|
# Cannot edit if cannot view
|
||||||
|
if not self.can_view(user):
|
||||||
|
return False
|
||||||
|
|
||||||
|
# No one can edit if already approved
|
||||||
|
if self.state == BudgetState.APPROVED.value:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Only Treasurer or Secretary may edit if submitted
|
||||||
|
if self.state != BudgetState.DRAFT.value and self.state != BudgetState.RESUBMIT.value:
|
||||||
|
if user.groups.filter(name='Treasury').exists() or user.groups.filter(name='Secretary').exists():
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Otherwise the submitter may edit
|
||||||
|
if user == self.author:
|
||||||
|
return True
|
||||||
|
if user in self.contributors.all():
|
||||||
|
return True
|
||||||
|
if user.groups.filter(name='Treasury').exists():
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Otherwise cannot edit
|
||||||
|
return False
|
||||||
|
|
||||||
|
def can_submit(self, user):
|
||||||
|
if not self.can_edit(user):
|
||||||
|
return False
|
||||||
|
if self.state == BudgetState.DRAFT.value or self.state == BudgetState.RESUBMIT.value:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
class Meta:
|
def can_withdraw(self, user):
|
||||||
ordering = ['id']
|
if not self.can_edit(user):
|
||||||
|
return False
|
||||||
|
if self.state == BudgetState.AWAIT_REVIEW.value or self.state == BudgetState.ENDORSED.value:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def can_endorse(self, user):
|
||||||
|
if not self.can_edit(user):
|
||||||
|
return False
|
||||||
|
if not user.groups.filter(name='Treasury').exists():
|
||||||
|
return False
|
||||||
|
if self.state == BudgetState.AWAIT_REVIEW.value:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def can_return(self, user):
|
||||||
|
return self.can_endorse(user)
|
||||||
|
|
||||||
|
def can_approve(self, user):
|
||||||
|
if not self.can_edit(user):
|
||||||
|
return False
|
||||||
|
if not user.groups.filter(name='Secretary').exists():
|
||||||
|
return False
|
||||||
|
if self.state == BudgetState.ENDORSED.value:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def can_cmtereturn(self, user):
|
||||||
|
return self.can_approve(user)
|
||||||
|
|
||||||
class ClaimState(DescriptionEnum):
|
class ClaimState(DescriptionEnum):
|
||||||
DRAFT = 10, 'Draft'
|
DRAFT = 10, 'Draft'
|
||||||
@ -119,12 +195,78 @@ class ReimbursementClaim(models.Model):
|
|||||||
|
|
||||||
items = JSONField(default=[])
|
items = JSONField(default=[])
|
||||||
|
|
||||||
|
def update_state(self, user, state):
|
||||||
|
self.state = state.value
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
claim_history = ClaimHistory()
|
||||||
|
claim_history.claim = self
|
||||||
|
claim_history.author = user
|
||||||
|
claim_history.state = self.state
|
||||||
|
claim_history.time = timezone.now()
|
||||||
|
claim_history.action = ClaimAction.UPDATE_STATE.value
|
||||||
|
claim_history.save()
|
||||||
|
|
||||||
|
# Access control
|
||||||
|
|
||||||
def can_view(self, user):
|
def can_view(self, user):
|
||||||
if user == self.author:
|
if user == self.author:
|
||||||
return True
|
return True
|
||||||
if user.groups.filter(name='Treasury').exists():
|
if user.groups.filter(name='Treasury').exists():
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def can_edit(self, user):
|
||||||
|
# Cannot edit if cannot view
|
||||||
|
if not self.can_view(user):
|
||||||
|
return False
|
||||||
|
|
||||||
|
# No one can edit if already paid
|
||||||
|
if self.state == ClaimState.PAID.value:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Only Treasurer may edit if submitted
|
||||||
|
if self.state != ClaimState.DRAFT.value and self.state != ClaimState.RESUBMIT.value:
|
||||||
|
if user.groups.filter(name='Treasury').exists():
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Otherwise the submitter or Treasurer may edit
|
||||||
|
if user == self.author:
|
||||||
|
return True
|
||||||
|
if user in self.contributors.all():
|
||||||
|
return True
|
||||||
|
if user.groups.filter(name='Treasury').exists():
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Otherwise cannot edit
|
||||||
|
return False
|
||||||
|
|
||||||
|
def can_submit(self, user):
|
||||||
|
if not self.can_edit(user):
|
||||||
|
return False
|
||||||
|
if self.state == ClaimState.DRAFT.value or self.state == ClaimState.RESUBMIT.value:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def can_withdraw(self, user):
|
||||||
|
if not self.can_edit(user):
|
||||||
|
return False
|
||||||
|
if self.state == ClaimState.AWAIT_REVIEW.value or self.state == ClaimState.APPROVED.value:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def can_approve(self, user):
|
||||||
|
if not self.can_edit(user):
|
||||||
|
return False
|
||||||
|
if not user.groups.filter(name='Treasury').exists():
|
||||||
|
return False
|
||||||
|
if self.state == ClaimState.AWAIT_REVIEW.value:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def can_return(self, user):
|
||||||
|
return self.can_approve(user)
|
||||||
|
|
||||||
class ClaimReceipt(models.Model):
|
class ClaimReceipt(models.Model):
|
||||||
claim = models.ForeignKey(ReimbursementClaim, on_delete=models.CASCADE)
|
claim = models.ForeignKey(ReimbursementClaim, on_delete=models.CASCADE)
|
||||||
|
@ -32,10 +32,107 @@ import functools
|
|||||||
import itertools
|
import itertools
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
# HELPER DECORATORS
|
||||||
|
|
||||||
|
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 not revision.can_view(request.user):
|
||||||
|
raise PermissionDenied
|
||||||
|
return viewfunc(request, budget, revision)
|
||||||
|
return func
|
||||||
|
|
||||||
|
def budget_editable(viewfunc):
|
||||||
|
@functools.wraps(viewfunc)
|
||||||
|
def func(request, budget, revision):
|
||||||
|
if not revision.can_edit(request.user):
|
||||||
|
raise PermissionDenied
|
||||||
|
return viewfunc(request, budget, revision)
|
||||||
|
return func
|
||||||
|
|
||||||
|
def uses_claim(viewfunc):
|
||||||
|
@functools.wraps(viewfunc)
|
||||||
|
def func(request, id):
|
||||||
|
claim = models.ReimbursementClaim.objects.get(id=id)
|
||||||
|
return viewfunc(request, claim)
|
||||||
|
return func
|
||||||
|
|
||||||
|
def claim_viewable(viewfunc):
|
||||||
|
@functools.wraps(viewfunc)
|
||||||
|
def func(request, claim):
|
||||||
|
if not claim.can_view(request.user):
|
||||||
|
raise PermissionDenied
|
||||||
|
return viewfunc(request, claim)
|
||||||
|
return func
|
||||||
|
|
||||||
|
def claim_editable(viewfunc):
|
||||||
|
@functools.wraps(viewfunc)
|
||||||
|
def func(request, claim):
|
||||||
|
if not claim.can_edit(request.user):
|
||||||
|
raise PermissionDenied
|
||||||
|
return viewfunc(request, claim)
|
||||||
|
return func
|
||||||
|
|
||||||
|
# HELPER FUNCTIONS
|
||||||
|
|
||||||
|
def revision_from_form(budget, revision, form):
|
||||||
|
revision.budget = budget
|
||||||
|
|
||||||
|
revision.name = form['name']
|
||||||
|
revision.date = form['date'] if form['date'] else None
|
||||||
|
|
||||||
|
revision.comments = form['comments']
|
||||||
|
revision.revenue = json.loads(form['revenue'])
|
||||||
|
revision.revenue_comments = form['revenue_comments']
|
||||||
|
revision.expense = json.loads(form['expense'])
|
||||||
|
revision.expense_comments = form['expense_comments']
|
||||||
|
revision.expense_no_emergency_fund = True if form.get('expense_no_emergency_fund', False) else False
|
||||||
|
|
||||||
|
revision.save()
|
||||||
|
|
||||||
|
contributors = form['contributors'].split('\n')
|
||||||
|
for contributor in contributors:
|
||||||
|
validate_email(contributor.strip())
|
||||||
|
for contributor in contributors:
|
||||||
|
try:
|
||||||
|
user = User.objects.get(email=contributor.strip())
|
||||||
|
except User.DoesNotExist:
|
||||||
|
user = User.objects.create_user(contributor.strip().split('@')[0], contributor.strip())
|
||||||
|
revision.contributors.add(user)
|
||||||
|
|
||||||
|
revision.save()
|
||||||
|
|
||||||
|
return revision
|
||||||
|
|
||||||
|
def claim_from_form(claim, form):
|
||||||
|
claim.purpose = form['purpose']
|
||||||
|
claim.date = form['date'] if form['date'] else None
|
||||||
|
claim.budget_id = form['budget_id']
|
||||||
|
|
||||||
|
claim.comments = form['comments']
|
||||||
|
claim.state = models.ClaimState.DRAFT.value
|
||||||
|
claim.items = json.loads(form['items'])
|
||||||
|
|
||||||
|
claim.save()
|
||||||
|
|
||||||
|
return claim
|
||||||
|
|
||||||
|
# INDEX VIEW
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def index(request):
|
def index(request):
|
||||||
return render(request, 'sstreasury/index.html')
|
return render(request, 'sstreasury/index.html')
|
||||||
|
|
||||||
|
# BUDGET VIEWS
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def budget_list(request):
|
def budget_list(request):
|
||||||
budgets_action = []
|
budgets_action = []
|
||||||
@ -77,13 +174,10 @@ def budget_list(request):
|
|||||||
})
|
})
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def budget_view(request, id):
|
@uses_budget
|
||||||
budget = models.Budget.objects.get(id=id)
|
def budget_view(request, budget, revision):
|
||||||
|
|
||||||
if 'revision' in request.GET:
|
if 'revision' in request.GET:
|
||||||
revision = budget.budgetrevision_set.get(id=int(request.GET['revision']))
|
revision = budget.budgetrevision_set.get(id=int(request.GET['revision']))
|
||||||
else:
|
|
||||||
revision = budget.budgetrevision_set.reverse()[0]
|
|
||||||
|
|
||||||
if not revision.can_view(request.user):
|
if not revision.can_view(request.user):
|
||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
@ -98,13 +192,10 @@ def budget_view(request, id):
|
|||||||
})
|
})
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def budget_print(request, id):
|
@uses_budget
|
||||||
budget = models.Budget.objects.get(id=id)
|
def budget_print(request, budget, revision):
|
||||||
|
|
||||||
if 'revision' in request.GET:
|
if 'revision' in request.GET:
|
||||||
revision = budget.budgetrevision_set.get(id=int(request.GET['revision']))
|
revision = budget.budgetrevision_set.get(id=int(request.GET['revision']))
|
||||||
else:
|
|
||||||
revision = budget.budgetrevision_set.reverse()[0]
|
|
||||||
|
|
||||||
if not revision.can_view(request.user):
|
if not revision.can_view(request.user):
|
||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
@ -114,35 +205,6 @@ def budget_print(request, id):
|
|||||||
'is_latest': 'revision' not in request.GET
|
'is_latest': 'revision' not in request.GET
|
||||||
})
|
})
|
||||||
|
|
||||||
def revision_from_form(budget, revision, form):
|
|
||||||
revision.budget = budget
|
|
||||||
|
|
||||||
revision.name = form['name']
|
|
||||||
revision.date = form['date'] if form['date'] else None
|
|
||||||
|
|
||||||
revision.comments = form['comments']
|
|
||||||
revision.revenue = json.loads(form['revenue'])
|
|
||||||
revision.revenue_comments = form['revenue_comments']
|
|
||||||
revision.expense = json.loads(form['expense'])
|
|
||||||
revision.expense_comments = form['expense_comments']
|
|
||||||
revision.expense_no_emergency_fund = True if form.get('expense_no_emergency_fund', False) else False
|
|
||||||
|
|
||||||
revision.save()
|
|
||||||
|
|
||||||
contributors = form['contributors'].split('\n')
|
|
||||||
for contributor in contributors:
|
|
||||||
validate_email(contributor.strip())
|
|
||||||
for contributor in contributors:
|
|
||||||
try:
|
|
||||||
user = User.objects.get(email=contributor.strip())
|
|
||||||
except User.DoesNotExist:
|
|
||||||
user = User.objects.create_user(contributor.strip().split('@')[0], contributor.strip())
|
|
||||||
revision.contributors.add(user)
|
|
||||||
|
|
||||||
revision.save()
|
|
||||||
|
|
||||||
return revision
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def budget_new(request):
|
def budget_new(request):
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
@ -170,39 +232,6 @@ def budget_new(request):
|
|||||||
'contributors': request.user.email
|
'contributors': request.user.email
|
||||||
})
|
})
|
||||||
|
|
||||||
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 not revision.can_view(request.user):
|
|
||||||
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 not revision.can_view(request.user):
|
|
||||||
raise PermissionDenied
|
|
||||||
|
|
||||||
return viewfunc(request, budget, revision)
|
|
||||||
return func
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@uses_budget
|
@uses_budget
|
||||||
@budget_editable
|
@budget_editable
|
||||||
@ -253,16 +282,11 @@ def budget_action(request, budget, revision):
|
|||||||
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 'Submit' in actions:
|
if 'Submit' in actions:
|
||||||
if revision.state != models.BudgetState.DRAFT.value and revision.state != models.BudgetState.RESUBMIT.value:
|
if not revision.can_submit(request.user):
|
||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
|
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
revision.copy()
|
revision.update_state(request.user, models.BudgetState.AWAIT_REVIEW)
|
||||||
revision.author = request.user
|
|
||||||
revision.time = timezone.now()
|
|
||||||
revision.state = models.BudgetState.AWAIT_REVIEW.value
|
|
||||||
revision.action = models.BudgetAction.UPDATE_STATE.value
|
|
||||||
revision.save()
|
|
||||||
|
|
||||||
emailer = Emailer()
|
emailer = Emailer()
|
||||||
for user in User.objects.filter(groups__name='Treasury'):
|
for user in User.objects.filter(groups__name='Treasury'):
|
||||||
@ -271,29 +295,18 @@ def budget_action(request, budget, revision):
|
|||||||
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 'Withdraw' in actions:
|
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 not revision.can_withdraw(user):
|
||||||
raise PermissionDenied
|
|
||||||
|
|
||||||
revision.copy()
|
|
||||||
revision.author = request.user
|
|
||||||
revision.time = timezone.now()
|
|
||||||
revision.state = models.BudgetState.DRAFT.value
|
|
||||||
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
|
raise PermissionDenied
|
||||||
|
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
revision.copy()
|
revision.update_state(request.user, models.BudgetState.DRAFT)
|
||||||
revision.author = request.user
|
|
||||||
revision.time = timezone.now()
|
if 'Endorse' in actions:
|
||||||
revision.state = models.BudgetState.ENDORSED.value
|
if not revision.can_endorse(user):
|
||||||
revision.action = models.BudgetAction.UPDATE_STATE.value
|
raise PermissionDenied
|
||||||
revision.save()
|
|
||||||
|
with transaction.atomic():
|
||||||
|
revision.update_state(request.user, models.BudgetState.ENDORSED)
|
||||||
|
|
||||||
emailer = Emailer()
|
emailer = Emailer()
|
||||||
for user in User.objects.filter(groups__name='Secretary'):
|
for user in User.objects.filter(groups__name='Secretary'):
|
||||||
@ -302,54 +315,33 @@ def budget_action(request, budget, revision):
|
|||||||
emailer.send_mail([user.email], 'Budget endorsed, awaiting committee approval: {}'.format(revision.name), 'sstreasury/email/endorsed.md', {'revision': revision})
|
emailer.send_mail([user.email], 'Budget endorsed, awaiting committee approval: {}'.format(revision.name), 'sstreasury/email/endorsed.md', {'revision': revision})
|
||||||
|
|
||||||
if 'Return' in actions:
|
if 'Return' in actions:
|
||||||
if revision.state != models.BudgetState.AWAIT_REVIEW.value:
|
if not revision.can_return(user):
|
||||||
raise PermissionDenied
|
|
||||||
if not request.user.groups.filter(name='Treasury').exists():
|
|
||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
|
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
revision.copy()
|
revision.update_state(request.user, models.BudgetState.RESUBMIT)
|
||||||
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()
|
emailer = Emailer()
|
||||||
for user in revision.contributors.all():
|
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})
|
emailer.send_mail([user.email], 'Action required: Budget returned for re-drafting: {}'.format(revision.name), 'sstreasury/email/returned.md', {'revision': revision})
|
||||||
|
|
||||||
if 'Approve' in actions:
|
if 'Approve' in actions:
|
||||||
if revision.state == models.BudgetState.APPROVED.value:
|
if not revision.can_approve(user):
|
||||||
raise PermissionDenied
|
return PermissionDenied
|
||||||
if not request.user.groups.filter(name='Secretary').exists():
|
|
||||||
raise PermissionDenied
|
|
||||||
|
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
revision.copy()
|
revision.update_state(request.user, models.BudgetState.APPROVED)
|
||||||
revision.author = request.user
|
|
||||||
revision.time = timezone.now()
|
|
||||||
revision.state = models.BudgetState.APPROVED.value
|
|
||||||
revision.action = models.BudgetAction.UPDATE_STATE.value
|
|
||||||
revision.save()
|
|
||||||
|
|
||||||
emailer = Emailer()
|
emailer = Emailer()
|
||||||
for user in revision.contributors.all():
|
for user in revision.contributors.all():
|
||||||
emailer.send_mail([user.email], 'Budget approved: {}'.format(revision.name), 'sstreasury/email/approved.md', {'revision': revision})
|
emailer.send_mail([user.email], 'Budget approved: {}'.format(revision.name), 'sstreasury/email/approved.md', {'revision': revision})
|
||||||
|
|
||||||
if 'CmteReturn' in actions:
|
if 'CmteReturn' in actions:
|
||||||
if revision.state == models.BudgetState.APPROVED.value:
|
if not revision.can_cmtereturn(user):
|
||||||
raise PermissionDenied
|
return PermissionDenied
|
||||||
if not request.user.groups.filter(name='Secretary').exists():
|
|
||||||
raise PermissionDenied
|
|
||||||
|
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
revision.copy()
|
revision.update_state(request.user, models.BudgetState.RESUBMIT)
|
||||||
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()
|
emailer = Emailer()
|
||||||
for user in revision.contributors.all():
|
for user in revision.contributors.all():
|
||||||
@ -392,19 +384,6 @@ def claim_list(request):
|
|||||||
'claims_closed': claims_closed
|
'claims_closed': claims_closed
|
||||||
})
|
})
|
||||||
|
|
||||||
def claim_from_form(claim, form):
|
|
||||||
claim.purpose = form['purpose']
|
|
||||||
claim.date = form['date'] if form['date'] else None
|
|
||||||
claim.budget_id = form['budget_id']
|
|
||||||
|
|
||||||
claim.comments = form['comments']
|
|
||||||
claim.state = models.ClaimState.DRAFT.value
|
|
||||||
claim.items = json.loads(form['items'])
|
|
||||||
|
|
||||||
claim.save()
|
|
||||||
|
|
||||||
return claim
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def claim_new(request):
|
def claim_new(request):
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
@ -412,7 +391,6 @@ def claim_new(request):
|
|||||||
claim = models.ReimbursementClaim()
|
claim = models.ReimbursementClaim()
|
||||||
claim.author = request.user
|
claim.author = request.user
|
||||||
claim.time = timezone.now()
|
claim.time = timezone.now()
|
||||||
#revision.action = models.BudgetAction.CREATE.value
|
|
||||||
claim.state = models.BudgetState.DRAFT.value
|
claim.state = models.BudgetState.DRAFT.value
|
||||||
claim = claim_from_form(claim, request.POST)
|
claim = claim_from_form(claim, request.POST)
|
||||||
|
|
||||||
@ -437,38 +415,6 @@ def claim_new(request):
|
|||||||
'claim': claim
|
'claim': claim
|
||||||
})
|
})
|
||||||
|
|
||||||
def uses_claim(viewfunc):
|
|
||||||
@functools.wraps(viewfunc)
|
|
||||||
def func(request, id):
|
|
||||||
claim = models.ReimbursementClaim.objects.get(id=id)
|
|
||||||
return viewfunc(request, claim)
|
|
||||||
return func
|
|
||||||
|
|
||||||
def claim_viewable(viewfunc):
|
|
||||||
@functools.wraps(viewfunc)
|
|
||||||
def func(request, claim):
|
|
||||||
if not claim.can_view(request.user):
|
|
||||||
raise PermissionDenied
|
|
||||||
|
|
||||||
return viewfunc(request, claim)
|
|
||||||
return func
|
|
||||||
|
|
||||||
def claim_editable(viewfunc):
|
|
||||||
@functools.wraps(viewfunc)
|
|
||||||
def func(request, claim):
|
|
||||||
if claim.state == models.ClaimState.PAID.value:
|
|
||||||
raise PermissionDenied
|
|
||||||
|
|
||||||
if claim.state != models.ClaimState.DRAFT.value and claim.state != models.ClaimState.RESUBMIT.value:
|
|
||||||
if not request.user.groups.filter(name='Treasury').exists():
|
|
||||||
raise PermissionDenied
|
|
||||||
|
|
||||||
if not claim.can_view(request.user):
|
|
||||||
raise PermissionDenied
|
|
||||||
|
|
||||||
return viewfunc(request, claim)
|
|
||||||
return func
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@uses_claim
|
@uses_claim
|
||||||
@claim_viewable
|
@claim_viewable
|
||||||
@ -541,20 +487,11 @@ def claim_action(request, claim):
|
|||||||
emailer.send_mail([comment.author], 'New comment on reimbursement claim: {}'.format(revision.name), 'sstreasury/email/claim_commented.md', {'claim': claim, 'comment': comment})
|
emailer.send_mail([comment.author], 'New comment on reimbursement claim: {}'.format(revision.name), 'sstreasury/email/claim_commented.md', {'claim': claim, 'comment': comment})
|
||||||
|
|
||||||
if 'Submit' in actions:
|
if 'Submit' in actions:
|
||||||
if claim.state != models.ClaimState.DRAFT.value and claim.state != models.ClaimState.RESUBMIT.value:
|
if not claim.can_submit(request.user):
|
||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
|
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
claim.state = models.ClaimState.AWAIT_REVIEW.value
|
claim.update_state(request.user, models.ClaimState.AWAIT_REVIEW)
|
||||||
claim.save()
|
|
||||||
|
|
||||||
claim_history = models.ClaimHistory()
|
|
||||||
claim_history.claim = claim
|
|
||||||
claim_history.author = request.user
|
|
||||||
claim_history.state = claim.state
|
|
||||||
claim_history.time = timezone.now()
|
|
||||||
claim_history.action = models.ClaimAction.UPDATE_STATE.value
|
|
||||||
claim_history.save()
|
|
||||||
|
|
||||||
emailer = Emailer()
|
emailer = Emailer()
|
||||||
for user in User.objects.filter(groups__name='Treasury'):
|
for user in User.objects.filter(groups__name='Treasury'):
|
||||||
@ -562,58 +499,28 @@ def claim_action(request, claim):
|
|||||||
emailer.send_mail([claim.author.email], 'Reimbursement claim submitted: {}'.format(claim.purpose), 'sstreasury/email/claim_submitted_drafter.md', {'claim': claim})
|
emailer.send_mail([claim.author.email], 'Reimbursement claim submitted: {}'.format(claim.purpose), 'sstreasury/email/claim_submitted_drafter.md', {'claim': claim})
|
||||||
|
|
||||||
if 'Withdraw' in actions:
|
if 'Withdraw' in actions:
|
||||||
if claim.state == models.ClaimState.DRAFT.value or claim.state == models.ClaimState.RESUBMIT.value or claim.state == models.ClaimState.PAID.value:
|
if not claim.can_withdraw(request.user):
|
||||||
raise PermissionDenied
|
|
||||||
|
|
||||||
claim.state = models.ClaimState.DRAFT.value
|
|
||||||
claim.save()
|
|
||||||
|
|
||||||
claim_history = models.ClaimHistory()
|
|
||||||
claim_history.claim = claim
|
|
||||||
claim_history.author = request.user
|
|
||||||
claim_history.state = claim.state
|
|
||||||
claim_history.time = timezone.now()
|
|
||||||
claim_history.action = models.ClaimAction.UPDATE_STATE.value
|
|
||||||
claim_history.save()
|
|
||||||
|
|
||||||
if 'Approve' in actions:
|
|
||||||
if claim.state != models.ClaimState.AWAIT_REVIEW.value:
|
|
||||||
raise PermissionDenied
|
|
||||||
if not request.user.groups.filter(name='Treasury').exists():
|
|
||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
|
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
claim.state = models.ClaimState.APPROVED.value
|
claim.update_state(request.user, models.ClaimState.DRAFT)
|
||||||
claim.save()
|
|
||||||
|
if 'Approve' in actions:
|
||||||
claim_history = models.ClaimHistory()
|
if not claim.can_approve(request.user):
|
||||||
claim_history.claim = claim
|
raise PermissionDenied
|
||||||
claim_history.author = request.user
|
|
||||||
claim_history.state = claim.state
|
with transaction.atomic():
|
||||||
claim_history.time = timezone.now()
|
claim.update_state(request.user, models.ClaimState.APPROVED)
|
||||||
claim_history.action = models.ClaimAction.UPDATE_STATE.value
|
|
||||||
claim_history.save()
|
|
||||||
|
|
||||||
emailer = Emailer()
|
emailer = Emailer()
|
||||||
emailer.send_mail([claim.author.email], 'Claim approved, awaiting payment: {}'.format(claim.purpose), 'sstreasury/email/claim_approved.md', {'claim': claim})
|
emailer.send_mail([claim.author.email], 'Claim approved, awaiting payment: {}'.format(claim.purpose), 'sstreasury/email/claim_approved.md', {'claim': claim})
|
||||||
|
|
||||||
if 'Return' in actions:
|
if 'Return' in actions:
|
||||||
if claim.state != models.ClaimState.AWAIT_REVIEW.value:
|
if not claim.can_approve(request.user):
|
||||||
raise PermissionDenied
|
|
||||||
if not request.user.groups.filter(name='Treasury').exists():
|
|
||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
|
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
claim.state = models.ClaimState.RESUBMIT.value
|
claim.update_state(request.user, models.ClaimState.RESUBMIT)
|
||||||
claim.save()
|
|
||||||
|
|
||||||
claim_history = models.ClaimHistory()
|
|
||||||
claim_history.claim = claim
|
|
||||||
claim_history.author = request.user
|
|
||||||
claim_history.state = claim.state
|
|
||||||
claim_history.time = timezone.now()
|
|
||||||
claim_history.action = models.ClaimAction.UPDATE_STATE.value
|
|
||||||
claim_history.save()
|
|
||||||
|
|
||||||
emailer = Emailer()
|
emailer = Emailer()
|
||||||
emailer.send_mail([claim.author.email], 'Action required: Reimbursement claim returned for re-drafting: {}'.format(claim.purpose), 'sstreasury/email/claim_returned.md', {'claim': claim})
|
emailer.send_mail([claim.author.email], 'Action required: Reimbursement claim returned for re-drafting: {}'.format(claim.purpose), 'sstreasury/email/claim_returned.md', {'claim': claim})
|
||||||
|
Loading…
Reference in New Issue
Block a user