finance/statement: show IBAN validity in submit views (#104)

* Add new attribute `iban_valid` to `Member`.
* Submit pages of regular expenses and activity expenses now include an overview which people with expenses have a valid IBAN.
* Add a note that IBAN validity should be checked manually before submitting.

Right now, no technical barriers are in place that prevent missing/wrong IBANs when a statement is submitted. This is intentional to preserve maximal flexibility.

In a follow-up PR, one could add custom warning messages on top of page when some IBANs are invalid.

Reviewed-on: #104
Reviewed-by: Christian Merten <christian@merten.dev>
Co-authored-by: marius.klein <marius.klein@alpenverein-heidelberg.de>
Co-committed-by: marius.klein <marius.klein@alpenverein-heidelberg.de>
toml-configuration-with-templates
marius.klein 11 months ago committed by Christian Merten
parent a2cbae2c8e
commit e7dcbb47ab

@ -89,11 +89,24 @@ class StatementUnSubmittedAdmin(CommonAdminMixin, admin.ModelAdmin):
messages.success(request, messages.success(request,
_("Successfully submited %(name)s. The finance department will notify the requestors as soon as possible.") % {'name': str(statement)}) _("Successfully submited %(name)s. The finance department will notify the requestors as soon as possible.") % {'name': str(statement)})
return HttpResponseRedirect(reverse('admin:%s_%s_changelist' % (self.opts.app_label, self.opts.model_name))) return HttpResponseRedirect(reverse('admin:%s_%s_changelist' % (self.opts.app_label, self.opts.model_name)))
if statement.excursion:
memberlist = statement.excursion
context = dict(self.admin_site.each_context(request),
title=_('Finance overview'),
opts=self.opts,
memberlist=memberlist,
object=memberlist,
participant_count=memberlist.participant_count,
ljp_contributions=memberlist.potential_ljp_contributions,
total_relative_costs=memberlist.total_relative_costs,
**memberlist.statement.template_context())
return render(request, 'admin/freizeit_finance_overview.html', context=context)
else:
context = dict(self.admin_site.each_context(request), context = dict(self.admin_site.each_context(request),
title=_('Submit statement'), title=_('Submit statement'),
opts=self.opts, opts=self.opts,
statement=statement) statement=statement)
return render(request, 'admin/submit_statement.html', context=context) return render(request, 'admin/submit_statement.html', context=context)

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-19 11:40+0100\n" "POT-Creation-Date: 2025-01-19 14:26+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -169,7 +169,7 @@ msgstr "Geldtöpfe"
msgid "Short description" msgid "Short description"
msgstr "Kurzbeschreibung" msgstr "Kurzbeschreibung"
#: finance/models.py #: finance/models.py finance/templates/admin/submit_statement.html
msgid "Explanation" msgid "Explanation"
msgstr "Erklärung" msgstr "Erklärung"
@ -208,7 +208,7 @@ msgstr "Preis pro Nacht"
#: finance/models.py #: finance/models.py
msgid "Submitted" msgid "Submitted"
msgstr "Eingericht" msgstr "Eingereicht"
#: finance/models.py #: finance/models.py
msgid "Submitted on" msgid "Submitted on"
@ -291,10 +291,11 @@ msgstr "Bezahlte Abrechnungen"
#: finance/models.py finance/templates/admin/confirmed_statement.html #: finance/models.py finance/templates/admin/confirmed_statement.html
#: finance/templates/admin/overview_submitted_statement.html #: finance/templates/admin/overview_submitted_statement.html
#: finance/templates/admin/submit_statement.html
msgid "Amount" msgid "Amount"
msgstr "Betrag" msgstr "Betrag"
#: finance/models.py #: finance/models.py finance/templates/admin/submit_statement.html
msgid "Paid by" msgid "Paid by"
msgstr "Bezahlt von" msgstr "Bezahlt von"
@ -533,6 +534,18 @@ msgstr "Einreichen"
msgid "Submit to the finance department" msgid "Submit to the finance department"
msgstr "Beim Finanzreferat einreichen" msgstr "Beim Finanzreferat einreichen"
#: finance/templates/admin/submit_statement.html
msgid ""
"Please check if all expenses are documented correctly and if all payers have "
"a valid account code."
msgstr ""
"Bitte überprüfe, ob alle Ausgaben korrekt erfasst sind und ob alle "
"auslegenden Personen eine gültige IBAN haben."
#: finance/templates/admin/submit_statement.html
msgid "IBAN valid"
msgstr "IBAN gültig"
#: finance/templates/admin/submit_statement.html #: finance/templates/admin/submit_statement.html
msgid "" msgid ""
"Do you want to submit the statement for further processing by the finance " "Do you want to submit the statement for further processing by the finance "

@ -1,5 +1,6 @@
{% extends "admin/base_site.html" %} {% extends "admin/base_site.html" %}
{% load i18n admin_urls static %} {% load i18n admin_urls static %}
{% load overview_extras %}
{% block extrahead %} {% block extrahead %}
{{ block.super }} {{ block.super }}
@ -40,7 +41,7 @@
{{ bill.amount }}€. {{ bill.amount }}€.
</td> </td>
<td> <td>
{{ bill.costs_covered }} {{ bill.costs_covered|render_bool }}
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}

@ -1,5 +1,6 @@
{% extends "admin/base_site.html" %} {% extends "admin/base_site.html" %}
{% load i18n admin_urls static %} {% load i18n admin_urls static %}
{% load overview_extras %}
{% block extrahead %} {% block extrahead %}
{{ block.super }} {{ block.super }}
@ -24,6 +25,35 @@
{% block content %} {% block content %}
<h2>{% translate "Submit to the finance department" %}</h2> <h2>{% translate "Submit to the finance department" %}</h2>
<p>{% translate "Please check if all expenses are documented correctly and if all payers have a valid account code." %}</p>
<table>
<th>
<td>{% trans "Explanation" %}</td>
<td>{% trans "Amount" %}</td>
<td>{% trans "Paid by" %}</td>
<td>{% trans "IBAN valid" %}</td>
</th>
{% for bill in statement.bill_set.all %}
<tr>
<td>
{{bill.short_description}}
</td>
<td>
{{bill.explanation}}
</td>
<td>
{{ bill.amount }}€
</td>
<td>
{{ bill.paid_by.name }}
</td>
<td>
{{ bill.paid_by.iban_valid|render_bool }}
</td>
</tr>
{% endfor %}
</table>
<p> <p>
{% trans "Do you want to submit the statement for further processing by the finance department? If you proceed, no further changes to the statement are possible." %} {% trans "Do you want to submit the statement for further processing by the finance department? If you proceed, no further changes to the statement are possible." %}
</p> </p>

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-19 10:08+0100\n" "POT-Creation-Date: 2025-01-19 19:16+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -411,7 +411,7 @@ msgid ""
"Successfully submited statement. The finance department will notify you as " "Successfully submited statement. The finance department will notify you as "
"soon as possible." "soon as possible."
msgstr "" msgstr ""
"Abrechnung erfolgreich eingericht. Die Finanzabteilung wird sich bei dir so " "Abrechnung erfolgreich eingereicht. Die Finanzabteilung wird sich bei dir so "
"schnell wie möglich melden." "schnell wie möglich melden."
#: members/admin.py members/templates/admin/freizeit_finance_overview.html #: members/admin.py members/templates/admin/freizeit_finance_overview.html
@ -1089,6 +1089,14 @@ msgstr "Erklärung"
msgid "Amount" msgid "Amount"
msgstr "Betrag" msgstr "Betrag"
#: members/templates/admin/freizeit_finance_overview.html
msgid "Paid by"
msgstr "Bezahlt von"
#: members/templates/admin/freizeit_finance_overview.html
msgid "IBAN valid"
msgstr "IBAN gültig"
#: members/templates/admin/freizeit_finance_overview.html #: members/templates/admin/freizeit_finance_overview.html
#, python-format #, python-format
msgid "The total expected expenses are %(total_bills_theoretic)s €." msgid "The total expected expenses are %(total_bills_theoretic)s €."
@ -1229,14 +1237,17 @@ msgstr "Abrechnung einreichen"
msgid "" msgid ""
"Did you already complete this excursion? If yes, please check if all listed " "Did you already complete this excursion? If yes, please check if all listed "
"expenses are correct\n" "expenses are correct\n"
"and then submit the statement for processing by the finance department. If " "and people who want their money back have valid bank account numbers. Then "
"you proceed,\n" "submit the statement for processing by the finance department. If you "
"proceed,\n"
"no further changes to the statement are possible." "no further changes to the statement are possible."
msgstr "" msgstr ""
"Hat die Ausfahrt bereits stattgefunden? Wenn ja, prüfe bitte ob alle " "Hat die Ausfahrt bereits stattgefunden? Wenn ja, prüfe bitte ob alle "
"aufgelisteten Kosten korrekt sind und reiche deine Abrechnung dann beim " "aufgelisteten Kosten korrekt sind, alle notwendigen Belege hochgeladen sind "
"Finanzreferat ein. Wenn du fortschreitest sind keine weiteren Änderungen an " "und ob alle Personen, die Geld ausbezahlt bekommen sollen, eine gültige IBAN "
"der Abrechnung mehr möglich." "haben. Reiche deine Abrechnung dann beim Finanzreferat ein. Wenn du "
"fortschreitest, sind keine weiteren Änderungen an der Abrechnung mehr "
"möglich."
#: members/templates/admin/freizeit_finance_overview.html #: members/templates/admin/freizeit_finance_overview.html
msgid "Submit" msgid "Submit"

@ -30,6 +30,7 @@ from contrib.rules import memberize_user, has_global_perm
from utils import cvt_to_decimal from utils import cvt_to_decimal
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from schwifty import IBAN
def generate_random_key(): def generate_random_key():
return uuid.uuid4().hex return uuid.uuid4().hex
@ -342,6 +343,10 @@ class Member(Person):
"""Returning the whole place (plz + town)""" """Returning the whole place (plz + town)"""
return "{0} {1}".format(self.plz, self.town) return "{0} {1}".format(self.plz, self.town)
@property
def iban_valid(self):
return IBAN(self.iban, allow_invalid=True).is_valid
@property @property
def address(self): def address(self):
"""Returning the whole address""" """Returning the whole address"""
@ -1210,7 +1215,7 @@ class Freizeit(CommonModel):
if not self.statement: if not self.statement:
return 0 return 0
total_costs = self.statement.total_bills_theoretic total_costs = self.statement.total_bills_theoretic
total_contributions = self.statement.total_staff + self.potential_ljp_contributions total_contributions = self.statement.total_subsidies + self.potential_ljp_contributions
return total_costs - total_contributions return total_costs - total_contributions
@property @property

@ -1,5 +1,6 @@
{% extends "admin/base_site.html" %} {% extends "admin/base_site.html" %}
{% load i18n admin_urls static %} {% load i18n admin_urls static %}
{% load overview_extras %}
{% block extrahead %} {% block extrahead %}
{{ block.super }} {{ block.super }}
@ -38,6 +39,8 @@ cost plan!
<th> <th>
<td>{% trans "Explanation" %}</td> <td>{% trans "Explanation" %}</td>
<td>{% trans "Amount" %}</td> <td>{% trans "Amount" %}</td>
<td>{% trans "Paid by" %}</td>
<td>{% trans "IBAN valid" %}</td>
</th> </th>
{% for bill in memberlist.statement.bill_set.all %} {% for bill in memberlist.statement.bill_set.all %}
<tr> <tr>
@ -50,6 +53,12 @@ cost plan!
<td> <td>
{{ bill.amount }}€ {{ bill.amount }}€
</td> </td>
<td>
{{ bill.paid_by.name }}
</td>
<td>
{{ bill.paid_by.iban_valid|render_bool }}
</td>
</tr> </tr>
{% endfor %} {% endfor %}
</table> </table>
@ -78,21 +87,29 @@ cost plan!
</p> </p>
<p> <p>
{% blocktrans %}The allowance of {{ allowance_per_yl }}€ per person is configured to be paid to:{% endblocktrans %} {% blocktrans %}The allowance of {{ allowance_per_yl }}€ per person is configured to be paid to:{% endblocktrans %}
<ul> <table>
<th>
<td>{% trans "IBAN valid" %}</td>
</th>
{% for member in memberlist.statement.allowance_to.all %} {% for member in memberlist.statement.allowance_to.all %}
<li> <tr>
{{ member.name }} <td>{{ member.name }}</td>
</li> <td>{{ member.iban_valid|render_bool }}</td>
</tr>
{% endfor %} {% endfor %}
</ul> </table>
</p> </p>
<p> <p>
{% blocktrans %}The subsidies for night and transportation costs of {{ total_subsidies }}€ is configured to be paid to:{% endblocktrans %} {% blocktrans %}The subsidies for night and transportation costs of {{ total_subsidies }}€ is configured to be paid to:{% endblocktrans %}
<ul> <table>
<li> <th>
{{ memberlist.statement.subsidy_to.name }} <td>{% trans "IBAN valid" %}</td>
</li> </th>
</ul> <tr>
<td>{{ memberlist.statement.subsidy_to.name }}</td>
<td>{{ memberlist.statement.subsidy_to.iban_valid|render_bool }}</td>
</tr>
</table>
</p> </p>
{% if not memberlist.statement.allowance_to_valid %} {% if not memberlist.statement.allowance_to_valid %}
<p> <p>
@ -128,7 +145,7 @@ you may obtain up to 25€ times {{ duration }} days for {{ participant_count }}
{% trans "Contributions by the association" %} {% trans "Contributions by the association" %}
</td> </td>
<td> <td>
-{{ total_staff }}€ -{{ total_subsidies }}€
</td> </td>
</tr> </tr>
<tr> <tr>
@ -162,7 +179,7 @@ excursions main page, you can generate a template for a seminar report.{% endblo
<h3>{% trans "Submit statement" %}</h3> <h3>{% trans "Submit statement" %}</h3>
<p> <p>
{% blocktrans %}Did you already complete this excursion? If yes, please check if all listed expenses are correct {% blocktrans %}Did you already complete this excursion? If yes, please check if all listed expenses are correct
and then submit the statement for processing by the finance department. If you proceed, and people who want their money back have valid bank account numbers. Then submit the statement for processing by the finance department. If you proceed,
no further changes to the statement are possible.{% endblocktrans %} no further changes to the statement are possible.{% endblocktrans %}
</p> </p>

@ -1,4 +1,5 @@
from django import template from django import template
from django.utils.html import format_html
register = template.Library() register = template.Library()
@ -15,3 +16,19 @@ def has_attendee_wrapper(klettertreff, member):
@register.simple_tag @register.simple_tag
def has_jugendleiter_wrapper(klettertreff, jugendleiter): def has_jugendleiter_wrapper(klettertreff, jugendleiter):
return blToColor(klettertreff.has_jugendleiter(jugendleiter)) return blToColor(klettertreff.has_jugendleiter(jugendleiter))
@register.filter
def render_bool(boolean_value):
if not isinstance(boolean_value, bool):
raise ValueError(f"""Custom Filter 'render_bool': Supplied value '{boolean_value}' is not bool, but {type(boolean_value)}.""")
if boolean_value: # True is a green tick
color = "#bcd386"
htmlclass = "icon-tick"
else: # False is a red cross
color = "#dba4a4"
htmlclass = "icon-cross"
return format_html(f"""<span style="font-weight: bold; color: {color};"
class="{htmlclass}"></span>""")
Loading…
Cancel
Save