SciPost Code Repository

Skip to content
Snippets Groups Projects
Commit 589991ac authored by Jean-Sébastien Caux's avatar Jean-Sébastien Caux
Browse files

Add CRUD facilities for SubsidyPayment

parent 6d6b2a50
No related branches found
No related tags found
No related merge requests found
......@@ -6,6 +6,7 @@ from django.contrib import admin
from .models import (
Subsidy,
SubsidyPayment,
SubsidyAttachment,
WorkLog,
PeriodicReportType,
......@@ -13,12 +14,16 @@ from .models import (
)
class SubsidyPaymentInline(admin.TabularInline):
model = SubsidyPayment
class SubsidyAttachmentInline(admin.TabularInline):
model = SubsidyAttachment
class SubsidyAdmin(admin.ModelAdmin):
inlines = [
SubsidyPaymentInline,
SubsidyAttachmentInline,
]
autocomplete_fields = [
......
......@@ -11,7 +11,7 @@ from django.db.models import Q, Sum
from django.utils import timezone
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Div, Field
from crispy_forms.layout import Layout, Div, Field, ButtonHolder, Submit
from crispy_forms.bootstrap import InlineRadios
from crispy_bootstrap5.bootstrap5 import FloatingField
......@@ -120,6 +120,32 @@ class SubsidyPaymentForm(forms.ModelForm):
"proof_of_payment",
)
def __init__(self, *args, **kwargs):
subsidy = kwargs.pop("subsidy")
super().__init__(*args, **kwargs)
self.fields["subsidy"].initial = subsidy
self.fields["subsidy"].widget = forms.HiddenInput()
self.fields["invoice"].queryset = subsidy.attachments.invoices()
self.fields["proof_of_payment"].queryset =\
subsidy.attachments.proofs_of_payment()
self.helper = FormHelper()
self.helper.layout = Layout(
Field("subsidy"),
Div(
Div(FloatingField("reference"), css_class="col-lg-5"),
Div(FloatingField("amount"), css_class="col-lg-3"),
Div(Field("date_scheduled"), css_class="col-lg-4"),
css_class="row",
),
Div(
Div(Field("invoice"), css_class="col-lg-6"),
Div(Field("proof_of_payment"), css_class="col-lg-6"),
css_class="row",
),
ButtonHolder(Submit("submit", "Submit")),
)
class SubsidyAttachmentForm(forms.ModelForm):
class Meta:
......
......@@ -27,5 +27,8 @@ class SubsidyAttachmentQuerySet(models.QuerySet):
def agreements(self):
return self.filter(kind=self.model.KIND_AGREEMENT)
def invoices(self):
return self.filter(kind=self.model.KIND_INVOICE)
def proofs_of_payment(self):
return self.filter(kind=self.model.KIND_PROOF_OF_PAYMENT)
......@@ -8,6 +8,7 @@ from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericForeignKey
from django.db import models
from django.db.models import Sum
from django.urls import reverse
from django.utils import timezone
from django.utils.html import format_html
......@@ -127,7 +128,7 @@ class Subsidy(models.Model):
"""
Verify that there exist SubsidyPayment objects covering full amount.
"""
return self.amount == self.payments.annotate(sum=Sum('amount'))["sum"]
return self.amount == self.payments.aggregate(Sum("amount"))["amount__sum"]
class SubsidyPayment(models.Model):
......
<details id="subsidy-{{ subsidy.id }}-finadmin-details"
class="bg-danger bg-opacity-10"
>
<summary class="bg-danger bg-opacity-10 p-2">Financial Administration</summary>
<details class="bg-danger bg-opacity-10">
<summary class="bg-danger bg-opacity-10 p-2">
Financial Administration
{% if subsidy.payments_all_scheduled %}
<span class="text-success bg-white ms-4 p-1">[all payments are scheduled]</span>
{% else %}
<span class="text-warning bg-white ms-4 p-1">[to be completed]</span>
{% endif %}
</summary>
<div class="p-2">
<h3>Payment schedule</h3>
<table class="table table-bordered">
......@@ -10,10 +15,9 @@
<th>Reference</th>
<th>Amount</th>
<th>Date&nbsp;scheduled</th>
<th>Invoiced?</th>
<th>Invoice</th>
<th>Paid?</th>
<th>Proof&nbsp;of&nbsp;payment</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
......@@ -22,15 +26,41 @@
<td>{{ payment.reference }}</td>
<td>{{ payment.amount }}</td>
<td>{{ payment.date_scheduled }}</td>
<td>{{ payment.invoiced }}</td>
<td>{{ payment.invoice }}</td>
<td>{{ paymend.paid }}</td>
<td>{{ payment.proof_of_payment }}</td>
<td>
<a class="btn btn-sm btn-warning"
hx-get="{% url 'finances:_hx_subsidypayment_form' subsidy_id=subsidy.id subsidypayment_id=payment.id %}"
hx-target="#subsidy-{{ subsidy.id }}-payment-{{ payment.id }}-update-form"
>
Update
</a>
<a class="btn btn-sm btn-danger ms-2 p-1"
hx-get="{% url 'finances:_hx_subsidypayment_delete' subsidy_id=subsidy.id subsidypayment_id=payment.id %}"
hx-target="#subsidy-{{ subsidy.id }}-finadmin-details"
hx-confirm="Are you sure you want to delete this payment information?"
>
{% include "bi/trash-fill.html" %}
</a>
</td>
</tr>
<tr>
<td colspan="6"
id="subsidy-{{ subsidy.id }}-payment-{{ payment.id }}-update-form"
>
</td>
{% empty %}
<tr><td colspan="5">No payment has been scheduled</td></tr>
{% endfor %}
</tbody>
</table>
<div id="subsidy-{{ subsidy.id }}-new-payment-form">
<a class="btn btn-sm btn-primary"
hx-get="{% url 'finances:_hx_subsidypayment_form' subsidy_id=subsidy.id %}"
hx-target="#subsidy-{{ subsidy.id }}-new-payment-form"
>
Add a new payment
</a>
</div>
</div>
</details>
......@@ -37,10 +37,12 @@
{% if "finadmin" in user_roles %}
<tr>
<td colspan="5" class="ps-4 pb-4">
<div hx-get="{% url 'finances:_hx_subsidy_finadmin_details' subsidy_id=subsidy.id %}"
hx-trigger="revealed"
hx-swap="outerHTML"
>
<div id="subsidy-{{ subsidy.id }}-finadmin-details">
<div hx-get="{% url 'finances:_hx_subsidy_finadmin_details' subsidy_id=subsidy.id %}"
hx-trigger="revealed"
hx-target="#subsidy-{{ subsidy.id }}-finadmin-details"
>
</div>
</div>
</td>
</tr>
......
{% load crispy_forms_tags %}
{% if form.instance.id %}
<form hx-post="{% url 'finances:_hx_subsidypayment_form' subsidy_id=subsidy.id subsidypayment_id=form.instance.id %}"
hx-target="#subsidy-{{ subsidy.id }}-new-payment-form"
>
{% crispy form %}
</form>
{% else %}
<form hx-post="{% url 'finances:_hx_subsidypayment_form' subsidy_id=subsidy.id %}"
hx-target="#subsidy-{{ subsidy.id }}-new-payment-form"
>
{% crispy form %}
</form>
{% endif %}
......@@ -37,14 +37,44 @@ urlpatterns = [
name="_hx_subsidy_list",
),
path(
"_hx_subsidy_finadmin_details/<int:subsidy_id>",
views._hx_subsidy_finadmin_details,
name="_hx_subsidy_finadmin_details",
"<int:subsidy_id>/",
include([
path(
"_hx_subsidy_finadmin_details",
views._hx_subsidy_finadmin_details,
name="_hx_subsidy_finadmin_details",
),
path(
"payment/",
include([
path(
"form",
views._hx_subsidypayment_form,
name="_hx_subsidypayment_form",
),
path(
"<int:subsidypayment_id>",
include([
path(
"",
views._hx_subsidypayment_form,
name="_hx_subsidypayment_form",
),
path(
"delete",
views._hx_subsidypayment_delete,
name="_hx_subsidypayment_delete",
),
]),
),
]),
),
]),
),
]),
),
path("subsidies/", views.subsidy_list, name="subsidies"),
path("subsidies/", views.SubsidyListView.as_view(), name="subsidies"),
path("subsidies/", views.SubsidyListView.as_view(), name="subsidies"), #deprecated
path("subsidies/add/", views.SubsidyCreateView.as_view(), name="subsidy_create"),
path(
"subsidies/<int:pk>/update/",
......
......@@ -24,8 +24,14 @@ from django.views.generic.detail import DetailView
from django.views.generic.edit import CreateView, UpdateView, DeleteView
from django.views.generic.list import ListView
from .forms import SubsidyForm, SubsidySearchForm, SubsidyAttachmentForm, LogsFilterForm
from .models import Subsidy, SubsidyAttachment, WorkLog, PeriodicReport
from .forms import (
SubsidyForm,
SubsidySearchForm,
SubsidyPaymentForm,
SubsidyAttachmentForm,
LogsFilterForm,
)
from .models import Subsidy, SubsidyPayment, SubsidyAttachment, WorkLog, PeriodicReport
from .utils import slug_to_id
from comments.constants import EXTENTIONS_IMAGES, EXTENTIONS_PDF
......@@ -350,6 +356,44 @@ def _hx_subsidy_finadmin_details(request, subsidy_id: int):
return render(request, "finances/_hx_subsidy_finadmin_details.html", context)
def _hx_subsidypayment_form(request, subsidy_id: int, subsidypayment_id: int=None):
subsidy = get_object_or_404(Subsidy, pk=subsidy_id)
if subsidypayment_id:
instance = get_object_or_404(SubsidyPayment, pk=subsidypayment_id)
else:
instance=None
form = SubsidyPaymentForm(
request.POST or None,
subsidy=subsidy,
instance=instance,
)
if form.is_valid():
form.save()
response = render(
request,
"finances/_hx_subsidy_finadmin_details.html",
context={"subsidy": subsidy,},
)
response["HX-Retarget"] = f"#subsidy-{subsidy.id}-finadmin-details"
return response
context = {
"subsidy": subsidy,
"form": form,
}
return render(request, "finances/_hx_subsidypayment_form.html", context)
def _hx_subsidypayment_delete(request, subsidy_id: int, subsidypayment_id: int):
subsidy = get_object_or_404(Subsidy, pk=subsidy_id)
SubsidyPayment.objects.filter(pk=subsidypayment_id).delete()
response = render(
request,
"finances/_hx_subsidy_finadmin_details.html",
context={"subsidy": subsidy,},
)
return response
def subsidy_toggle_amount_public_visibility(request, subsidy_id):
"""
Method to toggle the public visibility of the amount of a Subsidy.
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment