From 5d0aa18ddef32214cf4b53728e3154ddae561a39 Mon Sep 17 00:00:00 2001
From: Christian Merten
Date: Sat, 18 Jan 2025 22:48:25 +0100
Subject: [PATCH] finance: adapt to allowance_to and subsidy_to
Use the new `allowance_to` and `subsidy_to` fields in statement validation. The
night and travel costs have to be transferred to the person listed as `subsidy_to`,
the allowance has to be paid to the persons listed in `allowance_to`.
---
jdav_web/finance/admin.py | 8 +++
.../finance/locale/de/LC_MESSAGES/django.po | 47 +++++++++++++--
jdav_web/finance/models.py | 58 ++++++++++++++++---
.../admin/overview_submitted_statement.html | 19 ++++++
4 files changed, 118 insertions(+), 14 deletions(-)
diff --git a/jdav_web/finance/admin.py b/jdav_web/finance/admin.py
index 463e552..d9fae6f 100644
--- a/jdav_web/finance/admin.py
+++ b/jdav_web/finance/admin.py
@@ -206,6 +206,14 @@ class StatementSubmittedAdmin(admin.ModelAdmin):
_("Some transactions have no ledger configured. Please fill in the gaps.")
% {'name': str(statement)})
return HttpResponseRedirect(reverse('admin:%s_%s_overview' % (self.opts.app_label, self.opts.model_name), args=(statement.pk,)))
+ elif res == Statement.INVALID_ALLOWANCE_TO:
+ messages.error(request,
+ _("The configured recipients for the allowance don't match the regulations. Please correct this on the excursion."))
+ return HttpResponseRedirect(reverse('admin:%s_%s_overview' % (self.opts.app_label, self.opts.model_name), args=(statement.pk,)))
+ elif res == Statement.INVALID_TOTAL:
+ messages.error(request,
+ _("The calculated total amount does not match the sum of all transactions. This is most likely a bug."))
+ return HttpResponseRedirect(reverse('admin:%s_%s_overview' % (self.opts.app_label, self.opts.model_name), args=(statement.pk,)))
return HttpResponseRedirect(reverse('admin:%s_%s_changelist' % (self.opts.app_label, self.opts.model_name)))
if "reject" in request.POST:
diff --git a/jdav_web/finance/locale/de/LC_MESSAGES/django.po b/jdav_web/finance/locale/de/LC_MESSAGES/django.po
index 8738751..3c6ffaf 100644
--- a/jdav_web/finance/locale/de/LC_MESSAGES/django.po
+++ b/jdav_web/finance/locale/de/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2025-01-18 20:19+0100\n"
+"POT-Creation-Date: 2025-01-18 22:42+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
@@ -74,6 +74,22 @@ msgid "Some transactions have no ledger configured. Please fill in the gaps."
msgstr ""
"Manche Überweisungen haben kein Geldtopf eingestellt. Bitte trage das nach."
+#: finance/admin.py
+msgid ""
+"The configured recipients for the allowance don't match the regulations. "
+"Please correct this on the excursion."
+msgstr ""
+"Die ausgewählten Empfänger*innen der Aufwandsentschädigungen entsprechen "
+"nicht den Regularien. Bitte korrigiere das in der Ausfahrt."
+
+#: finance/admin.py
+msgid ""
+"The calculated total amount does not match the sum of all transactions. This "
+"is most likely a bug."
+msgstr ""
+"Der berechnete Gesamtbetrag stimmt nicht mit der Summe aller Überweisungen "
+"überein. Das ist höchstwahrscheinlich ein Fehler in der Implementierung."
+
#: finance/admin.py
#, python-format
msgid "Successfully rejected %(name)s. The requestor can reapply, when needed."
@@ -233,8 +249,13 @@ msgstr "Bereit zur Abwicklung"
#: finance/models.py
#, python-format
-msgid "Compensation for %(excu)s"
-msgstr "Entschädigung für %(excu)s"
+msgid "Allowance for %(excu)s"
+msgstr "Aufwandsentschädigung für %(excu)s"
+
+#: finance/models.py
+#, python-format
+msgid "Night and travel costs for %(excu)s"
+msgstr "Übernachtungs- und Fahrtkosten für %(excu)s"
#: finance/models.py finance/templates/admin/overview_submitted_statement.html
msgid "Total"
@@ -386,8 +407,8 @@ msgstr "Ausfahrt"
#, python-format
msgid "This excursion featured %(staff_count)s youth leader(s), each costing"
msgstr ""
-"Diese Ausfahrt hatte %(staff_count)s Jugendleiter*innen. Auf jede*n "
-"entfallen die folgenden Kosten:"
+"Diese Ausfahrt hatte %(staff_count)s genehmigte Jugendleiter*innen. Auf "
+"jede*n entfallen die folgenden Kosten:"
#: finance/templates/admin/overview_submitted_statement.html
#, python-format
@@ -425,6 +446,22 @@ msgstr ""
"Insgesamt sind das Kosten von %(total_per_yl)s€ mal %(staff_count)s, "
"insgesamt also %(total_staff)s€."
+#: finance/templates/admin/overview_submitted_statement.html
+#, python-format
+msgid "The allowance of %(allowance_per_yl)s€ per person should be paid to:"
+msgstr ""
+"Die Aufwandsentschädigung von %(allowance_per_yl)s€ pro Person soll "
+"ausgezahlt werden an:"
+
+#: finance/templates/admin/overview_submitted_statement.html
+#, python-format
+msgid ""
+"The subsidies for night and transportation costs of %(total_subsidies)s€ "
+"should be paid to:"
+msgstr ""
+"Die Zuschüsse für Übernachtungs- und Fahrtkosten von %(total_subsidies)s€ "
+"sollen ausgezahlt werden an:"
+
#: finance/templates/admin/overview_submitted_statement.html
#, python-format
msgid "This results in a total amount of %(total)s€"
diff --git a/jdav_web/finance/models.py b/jdav_web/finance/models.py
index ed437e7..81e62b0 100644
--- a/jdav_web/finance/models.py
+++ b/jdav_web/finance/models.py
@@ -47,7 +47,7 @@ class StatementManager(models.Manager):
class Statement(CommonModel):
- MISSING_LEDGER, NON_MATCHING_TRANSACTIONS, VALID = 0, 1, 2
+ MISSING_LEDGER, NON_MATCHING_TRANSACTIONS, INVALID_ALLOWANCE_TO, INVALID_TOTAL, VALID = 0, 1, 2, 3, 4
short_description = models.CharField(verbose_name=_('Short description'),
max_length=30,
@@ -123,7 +123,9 @@ class Statement(CommonModel):
needed_paiments = [(b.paid_by, b.amount) for b in self.bill_set.all() if b.costs_covered and b.paid_by]
if self.excursion is not None:
- needed_paiments.extend([(yl, self.real_per_yl) for yl in self.excursion.jugendleiter.all()])
+ needed_paiments.extend([(yl, self.allowance_per_yl) for yl in self.allowance_to.all()])
+ if self.subsidy_to:
+ needed_paiments.append((self.subsidy_to, self.total_subsidies))
needed_paiments = sorted(needed_paiments, key=lambda p: p[0].pk)
target = dict(map(lambda p: (p[0], sum([x[1] for x in p[1]])), groupby(needed_paiments, lambda p: p[0])))
@@ -157,10 +159,25 @@ class Statement(CommonModel):
def transactions_match_expenses(self):
return len(self.transaction_issues) == 0
- def is_valid(self):
- return self.ledgers_configured and self.transactions_match_expenses
- is_valid.boolean = True
- is_valid.short_description = _('Ready to confirm')
+ @property
+ def allowance_to_valid(self):
+ """Checks if the configured `allowance_to` field matches the regulations."""
+ if self.allowance_to.count() != self.real_staff_count:
+ return False
+ if self.excursion is not None:
+ yls = self.excursion.jugendleiter.all()
+ for yl in self.allowance_to.all():
+ if yl not in yls:
+ return False
+ return True
+
+ @property
+ def total_valid(self):
+ """Checks if the calculated total agrees with the total amount of all transactions."""
+ total_transactions = 0
+ for transaction in self.transaction_set.all():
+ total_transactions += transaction.amount
+ return self.total == total_transactions
@property
def validity(self):
@@ -168,9 +185,18 @@ class Statement(CommonModel):
return Statement.NON_MATCHING_TRANSACTIONS
if not self.ledgers_configured:
return Statement.MISSING_LEDGER
+ if not self.allowance_to_valid:
+ return Statement.INVALID_ALLOWANCE_TO
+ if not self.total_valid:
+ return Statement.INVALID_TOTAL
else:
return Statement.VALID
+ def is_valid(self):
+ return self.validity == Statement.VALID
+ is_valid.boolean = True
+ is_valid.short_description = _('Ready to confirm')
+
def confirm(self, confirmer=None):
if not self.submitted:
return False
@@ -203,9 +229,14 @@ class Statement(CommonModel):
if self.excursion is None:
return True
- for yl in self.excursion.jugendleiter.all():
- ref = _("Compensation for %(excu)s") % {'excu': self.excursion.name}
- Transaction(statement=self, member=yl, amount=self.real_per_yl, confirmed=False, reference=ref).save()
+ # allowance
+ for yl in self.allowance_to.all():
+ ref = _("Allowance for %(excu)s") % {'excu': self.excursion.name}
+ Transaction(statement=self, member=yl, amount=self.allowance_per_yl, confirmed=False, reference=ref).save()
+
+ # subsidies (i.e. night and transportation costs)
+ ref = _("Night and travel costs for %(excu)s") % {'excu': self.excursion.name}
+ Transaction(statement=self, member=self.subsidy_to, amount=self.total_subsidies, confirmed=False, reference=ref).save()
return True
def reduce_transactions(self):
@@ -300,6 +331,14 @@ class Statement(CommonModel):
return cvt_to_decimal(self.total_staff / self.excursion.staff_count)
+ @property
+ def total_subsidies(self):
+ """
+ The total amount of subsidies excluding the allowance, i.e. the transportation
+ and night costs per youth leader multiplied with the real number of youth leaders.
+ """
+ return (self.transportation_per_yl + self.nights_per_yl) * self.real_staff_count
+
@property
def total_staff(self):
return self.total_per_yl * self.real_staff_count
@@ -353,6 +392,7 @@ class Statement(CommonModel):
'transportation_per_yl': self.transportation_per_yl,
'total_per_yl': self.total_per_yl,
'total_staff': self.total_staff,
+ 'total_subsidies': self.total_subsidies,
}
return dict(context, **excursion_context)
else:
diff --git a/jdav_web/finance/templates/admin/overview_submitted_statement.html b/jdav_web/finance/templates/admin/overview_submitted_statement.html
index 4fd11f8..4a33ae6 100644
--- a/jdav_web/finance/templates/admin/overview_submitted_statement.html
+++ b/jdav_web/finance/templates/admin/overview_submitted_statement.html
@@ -73,6 +73,25 @@
{% blocktrans %}In total this is {{ total_per_yl }}€ times {{ staff_count }}, giving {{ total_staff }}€.{% endblocktrans %}
+
+{% blocktrans %}The allowance of {{ allowance_per_yl }}€ per person should be paid to:{% endblocktrans %}
+
+ {% for member in statement.allowance_to.all %}
+ -
+ {{ member.name }}
+
+ {% endfor %}
+
+
+
+{% blocktrans %}The subsidies for night and transportation costs of {{ total_subsidies }}€ should be paid to:{% endblocktrans %}
+
+ -
+ {{ statement.subsidy_to.name }}
+
+
+
+
{% endif %}
{% trans "Total" %}