diff --git a/sstreasury/jinja2/sstreasury/budget_view.html b/sstreasury/jinja2/sstreasury/budget_view.html index d4d37ea..432a11c 100644 --- a/sstreasury/jinja2/sstreasury/budget_view.html +++ b/sstreasury/jinja2/sstreasury/budget_view.html @@ -43,6 +43,9 @@ {% if revision.can_withdraw(request.user) %} {% endif %} + {% if revision.can_cancel(request.user) %} + + {% endif %} {% if revision.can_edit(request.user) %} Edit @@ -290,6 +293,9 @@

Are you sure you want to withdraw this budget from being considered for approval? The budget will be reverted to a draft.

+
+

Are you sure you want to cancel this budget?

+
Continue
Cancel
diff --git a/sstreasury/models.py b/sstreasury/models.py index 87c82e5..6e2342b 100644 --- a/sstreasury/models.py +++ b/sstreasury/models.py @@ -44,11 +44,12 @@ class BudgetComment(models.Model): class BudgetState(DescriptionEnum): DRAFT = 10, 'Draft' + WITHDRAWN = 15, 'Withdrawn' RESUBMIT = 20, 'Returned for redrafting' AWAIT_REVIEW = 30, 'Awaiting Treasury review' ENDORSED = 40, 'Endorsed by Treasury, awaiting committee approval' APPROVED = 50, 'Approved' - #CANCELLED = 60, 'Cancelled' + CANCELLED = 60, 'Cancelled' class BudgetAction(DescriptionEnum): CREATE = 5, 'Created' @@ -121,7 +122,7 @@ class BudgetRevision(models.Model): return True if user.groups.filter(name='Treasury').exists(): return True - if (self.state == BudgetState.AWAIT_REVIEW.value or self.state == BudgetState.ENDORSED.value or self.state == BudgetState.APPROVED.value) and user.groups.filter(name='Committee').exists(): + if self.state in (BudgetState.AWAIT_REVIEW.value, BudgetState.ENDORSED.value, BudgetState.APPROVED.value) and user.groups.filter(name='Committee').exists(): return True return False @@ -136,7 +137,7 @@ class BudgetRevision(models.Model): return False # Only Treasurer or Secretary may edit if submitted - if self.state != BudgetState.DRAFT.value and self.state != BudgetState.RESUBMIT.value: + if self.state not in (BudgetState.DRAFT.value, BudgetState.RESUBMIT.value, BudgetState.WITHDRAWN.value): if user.groups.filter(name='Treasury').exists() or user.groups.filter(name='Secretary').exists(): return True return False @@ -155,7 +156,7 @@ class BudgetRevision(models.Model): 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: + if self.state in (BudgetState.DRAFT.value, BudgetState.RESUBMIT.value, BudgetState.WITHDRAWN.value): return True return False @@ -165,7 +166,7 @@ class BudgetRevision(models.Model): if user != self.author and user not in self.contributors.all() and not user.groups.filter(name='Treasury').exists(): return False - if self.state == BudgetState.AWAIT_REVIEW.value or self.state == BudgetState.ENDORSED.value: + if self.state in (BudgetState.DRAFT.value, BudgetState.RESUBMIT.value, BudgetState.AWAIT_REVIEW.value, BudgetState.ENDORSED.value): return True return False @@ -174,7 +175,7 @@ class BudgetRevision(models.Model): return False if not user.groups.filter(name='Treasury').exists(): return False - if self.state == BudgetState.AWAIT_REVIEW.value or self.state == BudgetState.DRAFT.value or self.state == BudgetState.RESUBMIT.value: + if self.state in (BudgetState.AWAIT_REVIEW.value, BudgetState.DRAFT.value, BudgetState.RESUBMIT.value, BudgetState.WITHDRAWN.value): return True return False @@ -196,6 +197,15 @@ class BudgetRevision(models.Model): if self.state == BudgetState.APPROVED.value: return False return self.can_approve(user) + + def can_cancel(self, user): + if not self.can_view(user): + return False + if not user.groups.filter(name='Secretary').exists() and not user.groups.filter(name='Treasury').exists(): + return False + if self.state != BudgetState.APPROVED.value: + return False + return True class ClaimState(DescriptionEnum): DRAFT = 10, 'Draft' diff --git a/sstreasury/views.py b/sstreasury/views.py index 4cedc83..67b5a7d 100644 --- a/sstreasury/views.py +++ b/sstreasury/views.py @@ -211,6 +211,9 @@ def budget_list(request): revision = budget.budgetrevision_set.reverse()[0] state = models.BudgetState(revision.state) + if not revision.can_view(request.user): + continue + group = None if request.user.groups.filter(name='Treasury').exists() and state == models.BudgetState.AWAIT_REVIEW: @@ -226,8 +229,8 @@ def budget_list(request): group = budgets_open else: group = budgets_closed - elif request.user.groups.filter(name='Treasury').exists() or request.user.groups.filter(name='Secretary').exists() or request.user.groups.filter(name='Committee').exists(): - if state == models.BudgetState.APPROVED: + else: + if state in (models.BudgetState.APPROVED, models.BudgetState.WITHDRAWN, models.BudgetState.CANCELLED): group = budgets_closed else: group = budgets_open @@ -390,7 +393,7 @@ def budget_action(request, budget, revision): raise PermissionDenied with transaction.atomic(): - revision.update_state(request.user, models.BudgetState.DRAFT) + revision.update_state(request.user, models.BudgetState.WITHDRAWN) if 'Endorse' in actions: if not revision.can_endorse(request.user): @@ -438,6 +441,13 @@ def budget_action(request, budget, revision): for user in revision.contributors.all(): emailer.send_mail([user.email], 'Action required: Budget returned for re-drafting: {} (BU-{})'.format(revision.name, budget.id), 'sstreasury/email/budget_returned_committee.md', {'revision': revision}) + if 'Cancel' in actions: + if not revision.can_cancel(request.user): + raise PermissionDenied + + with transaction.atomic(): + revision.update_state(request.user, models.BudgetState.CANCELLED) + return redirect(reverse('budget_view', kwargs={'id': budget.id})) @login_required