From 48dad920181a66b565f8b5df5ca6aed97ca1d76f Mon Sep 17 00:00:00 2001
From: mariusrklein <47218379+mariusrklein@users.noreply.github.com>
Date: Tue, 18 Mar 2025 22:59:36 +0100
Subject: [PATCH 01/18] feat(finance): include amount when summarizing
transactions
---
jdav_web/finance/models.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/jdav_web/finance/models.py b/jdav_web/finance/models.py
index 5fb1aa9..72e795f 100644
--- a/jdav_web/finance/models.py
+++ b/jdav_web/finance/models.py
@@ -261,7 +261,7 @@ class Statement(CommonModel):
continue
new_amount = sum((trans.amount for trans in grp))
- new_ref = "\n".join((trans.reference for trans in grp))
+ new_ref = ", ".join((f"{trans.reference} EUR{trans.amount: .2f}" for trans in grp))
Transaction(statement=self, member=member, amount=new_amount, confirmed=False, reference=new_ref,
ledger=ledger).save()
for trans in grp:
--
2.38.4
From b2a90f54ea83ba70bbb8fbfadd00d4a9a757bd65 Mon Sep 17 00:00:00 2001
From: mariusrklein <47218379+mariusrklein@users.noreply.github.com>
Date: Thu, 27 Mar 2025 23:34:57 +0100
Subject: [PATCH 02/18] feat(finance): display transaction reference length as
it is restricted to 140 chars
---
jdav_web/finance/admin.py | 14 +++++++++++++-
1 file changed, 13 insertions(+), 1 deletion(-)
diff --git a/jdav_web/finance/admin.py b/jdav_web/finance/admin.py
index 0f5237d..7ba30cb 100644
--- a/jdav_web/finance/admin.py
+++ b/jdav_web/finance/admin.py
@@ -1,4 +1,5 @@
from django.contrib import admin, messages
+from django.utils.safestring import mark_safe
from django import forms
from django.forms import Textarea, ClearableFileInput
from django.http import HttpResponse, HttpResponseRedirect
@@ -112,11 +113,22 @@ class StatementUnSubmittedAdmin(CommonAdminMixin, admin.ModelAdmin):
class TransactionOnSubmittedStatementInline(admin.TabularInline):
model = Transaction
- fields = ['amount', 'member', 'reference', 'ledger']
+ fields = ['amount', 'member', 'reference', 'text_length_warning', 'ledger']
formfield_overrides = {
TextField: {'widget': Textarea(attrs={'rows': 1, 'cols': 40})}
}
+ readonly_fields = ['text_length_warning']
extra = 0
+
+ def text_length_warning(self, obj):
+ """Display reference length, warn if exceeds 140 characters."""
+ len_reference = len(obj.reference)
+ len_string = f"{len_reference}/140"
+ if len_reference > 140:
+ return mark_safe(f'{len_string}')
+
+ return len_string
+ text_length_warning.short_description = "Länge"
class BillOnSubmittedStatementInline(BillOnStatementInline):
--
2.38.4
From 53b77a110ab9b5eb8182b511df6f31a88d6d305e Mon Sep 17 00:00:00 2001
From: mariusrklein <47218379+mariusrklein@users.noreply.github.com>
Date: Thu, 27 Mar 2025 23:38:09 +0100
Subject: [PATCH 03/18] feat(finance): create finance statement and excursion
summary
---
jdav_web/finance/admin.py | 25 +++
.../finance/locale/de/LC_MESSAGES/django.po | 6 +-
jdav_web/finance/models.py | 8 +
.../templates/finance/statement_summary.tex | 186 ++++++++++++++++++
jdav_web/locale/de/LC_MESSAGES/django.po | 6 +-
jdav_web/members/pdf.py | 56 ++++--
.../change_form_object_tools.html | 5 +-
7 files changed, 272 insertions(+), 20 deletions(-)
create mode 100644 jdav_web/finance/templates/finance/statement_summary.tex
diff --git a/jdav_web/finance/admin.py b/jdav_web/finance/admin.py
index 7ba30cb..95f8c95 100644
--- a/jdav_web/finance/admin.py
+++ b/jdav_web/finance/admin.py
@@ -14,6 +14,7 @@ from contrib.admin import CommonAdminInlineMixin, CommonAdminMixin
from utils import get_member, RestrictedFileField
from rules.contrib.admin import ObjectPermissionsModelAdmin
+from members.pdf import render_tex_with_attachments
from .models import Ledger, Statement, Receipt, Transaction, Bill, StatementSubmitted, StatementConfirmed,\
StatementUnSubmitted, BillOnStatementProxy
@@ -212,6 +213,8 @@ class StatementSubmittedAdmin(admin.ModelAdmin):
messages.success(request,
_("Successfully confirmed %(name)s. I hope you executed the associated transactions, I wont remind you again.")
% {'name': str(statement)})
+ messages.success(request,
+ mark_safe(f"Hier kannst du den Abrechnungsbeleg herunterladen.")) #TODO: nice path resolution
return HttpResponseRedirect(reverse('admin:%s_%s_changelist' % (self.opts.app_label, self.opts.model_name)))
if "confirm" in request.POST:
res = statement.validity
@@ -316,6 +319,11 @@ class StatementConfirmedAdmin(admin.ModelAdmin):
wrap(self.unconfirm_view),
name="%s_%s_unconfirm" % (self.opts.app_label, self.opts.model_name),
),
+ path(
+ "/summary/",
+ wrap(self.statement_summary_view),
+ name="%s_%s_summary" % (self.opts.app_label, self.opts.model_name),
+ ),
]
return custom_urls + urls
@@ -342,6 +350,23 @@ class StatementConfirmedAdmin(admin.ModelAdmin):
statement=statement)
return render(request, 'admin/unconfirm_statement.html', context=context)
+
+ def statement_summary_view(self, request, object_id):
+ statement = StatementConfirmed.objects.get(pk=object_id)
+
+ if not statement.confirmed:
+ messages.error(request,
+ _("%(name)s is not yet confirmed.") % {'name': str(statement)})
+ return HttpResponseRedirect(reverse('admin:%s_%s_change' % (self.opts.app_label, self.opts.model_name), args=(statement.pk,)))
+ excursion = statement.excursion
+ context = dict(statement=statement.template_context(), excursion=excursion, settings=settings)
+
+ pdf_filename = f"{excursion.code}_{excursion.name}_Zuschussbeleg" if excursion else f"Abrechnungsbeleg"
+ attachments = [bill.proof.path for bill in statement.bills_covered]
+ return render_tex_with_attachments(pdf_filename, 'finance/statement_summary.tex', context, attachments)
+
+ statement_summary_view.short_description = _('Download summary')
+
@admin.register(Transaction)
class TransactionAdmin(admin.ModelAdmin):
diff --git a/jdav_web/finance/locale/de/LC_MESSAGES/django.po b/jdav_web/finance/locale/de/LC_MESSAGES/django.po
index b4ba049..13199ba 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-02-01 21:11+0100\n"
+"POT-Creation-Date: 2025-03-27 23:35+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
@@ -152,6 +152,10 @@ msgstr ""
msgid "Unconfirm statement"
msgstr "Bestätigung zurücknehmen"
+#: finance/admin.py
+msgid "Download summary"
+msgstr "Beleg herunterladen"
+
#: finance/apps.py
msgid "Finance"
msgstr "Finanzen"
diff --git a/jdav_web/finance/models.py b/jdav_web/finance/models.py
index 72e795f..b4a770c 100644
--- a/jdav_web/finance/models.py
+++ b/jdav_web/finance/models.py
@@ -271,6 +271,11 @@ class Statement(CommonModel):
def total_bills(self):
return sum([bill.amount for bill in self.bill_set.all() if bill.costs_covered])
+ @property
+ def bills_covered(self):
+ """Returns the bills that are marked for reimbursement by the finance officer"""
+ return [bill for bill in self.bill_set.all() if bill.costs_covered]
+
@property
def total_bills_theoretic(self):
return sum([bill.amount for bill in self.bill_set.all()])
@@ -403,6 +408,7 @@ class Statement(CommonModel):
context = {
'total_bills': self.total_bills,
'total_bills_theoretic': self.total_bills_theoretic,
+ 'bills_covered': self.bills_covered,
'total': self.total,
}
if self.excursion:
@@ -424,6 +430,8 @@ class Statement(CommonModel):
'theoretical_total_staff': self.theoretical_total_staff,
'real_staff_count': self.real_staff_count,
'total_subsidies': self.total_subsidies,
+ 'subsidy_to': self.subsidy_to,
+ 'allowance_to': self.allowance_to,
}
return dict(context, **excursion_context)
else:
diff --git a/jdav_web/finance/templates/finance/statement_summary.tex b/jdav_web/finance/templates/finance/statement_summary.tex
new file mode 100644
index 0000000..3d37b32
--- /dev/null
+++ b/jdav_web/finance/templates/finance/statement_summary.tex
@@ -0,0 +1,186 @@
+{% load static common tex_extras %}
+
+\documentclass[a4paper]{article}
+
+\usepackage[utf8]{inputenc}
+% remove all undefined unicode characters instead of throwing an error
+\makeatletter
+\def\UTFviii@undefined@err#1{}
+\makeatother
+\usepackage{booktabs}
+\usepackage{amssymb}
+\usepackage{cmbright}
+\usepackage{graphicx}
+\usepackage{textpos}
+\usepackage[colorlinks, breaklinks]{hyperref}
+\usepackage{float}
+\usepackage[margin=2cm]{geometry}
+\usepackage{array}
+\usepackage{tabularx}
+\usepackage{ltablex}
+
+\newcommand{\picpos}[4]{
+ \begin{textblock*}{#1}(#2, #3)
+ \includegraphics[width=\textwidth]{#4}
+ \end{textblock*}
+}
+
+% custom url command for properly formatting emails
+\DeclareUrlCommand\Email{\urlstyle{same}}
+% allow linebreak after every character
+\expandafter\def\expandafter\UrlBreaks\expandafter{\UrlBreaks
+\do\/\do\a\do\b\do\c\do\d\do\e\do\f\do\g\do\h\do\i\do\j\do\k
+\do\l\do\m\do\n\do\o\do\p\do\q\do\r\do\s\do\t\do\u\do\v
+\do\w\do\x\do\y\do\z
+\do\A\do\B\do\C\do\D\do\E\do\F\do\G\do\H\do\I\do\J\do\K
+\do\L\do\M\do\N\do\O\do\P\do\Q\do\R\do\S\do\T\do\U\do\V
+\do\W\do\X\do\Y\do\Z}
+
+\renewcommand{\arraystretch}{1.5}
+
+\newcolumntype{L}{>{\hspace{0pt}\raggedright\arraybackslash}X}
+\newcolumntype{S}{>{\raggedright\arraybackslash\hsize=0.7\hsize}X}
+
+\newcommand{\tickedbox}{
+ \makebox[0pt][l]{$\square$}\raisebox{.15ex}{\hspace{0.1em}$\checkmark$}
+}
+\newcommand{\checkbox}{
+ \makebox[0pt][l]{$\square$}
+}
+\begin{document}
+% HEADER RIGHT
+{% settings_value 'DEFAULT_STATIC_PATH' as static_root %}
+\picpos{4.5cm}{11.5cm}{0cm}{%
+{{ static_root }}/general/img/dav_logo_sektion.png%
+}
+\begin{textblock*}{5cm}(12cm, 2.3cm)
+ \begin{flushright}
+ \small
+ \noindent Deutscher Alpenverein e. V. \\
+ Sektion {{ settings.SEKTION }} \\
+ {{ settings.SEKTION_STREET }} \\
+ {{ settings.SEKTION_TOWN }} \\
+ Tel.: {{ settings.SEKTION_TELEPHONE }} \\
+ Fax: {{ settings.SEKTION_TELEFAX }} \\
+ {{ settings.SEKTION_CONTACT_MAIL }} \\
+ \end{flushright}
+\end{textblock*}
+
+% HEADLINE
+{\noindent\LARGE{Abrechnungs- und Zuschussbeleg\\[2mm]Sektionsveranstaltung}}\\[1mm]
+\textit{Erstellt: {{ creation_date }} }\\
+
+{% if excursion %}
+\noindent\textbf{\large Ausfahrt}
+ % DESCRIPTION TABLE
+ \begin{table}[H]
+ \begin{tabular}{ll}
+ Aktivität: & {{ excursion.name|esc_all }} \\
+ Ordnungsnummer & {{ excursion.code|esc_all }} \\
+ Ort / Stützpunkt: & {{ excursion.place|esc_all }} \\
+ Zeitraum: & {{ excursion.duration|esc_all}} Tage ({{ excursion.time_period_str|esc_all }}) \\
+ Teilnehmer*innen: & {{ excursion.participant_count }} der Gruppe(n) {{ excursion.groups_str|esc_all }} \\
+ Betreuer*innen: & {{excursion.staff_count|esc_all }} ({{ excursion.staff_str|esc_all }}) \\
+ Art der Tour: & {% checked_if_true 'Gemeinschaftstour' excursion.get_tour_type %}
+ {% checked_if_true 'Führungstour' excursion.get_tour_type %}
+ {% checked_if_true 'Ausbildung' excursion.get_tour_type %} \\
+ Anreise: & {% checked_if_true 'ÖPNV' excursion.get_tour_approach %}
+ {% checked_if_true 'Muskelkraft' excursion.get_tour_approach %}
+ {% checked_if_true 'Fahrgemeinschaften' excursion.get_tour_approach %}
+ \end{tabular}
+ \end{table}
+
+\noindent\textbf{\large Zuschüsse und Aufwandsentschädigung}
+{% if excursion.approved_staff_count > 0 %}
+ \noindent Gemäß Beschluss des Jugendausschusses gelten folgende Sätze für Zuschüsse pro genehmigter Jugendleiter*in:
+
+ \begin{table}[H]
+ \centering
+ \begin{tabular}{lllr}
+ \toprule
+ \textbf{Posten} & \textbf{Einzelsatz} & \textbf{Anzahl} & \textbf{Gesamtbetrag pro JL} \\
+ \midrule
+ Zuschuss Übernachtung & {{ statement.price_per_night }} € / Nacht & {{ statement.nights }} Nächte & {{ statement.nights_per_yl }} € \\
+ Zuschuss Anreise & {{statement.euro_per_km}} € / km ({{ statement.means_of_transport }}) & {{ statement.kilometers_traveled }} km & {{ statement.transportation_per_yl }} € \\
+ Aufwandsentschädigung & {{ statement.allowance_per_day }},00 € / Tag & {{ statement.duration }} Tage & {{ statement.allowance_per_yl }} € \\
+ \midrule
+ \textbf{Summe}& & & \textbf{ {{ statement.total_per_yl }} }€\\
+ \bottomrule
+ \end{tabular}
+ \end{table}
+
+\noindent Gemäß JDAV-Betreuungsschlüssel können bei {{ excursion.participant_count }} Teilnehmer*innen
+bis zu {{ excursion.approved_staff_count }} Jugendleiter*innen {% if excursion.approved_extra_youth_leader_count %}
+(davon {{ excursion.approved_extra_youth_leader_count }} durch das Jugendreferat zusätzlich genehmigt){% endif %} bezuschusst werden.
+Zuschüsse und Aufwandsentschädigung werden wie folgt abgerufen:
+\begin{itemize}
+
+ {% if statement.allowances_paid > 0 %}
+
+ \item Eine Aufwandsentschädigung von {{ statement.allowance_per_yl }} € pro Jugendleiter*in wird überwiesen an:
+ {% for m in statement.allowance_to.all %}{% if forloop.counter > 1 %}, {% endif %}{{ m.name }}{% endfor %}
+ {% else %}
+ \item Keiner*r der Jugendleiter*innen nimmt eine Aufwandsentschädigung in Anspruch.
+ {% endif %}
+
+ {% if statement.subsidy_to %}
+ \item Der Zuschuss zu Übernachtung und Anreise für alle Jugendleiter*innen in Höhe von {{ statement.total_subsidies }} € wird überwiesen an:
+ {{ statement.subsidy_to.name }}
+
+ {% else %}
+ \item Zuschüsse zu Übernachtung und Anreise werden nicht in Anspruch genommen.
+ {% endif %}
+
+\end{itemize}
+{% else %}
+\noindent Für die vorliegende Ausfahrt sind keine Jugendleiter*innen anspruchsberechtigt für Zuschüsse oder Aufwandsentschädigung.
+{% endif %}
+
+{% else %}
+\vspace{110pt}
+{% endif %}
+
+
+\vspace{12pt}
+
+\noindent\textbf{\large Ausgabenübersicht}
+\nopagebreak
+\begin{table}[H]
+ \centering
+ \begin{tabular}{lllr}
+ \toprule
+ \textbf{Titel} & \textbf{Beschreibung} & \textbf{Auszahlung an} & \textbf{Betrag} \\
+ \midrule
+
+{% if statement.bills_covered %}
+ {% for bill in statement.bills_covered %}
+ {{ forloop.counter }}. {{ bill.short_description}} & {{ bill.explanation}} & {{ bill.paid_by.name }} & {{ bill.amount }} € \\
+ {% endfor %}
+ \midrule
+ \multicolumn{3}{l}{\textbf{Summe übernommene Ausgaben}} & \textbf{ {{ statement.total_bills }} }€\\
+{% endif %}
+{% if excursion.approved_staff_count > 0 and statement.allowances_paid > 0 or excursion.approved_staff_count > 0 and statement.subsidy_to %}
+ \midrule
+ {% if statement.allowances_paid > 0 %}
+ {% for m in statement.allowance_to.all %}
+ Aufwandsentschädigung & & {{ m.name }} & {{ statement.allowance_per_yl }} €\\
+ {% endfor %}
+ {% endif %}
+ {% if statement.subsidy_to %}
+ \multicolumn{2}{l}{Zuschuss Übernachtung und Anreise für alle Jugendleiter*innen} & {{ statement.subsidy_to.name }} & {{ statement.total_subsidies}} €\\
+ {% endif %}
+ \midrule
+ \multicolumn{3}{l}{\textbf{Summe Zuschüsse und Aufwandsentschädigung}} & \textbf{ {{ statement.total_staff }} }€\\
+{%endif %}
+{% if statement.bills_covered and excursion.approved_staff_count > 0 %}
+ \midrule
+ \textbf{Gesamtsumme}& & & \textbf{ {{ statement.total }} }€\\
+{% endif %}
+ \bottomrule
+ \end{tabular}
+\end{table}
+
+\noindent Dieser Beleg wird automatisch erstellt und daher nicht unterschrieben.
+
+
+\end{document}
diff --git a/jdav_web/locale/de/LC_MESSAGES/django.po b/jdav_web/locale/de/LC_MESSAGES/django.po
index cafea14..e32540b 100644
--- a/jdav_web/locale/de/LC_MESSAGES/django.po
+++ b/jdav_web/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-02-01 14:54+0100\n"
+"POT-Creation-Date: 2025-03-27 23:35+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
@@ -236,6 +236,10 @@ msgstr "Löschen?"
msgid "Unconfirm"
msgstr "Bestätigung zurücknehmen"
+#: templates/admin/finance/statementconfirmed/change_form_object_tools.html
+msgid "Download summary"
+msgstr "Beleg herunterladen"
+
#: templates/admin/finance/statementsubmitted/change_form_object_tools.html
msgid "Reduce transactions"
msgstr "Überweisungen minimieren"
diff --git a/jdav_web/members/pdf.py b/jdav_web/members/pdf.py
index fdd82f0..57e414a 100644
--- a/jdav_web/members/pdf.py
+++ b/jdav_web/members/pdf.py
@@ -47,6 +47,23 @@ def render_docx(name, template_path, context, date=None, save_only=False):
return filename_docx
return serve_media(filename_docx, 'application/docx')
+def render_tex_with_attachments(name, template_path, context, attachments, save_only=False):
+
+ rendered_pdf = render_tex(name, template_path, context, save_only=True)
+
+ reader = PdfReader(media_path(rendered_pdf))
+ writer = PdfWriter()
+ writer.append(reader)
+
+ pdf_add_attachments(writer, attachments)
+
+ with open(media_path(rendered_pdf), 'wb') as output_stream:
+ writer.write(output_stream)
+
+ if save_only:
+ return rendered_pdf
+ return serve_pdf(rendered_pdf)
+
def render_tex(name, template_path, context, date=None, save_only=False):
filename = generate_tex(name, template_path, context, date=date)
@@ -73,6 +90,27 @@ def render_tex(name, template_path, context, date=None, save_only=False):
return serve_pdf(filename_pdf)
+def pdf_add_attachments(pdf_writer, attachments):
+ for fp in attachments:
+ try:
+ if fp.endswith(".pdf"):
+ # append pdf directly
+ img_pdf = PdfReader(fp)
+ else:
+ # convert ensures that png files with an alpha channel can be appended
+ img = Image.open(fp).convert("RGB")
+ img_io = BytesIO()
+ img.save(img_io, "pdf")
+ img_io.seek(0)
+ img_pdf = PdfReader(img_io)
+ img_pdf_scaled = scale_pdf_to_a4(img_pdf)
+ pdf_writer.append(img_pdf_scaled)
+
+ except Exception as e:
+ print("Could not add image", fp)
+ print(e)
+
+
def scale_pdf_page_to_a4(page):
A4_WIDTH, A4_HEIGHT = 595, 842
@@ -114,23 +152,7 @@ def fill_pdf_form(name, template_path, fields, attachments=[], date=None, save_o
writer.update_page_form_field_values(None, fields, auto_regenerate=False)
- for fp in attachments:
- try:
- if fp.endswith(".pdf"):
- # append pdf directly
- img_pdf = PdfReader(fp)
- else:
- # convert ensures that png files with an alpha channel can be appended
- img = Image.open(fp).convert("RGB")
- img_io = BytesIO()
- img.save(img_io, "pdf")
- img_io.seek(0)
- img_pdf = PdfReader(img_io)
- img_pdf_scaled = scale_pdf_to_a4(img_pdf)
- writer.append(img_pdf_scaled)
- except Exception as e:
- print("Could not add image", fp)
- print(e)
+ pdf_add_attachments(writer, attachments)
with open(media_path(filename_pdf), 'wb') as output_stream:
writer.write(output_stream)
diff --git a/jdav_web/templates/admin/finance/statementconfirmed/change_form_object_tools.html b/jdav_web/templates/admin/finance/statementconfirmed/change_form_object_tools.html
index 1906f52..0ab5d39 100644
--- a/jdav_web/templates/admin/finance/statementconfirmed/change_form_object_tools.html
+++ b/jdav_web/templates/admin/finance/statementconfirmed/change_form_object_tools.html
@@ -7,7 +7,10 @@
{% url opts|admin_urlname:'unconfirm' original.pk|admin_urlquote as invite_url %}
{% trans 'Unconfirm' %}
-
+
+ {% url opts|admin_urlname:'summary' original.pk|admin_urlquote as invite_url %}
+ {% trans 'Download summary' %}
+
{{block.super}}
{% endblock %}
--
2.38.4
From 74304f97f9b6ae8264d69bcdd025b8df61421bac Mon Sep 17 00:00:00 2001
From: Christian Merten
Date: Sun, 6 Apr 2025 00:11:39 +0200
Subject: [PATCH 04/18] refactor(latex): add base template and unify designs
We add a base template for all latex documents to unify the desings and improve maintainability. Also
the table definitions are simplified fixing the various width issues.
---
.../templates/finance/statement_summary.tex | 168 ++++++------------
1 file changed, 52 insertions(+), 116 deletions(-)
diff --git a/jdav_web/finance/templates/finance/statement_summary.tex b/jdav_web/finance/templates/finance/statement_summary.tex
index 3d37b32..5191119 100644
--- a/jdav_web/finance/templates/finance/statement_summary.tex
+++ b/jdav_web/finance/templates/finance/statement_summary.tex
@@ -1,123 +1,60 @@
+{% extends "members/tex_base.tex" %}
{% load static common tex_extras %}
-\documentclass[a4paper]{article}
-
-\usepackage[utf8]{inputenc}
-% remove all undefined unicode characters instead of throwing an error
-\makeatletter
-\def\UTFviii@undefined@err#1{}
-\makeatother
-\usepackage{booktabs}
-\usepackage{amssymb}
-\usepackage{cmbright}
-\usepackage{graphicx}
-\usepackage{textpos}
-\usepackage[colorlinks, breaklinks]{hyperref}
-\usepackage{float}
-\usepackage[margin=2cm]{geometry}
-\usepackage{array}
-\usepackage{tabularx}
-\usepackage{ltablex}
-
-\newcommand{\picpos}[4]{
- \begin{textblock*}{#1}(#2, #3)
- \includegraphics[width=\textwidth]{#4}
- \end{textblock*}
-}
-
-% custom url command for properly formatting emails
-\DeclareUrlCommand\Email{\urlstyle{same}}
-% allow linebreak after every character
-\expandafter\def\expandafter\UrlBreaks\expandafter{\UrlBreaks
-\do\/\do\a\do\b\do\c\do\d\do\e\do\f\do\g\do\h\do\i\do\j\do\k
-\do\l\do\m\do\n\do\o\do\p\do\q\do\r\do\s\do\t\do\u\do\v
-\do\w\do\x\do\y\do\z
-\do\A\do\B\do\C\do\D\do\E\do\F\do\G\do\H\do\I\do\J\do\K
-\do\L\do\M\do\N\do\O\do\P\do\Q\do\R\do\S\do\T\do\U\do\V
-\do\W\do\X\do\Y\do\Z}
-
-\renewcommand{\arraystretch}{1.5}
-
-\newcolumntype{L}{>{\hspace{0pt}\raggedright\arraybackslash}X}
-\newcolumntype{S}{>{\raggedright\arraybackslash\hsize=0.7\hsize}X}
-
-\newcommand{\tickedbox}{
- \makebox[0pt][l]{$\square$}\raisebox{.15ex}{\hspace{0.1em}$\checkmark$}
-}
-\newcommand{\checkbox}{
- \makebox[0pt][l]{$\square$}
-}
-\begin{document}
-% HEADER RIGHT
-{% settings_value 'DEFAULT_STATIC_PATH' as static_root %}
-\picpos{4.5cm}{11.5cm}{0cm}{%
-{{ static_root }}/general/img/dav_logo_sektion.png%
-}
-\begin{textblock*}{5cm}(12cm, 2.3cm)
- \begin{flushright}
- \small
- \noindent Deutscher Alpenverein e. V. \\
- Sektion {{ settings.SEKTION }} \\
- {{ settings.SEKTION_STREET }} \\
- {{ settings.SEKTION_TOWN }} \\
- Tel.: {{ settings.SEKTION_TELEPHONE }} \\
- Fax: {{ settings.SEKTION_TELEFAX }} \\
- {{ settings.SEKTION_CONTACT_MAIL }} \\
- \end{flushright}
-\end{textblock*}
-
-% HEADLINE
-{\noindent\LARGE{Abrechnungs- und Zuschussbeleg\\[2mm]Sektionsveranstaltung}}\\[1mm]
-\textit{Erstellt: {{ creation_date }} }\\
+{% block title %}Abrechnungs- und Zuschussbeleg\\[2mm]Sektionsveranstaltung{% endblock %}
+
+{% block content %}
{% if excursion %}
\noindent\textbf{\large Ausfahrt}
- % DESCRIPTION TABLE
- \begin{table}[H]
- \begin{tabular}{ll}
- Aktivität: & {{ excursion.name|esc_all }} \\
- Ordnungsnummer & {{ excursion.code|esc_all }} \\
- Ort / Stützpunkt: & {{ excursion.place|esc_all }} \\
- Zeitraum: & {{ excursion.duration|esc_all}} Tage ({{ excursion.time_period_str|esc_all }}) \\
- Teilnehmer*innen: & {{ excursion.participant_count }} der Gruppe(n) {{ excursion.groups_str|esc_all }} \\
- Betreuer*innen: & {{excursion.staff_count|esc_all }} ({{ excursion.staff_str|esc_all }}) \\
- Art der Tour: & {% checked_if_true 'Gemeinschaftstour' excursion.get_tour_type %}
- {% checked_if_true 'Führungstour' excursion.get_tour_type %}
- {% checked_if_true 'Ausbildung' excursion.get_tour_type %} \\
- Anreise: & {% checked_if_true 'ÖPNV' excursion.get_tour_approach %}
- {% checked_if_true 'Muskelkraft' excursion.get_tour_approach %}
- {% checked_if_true 'Fahrgemeinschaften' excursion.get_tour_approach %}
- \end{tabular}
- \end{table}
+
+% DESCRIPTION TABLE
+\begin{table}[H]
+ \begin{tabular}{ll}
+ Aktivität: & {{ excursion.name|esc_all }} \\
+ Ordnungsnummer & {{ excursion.code|esc_all }} \\
+ Ort / Stützpunkt: & {{ excursion.place|esc_all }} \\
+ Zeitraum: & {{ excursion.duration|esc_all}} Tage ({{ excursion.time_period_str|esc_all }}) \\
+ Teilnehmer*innen: & {{ excursion.participant_count }} der Gruppe(n) {{ excursion.groups_str|esc_all }} \\
+ Betreuer*innen: & {{excursion.staff_count|esc_all }} ({{ excursion.staff_str|esc_all }}) \\
+ Art der Tour: & {% checked_if_true 'Gemeinschaftstour' excursion.get_tour_type %}
+ {% checked_if_true 'Führungstour' excursion.get_tour_type %}
+ {% checked_if_true 'Ausbildung' excursion.get_tour_type %} \\
+ Anreise: & {% checked_if_true 'ÖPNV' excursion.get_tour_approach %}
+ {% checked_if_true 'Muskelkraft' excursion.get_tour_approach %}
+ {% checked_if_true 'Fahrgemeinschaften' excursion.get_tour_approach %}
+ \end{tabular}
+\end{table}
\noindent\textbf{\large Zuschüsse und Aufwandsentschädigung}
{% if excursion.approved_staff_count > 0 %}
- \noindent Gemäß Beschluss des Jugendausschusses gelten folgende Sätze für Zuschüsse pro genehmigter Jugendleiter*in:
-
- \begin{table}[H]
- \centering
- \begin{tabular}{lllr}
- \toprule
- \textbf{Posten} & \textbf{Einzelsatz} & \textbf{Anzahl} & \textbf{Gesamtbetrag pro JL} \\
- \midrule
- Zuschuss Übernachtung & {{ statement.price_per_night }} € / Nacht & {{ statement.nights }} Nächte & {{ statement.nights_per_yl }} € \\
- Zuschuss Anreise & {{statement.euro_per_km}} € / km ({{ statement.means_of_transport }}) & {{ statement.kilometers_traveled }} km & {{ statement.transportation_per_yl }} € \\
- Aufwandsentschädigung & {{ statement.allowance_per_day }},00 € / Tag & {{ statement.duration }} Tage & {{ statement.allowance_per_yl }} € \\
- \midrule
- \textbf{Summe}& & & \textbf{ {{ statement.total_per_yl }} }€\\
- \bottomrule
- \end{tabular}
- \end{table}
-
-\noindent Gemäß JDAV-Betreuungsschlüssel können bei {{ excursion.participant_count }} Teilnehmer*innen
+
+\noindent Gemäß Beschluss des Jugendausschusses gelten folgende Sätze für Zuschüsse pro genehmigter Jugendleiter*in:
+
+\begin{table}[H]
+ \centering
+ \begin{tabularx}{.97\textwidth}{Xllr}
+ \toprule
+ \textbf{Posten} & \textbf{Einzelsatz} & \textbf{Anzahl} & \textbf{Gesamtbetrag pro JL} \\
+ \midrule
+ Zuschuss Übernachtung & {{ statement.price_per_night }} € / Nacht & {{ statement.nights }} Nächte & {{ statement.nights_per_yl }} € \\
+ Zuschuss Anreise & {{statement.euro_per_km}} € / km ({{ statement.means_of_transport }}) & {{ statement.kilometers_traveled }} km & {{ statement.transportation_per_yl }} € \\
+ Aufwandsentschädigung & {{ statement.allowance_per_day }},00 € / Tag & {{ statement.duration }} Tage & {{ statement.allowance_per_yl }} € \\
+ \midrule
+ \textbf{Summe}& & & \textbf{ {{ statement.total_per_yl }} }€\\
+ \bottomrule
+ \end{tabularx}
+\end{table}
+
+\noindent Gemäß JDAV-Betreuungsschlüssel können bei {{ excursion.participant_count }} Teilnehmer*innen
bis zu {{ excursion.approved_staff_count }} Jugendleiter*innen {% if excursion.approved_extra_youth_leader_count %}
(davon {{ excursion.approved_extra_youth_leader_count }} durch das Jugendreferat zusätzlich genehmigt){% endif %} bezuschusst werden.
Zuschüsse und Aufwandsentschädigung werden wie folgt abgerufen:
\begin{itemize}
-
+
{% if statement.allowances_paid > 0 %}
- \item Eine Aufwandsentschädigung von {{ statement.allowance_per_yl }} € pro Jugendleiter*in wird überwiesen an:
+ \item Eine Aufwandsentschädigung von {{ statement.allowance_per_yl }} € pro Jugendleiter*in wird überwiesen an:
{% for m in statement.allowance_to.all %}{% if forloop.counter > 1 %}, {% endif %}{{ m.name }}{% endfor %}
{% else %}
\item Keiner*r der Jugendleiter*innen nimmt eine Aufwandsentschädigung in Anspruch.
@@ -126,14 +63,14 @@ Zuschüsse und Aufwandsentschädigung werden wie folgt abgerufen:
{% if statement.subsidy_to %}
\item Der Zuschuss zu Übernachtung und Anreise für alle Jugendleiter*innen in Höhe von {{ statement.total_subsidies }} € wird überwiesen an:
{{ statement.subsidy_to.name }}
-
+
{% else %}
\item Zuschüsse zu Übernachtung und Anreise werden nicht in Anspruch genommen.
{% endif %}
\end{itemize}
{% else %}
-\noindent Für die vorliegende Ausfahrt sind keine Jugendleiter*innen anspruchsberechtigt für Zuschüsse oder Aufwandsentschädigung.
+\noindent Für die vorliegende Ausfahrt sind keine Jugendleiter*innen anspruchsberechtigt für Zuschüsse oder Aufwandsentschädigung.
{% endif %}
{% else %}
@@ -145,16 +82,16 @@ Zuschüsse und Aufwandsentschädigung werden wie folgt abgerufen:
\noindent\textbf{\large Ausgabenübersicht}
\nopagebreak
-\begin{table}[H]
+\begin{table}[H]
\centering
- \begin{tabular}{lllr}
+ \begin{tabularx}{.97\textwidth}{lXlr}
\toprule
\textbf{Titel} & \textbf{Beschreibung} & \textbf{Auszahlung an} & \textbf{Betrag} \\
\midrule
-
+
{% if statement.bills_covered %}
{% for bill in statement.bills_covered %}
- {{ forloop.counter }}. {{ bill.short_description}} & {{ bill.explanation}} & {{ bill.paid_by.name }} & {{ bill.amount }} € \\
+ {{ forloop.counter }}. {{ bill.short_description}} & {{ bill.explanation}} & {{ bill.paid_by.name|esc_all }} & {{ bill.amount }} € \\
{% endfor %}
\midrule
\multicolumn{3}{l}{\textbf{Summe übernommene Ausgaben}} & \textbf{ {{ statement.total_bills }} }€\\
@@ -163,7 +100,7 @@ Zuschüsse und Aufwandsentschädigung werden wie folgt abgerufen:
\midrule
{% if statement.allowances_paid > 0 %}
{% for m in statement.allowance_to.all %}
- Aufwandsentschädigung & & {{ m.name }} & {{ statement.allowance_per_yl }} €\\
+ Aufwandsentschädigung & & {{ m.name|esc_all }} & {{ statement.allowance_per_yl }} €\\
{% endfor %}
{% endif %}
{% if statement.subsidy_to %}
@@ -177,10 +114,9 @@ Zuschüsse und Aufwandsentschädigung werden wie folgt abgerufen:
\textbf{Gesamtsumme}& & & \textbf{ {{ statement.total }} }€\\
{% endif %}
\bottomrule
- \end{tabular}
+ \end{tabularx}
\end{table}
\noindent Dieser Beleg wird automatisch erstellt und daher nicht unterschrieben.
-
-\end{document}
+{% endblock %}
--
2.38.4
From 1aba1129c55732ba56a0bc3c5397f363032e8f51 Mon Sep 17 00:00:00 2001
From: Christian Merten
Date: Sun, 6 Apr 2025 00:22:34 +0200
Subject: [PATCH 05/18] use target=_blank
---
.../finance/statementconfirmed/change_form_object_tools.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/jdav_web/templates/admin/finance/statementconfirmed/change_form_object_tools.html b/jdav_web/templates/admin/finance/statementconfirmed/change_form_object_tools.html
index 0ab5d39..cb53b47 100644
--- a/jdav_web/templates/admin/finance/statementconfirmed/change_form_object_tools.html
+++ b/jdav_web/templates/admin/finance/statementconfirmed/change_form_object_tools.html
@@ -9,7 +9,7 @@
{% url opts|admin_urlname:'summary' original.pk|admin_urlquote as invite_url %}
- {% trans 'Download summary' %}
+ {% trans 'Download summary' %}
{{block.super}}
--
2.38.4
From 64b7788887ab6aa50a294e8bc4846afd141913ac Mon Sep 17 00:00:00 2001
From: mariusrklein <47218379+mariusrklein@users.noreply.github.com>
Date: Sun, 6 Apr 2025 14:24:22 +0200
Subject: [PATCH 06/18] fix(excursion/ljp): added intervention-based
calculation of seminar duration.
---
jdav_web/members/excel.py | 2 +-
jdav_web/members/models.py | 34 ++++++++++++++++++-
.../templates/members/seminar_report_docx.tex | 2 +-
3 files changed, 35 insertions(+), 3 deletions(-)
diff --git a/jdav_web/members/excel.py b/jdav_web/members/excel.py
index 20bbbc1..d8b55e3 100644
--- a/jdav_web/members/excel.py
+++ b/jdav_web/members/excel.py
@@ -117,7 +117,7 @@ def generate_ljp_vbk(excursion):
sheet['D19'] = settings.SEKTION
sheet['G19'] = title
sheet['I19'] = f"von {excursion.date:%d.%m.%y} bis {excursion.end:%d.%m.%y}"
- sheet['J19'] = excursion.duration
+ sheet['J19'] = excursion.ljp_duration
sheet['L19'] = f"{excursion.ljp_participant_count}"
sheet['H19'] = excursion.get_ljp_activity_category()
sheet['M19'] = f"{excursion.postcode}, {excursion.place}"
diff --git a/jdav_web/members/models.py b/jdav_web/members/models.py
index a1f179d..5e77482 100644
--- a/jdav_web/members/models.py
+++ b/jdav_web/members/models.py
@@ -8,6 +8,7 @@ import csv
from django.db import models
from django.db.models import TextField, ManyToManyField, ForeignKey, Count,\
Sum, Case, Q, F, When, Value, IntegerField, Subquery, OuterRef
+from django.db.models.functions import TruncDate
from django.utils.translation import gettext_lazy as _
from django.utils import timezone
from django.utils.html import format_html
@@ -1242,7 +1243,31 @@ class Freizeit(CommonModel):
return sum([i.duration for i in self.ljpproposal.intervention_set.all()])
else:
return 0
+
+ @property
+ def total_seminar_days(self):
+ """calculate seminar days based on intervention hours in every day"""
+ if hasattr(self, 'ljpproposal'):
+ hours_per_day = (
+ Intervention.objects
+ .filter(ljp_proposal_id=self.ljpproposal.id)
+ .annotate(day=TruncDate('date_start')) # Extract the date (without time)
+ .values('day') # Group by day
+ .annotate(total_duration=Sum('duration')) # Sum durations for each day
+ .order_by('day') # Sort results by date
+ )
+ # Calculate the total number of seminar days
+ # Each day is counted as 1 if total_duration is >= 5 hours, as 0.5 if total_duration is >= 2.5
+ # otherwise 0
+ return sum([min(math.floor(h['total_duration']/cvt_to_decimal(2.5))/2, 1) for h in hours_per_day])
+ else:
+ return 0
+ @property
+ def ljp_duration(self):
+ """calculate the duration in days for the LJP"""
+ return min(self.duration, self.total_seminar_days)
+
@property
def staff_count(self):
return self.jugendleiter.count()
@@ -1331,12 +1356,19 @@ class Freizeit(CommonModel):
return cvt_to_decimal(min(self.maximal_ljp_contributions,
0.9 * float(self.statement.total_bills_theoretic) + float(self.statement.total_staff)))
+ @property
+ def payable_ljp_contributions(self):
+ """from the requested ljp contributions, a tax may be deducted for risk reduction"""
+ if self.statement.ljp_to:
+ return self.statement.paid_ljp_contributions
+ return cvt_to_decimal(self.potential_ljp_contributions * cvt_to_decimal(1 - settings.LJP_TAX))
+
@property
def total_relative_costs(self):
if not self.statement:
return 0
total_costs = self.statement.total_bills_theoretic
- total_contributions = self.statement.total_subsidies + self.potential_ljp_contributions
+ total_contributions = self.statement.total_subsidies + self.payable_ljp_contributions
return total_costs - total_contributions
@property
diff --git a/jdav_web/members/templates/members/seminar_report_docx.tex b/jdav_web/members/templates/members/seminar_report_docx.tex
index 21c58b8..73539a2 100644
--- a/jdav_web/members/templates/members/seminar_report_docx.tex
+++ b/jdav_web/members/templates/members/seminar_report_docx.tex
@@ -36,7 +36,7 @@
\textbf{Sektion:} & {{ settings.SEKTION }} \\
\textbf{Titel der Maßnahme:} & {% if not memberlist.ljpproposal %}{{ memberlist.name|esc_all }}{% else %}{{ memberlist.ljpproposal.title }} {% endif %} \\
\textbf{Interne Ordnungsnummer:} & {{ memberlist.code|esc_all }} \\
- \textbf{Anzahl der durchgeführten Lehrgangstage:} & {{ memberlist.duration }} \\
+ \textbf{Anzahl der durchgeführten Lehrgangstage:} & {{ memberlist.ljp_duration }} \\
\end{tabular}
\end{table}
--
2.38.4
From c6659e00323a7391d7948ee4e34f12b3f83e914e Mon Sep 17 00:00:00 2001
From: mariusrklein <47218379+mariusrklein@users.noreply.github.com>
Date: Sun, 6 Apr 2025 14:23:05 +0200
Subject: [PATCH 07/18] feat(finance/excursion): added ljp payout functionality
and tax
---
jdav_web/finance/admin.py | 3 +-
.../finance/locale/de/LC_MESSAGES/django.po | 33 ++++++++++++++++++-
.../migrations/0009_statement_ljp_to.py | 20 +++++++++++
jdav_web/finance/models.py | 28 +++++++++++++++-
.../admin/overview_submitted_statement.html | 19 +++++++++++
.../templates/finance/statement_summary.tex | 19 +++++++++--
jdav_web/jdav_web/settings/local.py | 1 +
jdav_web/members/admin.py | 8 ++---
.../members/locale/de/LC_MESSAGES/django.po | 28 ++++++++++++++--
.../admin/freizeit_finance_overview.html | 16 +++++++--
10 files changed, 162 insertions(+), 13 deletions(-)
create mode 100644 jdav_web/finance/migrations/0009_statement_ljp_to.py
diff --git a/jdav_web/finance/admin.py b/jdav_web/finance/admin.py
index 95f8c95..ea4ff77 100644
--- a/jdav_web/finance/admin.py
+++ b/jdav_web/finance/admin.py
@@ -100,7 +100,7 @@ class StatementUnSubmittedAdmin(CommonAdminMixin, admin.ModelAdmin):
memberlist=memberlist,
object=memberlist,
participant_count=memberlist.participant_count,
- ljp_contributions=memberlist.potential_ljp_contributions,
+ ljp_contributions=memberlist.payable_ljp_contributions,
total_relative_costs=memberlist.total_relative_costs,
**memberlist.statement.template_context())
return render(request, 'admin/freizeit_finance_overview.html', context=context)
@@ -269,6 +269,7 @@ class StatementSubmittedAdmin(admin.ModelAdmin):
title=_('View submitted statement'),
opts=self.opts,
statement=statement,
+ settings=settings,
transaction_issues=statement.transaction_issues,
**statement.template_context())
diff --git a/jdav_web/finance/locale/de/LC_MESSAGES/django.po b/jdav_web/finance/locale/de/LC_MESSAGES/django.po
index 13199ba..4cc29ca 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-03-27 23:35+0100\n"
+"POT-Creation-Date: 2025-04-04 01:07+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
@@ -207,6 +207,16 @@ msgstr ""
"Die Person, die die Übernachtungs- und Fahrtkostenzuschüsse erhalten soll. "
"Dies ist in der Regel die Person, die sie bezahlt hat."
+#: finance/models.py
+msgid "Pay ljp contributions to"
+msgstr "LJP-Zuschüsse auszahlen an"
+
+#: finance/models.py
+msgid ""
+"The person that should receive the ljp contributions for the participants. "
+"Should be only selected if an ljp request was submitted."
+msgstr "Die Person, die die LJP-Zuschüsse für die Teilnehmenden erhalten soll. Nur auswählen, wenn ein LJP-Antrag abgegeben wird."
+
#: finance/models.py
msgid "Price per night"
msgstr "Preis pro Nacht"
@@ -266,6 +276,11 @@ msgstr "Aufwandsentschädigung für %(excu)s"
msgid "Night and travel costs for %(excu)s"
msgstr "Übernachtungs- und Fahrtkosten für %(excu)s"
+#: finance/models.py
+#, python-format
+msgid "LJP-Contribution %(excu)s"
+msgstr "LJP-Zuschuss %(excu)s"
+
#: finance/models.py finance/templates/admin/overview_submitted_statement.html
msgid "Total"
msgstr "Gesamtbetrag"
@@ -505,6 +520,22 @@ msgstr ""
"Keine Empfänger*innen für Sektionszuschüsse angegeben. Es werden daher keine "
"Sektionszuschüsse ausbezahlt."
+#: finance/templates/admin/overview_submitted_statement.html
+#, python-format
+msgid ""
+" The youth leaders have documented interventions worth of "
+"%(total_seminar_days)s seminar \n"
+"days for %(participant_count)s eligible participants. Taking into account "
+"the maximum contribution quota \n"
+"of 90%% and possible taxes (%(ljp_tax)s%%), this results in a total of "
+"%(paid_ljp_contributions)s€. \n"
+"Once their proposal was approved, the ljp contributions of should be paid to:"
+msgstr "Jugendleiter*innen haben Lerneinheiten für insgesamt %(total_seminar_days)s "
+"Seminartage und für %(participant_count)s Teilnehmende dokumentiert. Unter Einbezug "
+"der maximalen Förderquote von 90%% und möglichen Steuern (%(ljp_tax)s%%), ergibt sich "
+"ein auszuzahlender Betrag von %(paid_ljp_contributions)s€. "
+"Sobald der LJP-Antrag geprüft ist, können LJP-Zuschüsse ausbezahlt 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/migrations/0009_statement_ljp_to.py b/jdav_web/finance/migrations/0009_statement_ljp_to.py
new file mode 100644
index 0000000..fc13323
--- /dev/null
+++ b/jdav_web/finance/migrations/0009_statement_ljp_to.py
@@ -0,0 +1,20 @@
+# Generated by Django 4.2.20 on 2025-04-03 21:04
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('members', '0039_membertraining_certificate_attendance'),
+ ('finance', '0008_alter_statement_allowance_to_and_more'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='statement',
+ name='ljp_to',
+ field=models.ForeignKey(blank=True, help_text='The person that should receive the ljp contributions for the participants. Should be only selected if an ljp request was submitted.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='receives_ljp_for_statements', to='members.member', verbose_name='Pay ljp contributions to'),
+ ),
+ ]
diff --git a/jdav_web/finance/models.py b/jdav_web/finance/models.py
index b4a770c..83905d7 100644
--- a/jdav_web/finance/models.py
+++ b/jdav_web/finance/models.py
@@ -70,6 +70,13 @@ class Statement(CommonModel):
related_name='receives_subsidy_for_statements',
help_text=_('The person that should receive the subsidy for night and travel costs. Typically the person who paid for them.'))
+ ljp_to = models.ForeignKey(Member, verbose_name=_('Pay ljp contributions to'),
+ null=True,
+ blank=True,
+ on_delete=models.SET_NULL,
+ related_name='receives_ljp_for_statements',
+ help_text=_('The person that should receive the ljp contributions for the participants. Should be only selected if an ljp request was submitted.'))
+
night_cost = models.DecimalField(verbose_name=_('Price per night'), default=0, decimal_places=2, max_digits=5)
submitted = models.BooleanField(verbose_name=_('Submitted'), default=False)
@@ -128,6 +135,8 @@ class Statement(CommonModel):
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))
+ if self.ljp_to:
+ needed_paiments.append((self.ljp_to, self.paid_ljp_contributions))
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])))
@@ -242,6 +251,10 @@ class Statement(CommonModel):
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()
+ if self.ljp_to:
+ ref = _("LJP-Contribution %(excu)s") % {'excu': self.excursion.name}
+ Transaction(statement=self, member=self.ljp_to, amount=self.paid_ljp_contributions, confirmed=False, reference=ref).save()
+
return True
def reduce_transactions(self):
@@ -385,10 +398,18 @@ class Statement(CommonModel):
return 0
else:
return self.excursion.approved_staff_count
+
+ @property
+ def paid_ljp_contributions(self):
+ if hasattr(self.excursion, 'ljpproposal') and self.ljp_to:
+ return cvt_to_decimal((1-settings.LJP_TAX) * min(settings.LJP_CONTRIBUTION_PER_DAY * self.excursion.ljp_participant_count * self.excursion.ljp_duration,
+ 0.9 * float(self.total_bills_theoretic) + float(self.total_staff)))
+ else:
+ return 0
@property
def total(self):
- return self.total_bills + self.total_staff
+ return self.total_bills + self.total_staff + self.paid_ljp_contributions
@property
def total_theoretic(self):
@@ -432,6 +453,11 @@ class Statement(CommonModel):
'total_subsidies': self.total_subsidies,
'subsidy_to': self.subsidy_to,
'allowance_to': self.allowance_to,
+ 'paid_ljp_contributions': self.paid_ljp_contributions,
+ 'ljp_to': self.ljp_to,
+ 'participant_count': self.excursion.participant_count,
+ 'total_seminar_days': self.excursion.total_seminar_days,
+ 'ljp_tax': settings.LJP_TAX * 100,
}
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 e6196de..4cf6b99 100644
--- a/jdav_web/finance/templates/admin/overview_submitted_statement.html
+++ b/jdav_web/finance/templates/admin/overview_submitted_statement.html
@@ -113,6 +113,25 @@
{% blocktrans %}No receivers of the subsidies were provided. Subsidies will not be used.{% endblocktrans %}
{% endif %}
+{% if statement.ljp_to %}
+
+{% blocktrans %} The youth leaders have documented interventions worth of {{ total_seminar_days }} seminar
+days for {{ participant_count }} eligible participants. Taking into account the maximum contribution quota
+of 90% and possible taxes ({{ ljp_tax }}%), this results in a total of {{ paid_ljp_contributions }}€.
+Once their proposal was approved, the ljp contributions of should be paid to:{% endblocktrans %}
+
+ |
+ | {% trans "IBAN valid" %} |
+
+
+ | {{ statement.ljp_to.name }} |
+ {{ statement.ljp_to.iban_valid|render_bool }} |
+
+
+
+{% endif %}
+
+
{% endif %}
{% trans "Total" %}
diff --git a/jdav_web/finance/templates/finance/statement_summary.tex b/jdav_web/finance/templates/finance/statement_summary.tex
index 5191119..f76b364 100644
--- a/jdav_web/finance/templates/finance/statement_summary.tex
+++ b/jdav_web/finance/templates/finance/statement_summary.tex
@@ -71,6 +71,16 @@ Zuschüsse und Aufwandsentschädigung werden wie folgt abgerufen:
\end{itemize}
{% else %}
\noindent Für die vorliegende Ausfahrt sind keine Jugendleiter*innen anspruchsberechtigt für Zuschüsse oder Aufwandsentschädigung.
+
+{% endif %}
+
+{% if statement.ljp_to %}
+\noindent\textbf{LJP-Zuschüsse}
+
+\noindent Der LJP-Zuschuss für die Teilnehmenden in Höhe von {{ statement.paid_ljp_contributions|esc_all }} € wird überwiesen an:
+{{ statement.ljp_to.name|esc_all }} Dieser Zuschuss wird aus Landesmitteln gewährt und ist daher
+in der Ausgabenübersicht gesondert aufgeführt.
+
{% endif %}
{% else %}
@@ -104,11 +114,16 @@ Zuschüsse und Aufwandsentschädigung werden wie folgt abgerufen:
{% endfor %}
{% endif %}
{% if statement.subsidy_to %}
- \multicolumn{2}{l}{Zuschuss Übernachtung und Anreise für alle Jugendleiter*innen} & {{ statement.subsidy_to.name }} & {{ statement.total_subsidies}} €\\
+ \multicolumn{2}{l}{Zuschuss Übernachtung und Anreise für alle Jugendleiter*innen} & {{ statement.subsidy_to.name|esc_all }} & {{ statement.total_subsidies }} €\\
{% endif %}
\midrule
- \multicolumn{3}{l}{\textbf{Summe Zuschüsse und Aufwandsentschädigung}} & \textbf{ {{ statement.total_staff }} }€\\
+ \multicolumn{3}{l}{\textbf{Summe Zuschüsse und Aufwandsentschädigung Jugendleitende}} & \textbf{ {{ statement.total_staff }} }€\\
{%endif %}
+{% if statement.ljp_to %}
+ \midrule
+ LJP-Zuschuss für die Teilnehmenden && {{ statement.ljp_to.name|esc_all }} & {{ statement.paid_ljp_contributions|esc_all }} €\\
+
+{% endif %}
{% if statement.bills_covered and excursion.approved_staff_count > 0 %}
\midrule
\textbf{Gesamtsumme}& & & \textbf{ {{ statement.total }} }€\\
diff --git a/jdav_web/jdav_web/settings/local.py b/jdav_web/jdav_web/settings/local.py
index 20c8c13..7d7ebcc 100644
--- a/jdav_web/jdav_web/settings/local.py
+++ b/jdav_web/jdav_web/settings/local.py
@@ -20,6 +20,7 @@ DIGITAL_MAIL = get_var('section', 'digital_mail', default='bar@example.org')
V32_HEAD_ORGANISATION = get_var('LJP', 'v32_head_organisation', default='not configured')
LJP_CONTRIBUTION_PER_DAY = get_var('LJP', 'contribution_per_day', default=25)
+LJP_TAX = get_var('LJP', 'tax', default=0)
# echo
diff --git a/jdav_web/members/admin.py b/jdav_web/members/admin.py
index ea45863..e4822df 100644
--- a/jdav_web/members/admin.py
+++ b/jdav_web/members/admin.py
@@ -898,10 +898,11 @@ class StatementOnListForm(forms.ModelForm):
# of subsidies and allowance
self.fields['allowance_to'].queryset = excursion.jugendleiter.all()
self.fields['subsidy_to'].queryset = excursion.jugendleiter.all()
+ self.fields['ljp_to'].queryset = excursion.jugendleiter.all()
class Meta:
model = Statement
- fields = ['night_cost', 'allowance_to', 'subsidy_to']
+ fields = ['night_cost', 'allowance_to', 'subsidy_to', 'ljp_to']
def clean(self):
"""Check if the `allowance_to` and `subsidy_to` fields are compatible with
@@ -922,7 +923,7 @@ class StatementOnListInline(CommonAdminInlineMixin, nested_admin.NestedStackedIn
extra = 1
description = _('Please list here all expenses in relation with this excursion and upload relevant bills. These have to be permanently stored for the application of LJP contributions. The short descriptions are used in the seminar report cost overview (possible descriptions are e.g. food, material, etc.).')
sortable_options = []
- fields = ['night_cost', 'allowance_to', 'subsidy_to']
+ fields = ['night_cost', 'allowance_to', 'subsidy_to', 'ljp_to']
inlines = [BillOnExcursionInline]
form = StatementOnListForm
@@ -1238,8 +1239,7 @@ class FreizeitAdmin(CommonAdminMixin, nested_admin.NestedModelAdmin):
opts=self.opts,
memberlist=memberlist,
object=memberlist,
- participant_count=memberlist.participant_count,
- ljp_contributions=memberlist.potential_ljp_contributions,
+ ljp_contributions=memberlist.payable_ljp_contributions,
total_relative_costs=memberlist.total_relative_costs,
**memberlist.statement.template_context())
return render(request, 'admin/freizeit_finance_overview.html', context=context)
diff --git a/jdav_web/members/locale/de/LC_MESSAGES/django.po b/jdav_web/members/locale/de/LC_MESSAGES/django.po
index 9b46b2b..c232828 100644
--- a/jdav_web/members/locale/de/LC_MESSAGES/django.po
+++ b/jdav_web/members/locale/de/LC_MESSAGES/django.po
@@ -1345,6 +1345,26 @@ msgstr ""
msgid "LJP contributions"
msgstr "LJP Zuschüsse"
+#: members/templates/admin/freizeit_finance_overview.html
+#, python-format
+msgid ""
+"By submitting the given seminar report, you will receive LJP contributions. "
+"You have\n"
+" documented interventions worth of %(total_seminar_days)s seminar days "
+"for %(participant_count)s participants.\n"
+" This results in a total contribution of %(ljp_contributions)s€. To "
+"receive them, you need to \n"
+" submit the LJP-Proposal within 3 weeks after your excursion and have it "
+"approved by the finance office. "
+msgstr ""
+"Wenn du den erstellten LJP-Antrag einreichst, erhältst du LJP-Zuschüsse.Du "
+"hast Lehreinheiten für insgesamt %(total_seminar_days)s Seminartage und für "
+"%(participant_count)s Teilnehmende dokumentiert.\n"
+" Daraus ergibt sich ein auszahlbarer LJP-Zuschuss von "
+"%(ljp_contributions)s€. Um den zu erhalten, musst du den LJP-Antrag "
+"innerhalb von 3 Wochen nach de Ausfahrt beim Jugendreferat einreichen und "
+"formal genehmigt bekommen."
+
#: members/templates/admin/freizeit_finance_overview.html
#, python-format
msgid ""
@@ -1352,13 +1372,17 @@ msgid ""
"case,\n"
"you may obtain up to 25€ times %(duration)s days for %(participant_count)s "
"participants but only up to\n"
-"90%% of the total costs. This results in a total of %(ljp_contributions)s€."
+"90%% of the total costs. This results in a total of %(ljp_contributions)s€. "
+"If you have created a seminar report, you need to specify who should receive "
+"the contributions in order to make use of them."
msgstr ""
"Indem du einen Seminarbericht anfertigst, kannst du Landesjugendplan (LJP) "
"Zuschüsse beantragen. In diesem Fall kannst du bis zu 25€ mal %(duration)s "
"Tage für %(participant_count)s Teilnehmende, aber nicht mehr als 90%% der "
"Gesamtausgaben erhalten. Das resultiert in einem Gesamtzuschuss von "
-"%(ljp_contributions)s€."
+"%(ljp_contributions)s€. Wenn du schon einen Seminarbericht erstellt hast, "
+"musst du im Tab 'Abrechnungen' noch angeben, an wen die LJP-Zuschüsse ausgezahlt "
+"werden sollen."
#: members/templates/admin/freizeit_finance_overview.html
msgid "Summary"
diff --git a/jdav_web/members/templates/admin/freizeit_finance_overview.html b/jdav_web/members/templates/admin/freizeit_finance_overview.html
index 3a68dfe..a7fd43c 100644
--- a/jdav_web/members/templates/admin/freizeit_finance_overview.html
+++ b/jdav_web/members/templates/admin/freizeit_finance_overview.html
@@ -132,12 +132,24 @@ cost plan!
{% trans "LJP contributions" %}
+{% if memberlist.statement.ljp_to %}
+
+
+ {% blocktrans %}By submitting the given seminar report, you will receive LJP contributions. You have
+ documented interventions worth of {{ total_seminar_days }} seminar days for {{ participant_count }} participants.
+ This results in a total contribution of {{ ljp_contributions }}€. To receive them, you need to
+ submit the LJP-Proposal within 3 weeks after your excursion and have it approved by the finance office. {% endblocktrans %}
+
+
+{% else %}
{% blocktrans %}By submitting a seminar report, you may apply for LJP contributions. In this case,
you may obtain up to 25€ times {{ duration }} days for {{ participant_count }} participants but only up to
-90% of the total costs. This results in a total of {{ ljp_contributions }}€.{% endblocktrans %}
+90% of the total costs. This results in a total of {{ ljp_contributions }}€. If you have created a seminar report, you need to specify who should receive the contributions in order to make use of them.{% endblocktrans %}
+{% endif %}
+
{% trans "Summary" %}
@@ -163,7 +175,7 @@ you may obtain up to 25€ times {{ duration }} days for {{ participant_count }}
|
- {% trans "Potential LJP contributions" %}
+ {% if memberlist.statement.ljp_to %}{% trans "LJP contributions" %}{% else %}{% trans "Potential LJP contributions" %}{% endif %}
|
-{{ ljp_contributions }}€
--
2.38.4
From 9bd2b42ba8a34d4aa95b9635761e634e5f16b280 Mon Sep 17 00:00:00 2001
From: mariusrklein <47218379+mariusrklein@users.noreply.github.com>
Date: Sun, 6 Apr 2025 14:26:01 +0200
Subject: [PATCH 08/18] fix: abstraction of bill calculation
---
jdav_web/finance/models.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/jdav_web/finance/models.py b/jdav_web/finance/models.py
index 83905d7..86d369a 100644
--- a/jdav_web/finance/models.py
+++ b/jdav_web/finance/models.py
@@ -282,7 +282,7 @@ class Statement(CommonModel):
@property
def total_bills(self):
- return sum([bill.amount for bill in self.bill_set.all() if bill.costs_covered])
+ return sum([bill.amount for bill in self.bills_covered])
@property
def bills_covered(self):
--
2.38.4
From 81f24da1acaf66c2fc4073c2bfcc6c8d142df75a Mon Sep 17 00:00:00 2001
From: mariusrklein <47218379+mariusrklein@users.noreply.github.com>
Date: Sun, 6 Apr 2025 16:03:33 +0200
Subject: [PATCH 09/18] fix: verbosity of contributions in finance overview and
summary statement
---
.../finance/locale/de/LC_MESSAGES/django.po | 43 +++++++++++++++----
.../admin/overview_submitted_statement.html | 41 +++++++++++++++++-
.../templates/finance/statement_summary.tex | 2 +-
3 files changed, 76 insertions(+), 10 deletions(-)
diff --git a/jdav_web/finance/locale/de/LC_MESSAGES/django.po b/jdav_web/finance/locale/de/LC_MESSAGES/django.po
index 4cc29ca..26c7c24 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-04-04 01:07+0200\n"
+"POT-Creation-Date: 2025-04-06 15:55+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
@@ -215,7 +215,9 @@ msgstr "LJP-Zuschüsse auszahlen an"
msgid ""
"The person that should receive the ljp contributions for the participants. "
"Should be only selected if an ljp request was submitted."
-msgstr "Die Person, die die LJP-Zuschüsse für die Teilnehmenden erhalten soll. Nur auswählen, wenn ein LJP-Antrag abgegeben wird."
+msgstr ""
+"Die Person, die die LJP-Zuschüsse für die Teilnehmenden erhalten soll. Nur "
+"auswählen, wenn ein LJP-Antrag abgegeben wird."
#: finance/models.py
msgid "Price per night"
@@ -281,7 +283,7 @@ msgstr "Übernachtungs- und Fahrtkosten für %(excu)s"
msgid "LJP-Contribution %(excu)s"
msgstr "LJP-Zuschuss %(excu)s"
-#: finance/models.py finance/templates/admin/overview_submitted_statement.html
+#: finance/models.py
msgid "Total"
msgstr "Gesamtbetrag"
@@ -530,11 +532,36 @@ msgid ""
"of 90%% and possible taxes (%(ljp_tax)s%%), this results in a total of "
"%(paid_ljp_contributions)s€. \n"
"Once their proposal was approved, the ljp contributions of should be paid to:"
-msgstr "Jugendleiter*innen haben Lerneinheiten für insgesamt %(total_seminar_days)s "
-"Seminartage und für %(participant_count)s Teilnehmende dokumentiert. Unter Einbezug "
-"der maximalen Förderquote von 90%% und möglichen Steuern (%(ljp_tax)s%%), ergibt sich "
-"ein auszuzahlender Betrag von %(paid_ljp_contributions)s€. "
-"Sobald der LJP-Antrag geprüft ist, können LJP-Zuschüsse ausbezahlt werden an:"
+msgstr ""
+"Jugendleiter*innen haben Lerneinheiten für insgesamt %(total_seminar_days)s "
+"Seminartage und für %(participant_count)s Teilnehmende dokumentiert. Unter "
+"Einbezug der maximalen Förderquote von 90%% und möglichen Steuern "
+"(%(ljp_tax)s%%), ergibt sich ein auszuzahlender Betrag von "
+"%(paid_ljp_contributions)s€. Sobald der LJP-Antrag geprüft ist, können LJP-"
+"Zuschüsse ausbezahlt werden an:"
+
+#: finance/templates/admin/overview_submitted_statement.html
+msgid "Summary"
+msgstr "Zusammenfassung"
+
+#: finance/templates/admin/overview_submitted_statement.html
+msgid "Covered bills"
+msgstr "Übernommene Ausgaben"
+
+#: finance/templates/admin/overview_submitted_statement.html
+msgid "Allowance"
+msgstr "Aufwandsentschädigung"
+
+#: finance/templates/admin/overview_submitted_statement.html
+msgid "Contributions by the association"
+msgstr "Sektionszuschüsse"
+
+
+#: finance/templates/admin/overview_submitted_statement.html
+#, fuzzy
+#| msgid "Pay ljp contributions to"
+msgid "ljp contributions"
+msgstr "LJP-Zuschüsse auszahlen an"
#: finance/templates/admin/overview_submitted_statement.html
#, python-format
diff --git a/jdav_web/finance/templates/admin/overview_submitted_statement.html b/jdav_web/finance/templates/admin/overview_submitted_statement.html
index 4cf6b99..0bab00d 100644
--- a/jdav_web/finance/templates/admin/overview_submitted_statement.html
+++ b/jdav_web/finance/templates/admin/overview_submitted_statement.html
@@ -134,7 +134,46 @@ Once their proposal was approved, the ljp contributions of should be paid to:{%
{% endif %}
-{% trans "Total" %}
+
+{% endif %}
+
+{% trans "Summary" %}
+
+
+
+
+ |
+ {% trans "Covered bills" %}
+ |
+
+ {{ total_bills }}€
+ |
+
+
+ |
+ {% trans "Allowance" %}
+ |
+
+ {{ total_allowance }}€
+ |
+
+
+ |
+ {% trans "Contributions by the association" %}
+ |
+
+ {{ total_subsidies_theoretical }}€
+ |
+
+
+ |
+ {% trans "ljp contributions" %}
+ |
+
+ {{ paid_ljp_contributions }}€
+ |
+
+
{% blocktrans %}This results in a total amount of {{ total }}€{% endblocktrans %}
diff --git a/jdav_web/finance/templates/finance/statement_summary.tex b/jdav_web/finance/templates/finance/statement_summary.tex
index f76b364..ee37f02 100644
--- a/jdav_web/finance/templates/finance/statement_summary.tex
+++ b/jdav_web/finance/templates/finance/statement_summary.tex
@@ -124,7 +124,7 @@ in der Ausgabenübersicht gesondert aufgeführt.
LJP-Zuschuss für die Teilnehmenden && {{ statement.ljp_to.name|esc_all }} & {{ statement.paid_ljp_contributions|esc_all }} €\\
{% endif %}
-{% if statement.bills_covered and excursion.approved_staff_count > 0 %}
+{% if statement.ljp_to or statement.bills_covered and excursion.approved_staff_count > 0 %}
\midrule
\textbf{Gesamtsumme}& & & \textbf{ {{ statement.total }} }€\\
{% endif %}
--
2.38.4
From 1b8627588b5ea55789b4f20458ebf49cb712ec66 Mon Sep 17 00:00:00 2001
From: Christian Merten
Date: Sun, 6 Apr 2025 16:59:31 +0200
Subject: [PATCH 10/18] fix whitespace, path resolution
---
jdav_web/finance/admin.py | 25 +++++++++++--------
.../finance/locale/de/LC_MESSAGES/django.po | 22 ++++++++++++----
.../admin/overview_submitted_statement.html | 3 ---
3 files changed, 32 insertions(+), 18 deletions(-)
diff --git a/jdav_web/finance/admin.py b/jdav_web/finance/admin.py
index ea4ff77..ed70b8f 100644
--- a/jdav_web/finance/admin.py
+++ b/jdav_web/finance/admin.py
@@ -91,7 +91,7 @@ class StatementUnSubmittedAdmin(CommonAdminMixin, admin.ModelAdmin):
messages.success(request,
_("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)))
-
+
if statement.excursion:
memberlist = statement.excursion
context = dict(self.admin_site.each_context(request),
@@ -120,16 +120,16 @@ class TransactionOnSubmittedStatementInline(admin.TabularInline):
}
readonly_fields = ['text_length_warning']
extra = 0
-
+
def text_length_warning(self, obj):
"""Display reference length, warn if exceeds 140 characters."""
len_reference = len(obj.reference)
len_string = f"{len_reference}/140"
if len_reference > 140:
return mark_safe(f'{len_string}')
-
+
return len_string
- text_length_warning.short_description = "Länge"
+ text_length_warning.short_description = _("Length")
class BillOnSubmittedStatementInline(BillOnStatementInline):
@@ -213,8 +213,9 @@ class StatementSubmittedAdmin(admin.ModelAdmin):
messages.success(request,
_("Successfully confirmed %(name)s. I hope you executed the associated transactions, I wont remind you again.")
% {'name': str(statement)})
+ download_link = reverse('admin:finance_statementconfirmed_summary', args=(statement.pk,))
messages.success(request,
- mark_safe(f"Hier kannst du den Abrechnungsbeleg herunterladen.")) #TODO: nice path resolution
+ mark_safe(_("You can download a receipt.") % {'link': download_link}))
return HttpResponseRedirect(reverse('admin:%s_%s_changelist' % (self.opts.app_label, self.opts.model_name)))
if "confirm" in request.POST:
res = statement.validity
@@ -351,21 +352,25 @@ class StatementConfirmedAdmin(admin.ModelAdmin):
statement=statement)
return render(request, 'admin/unconfirm_statement.html', context=context)
-
+
def statement_summary_view(self, request, object_id):
- statement = StatementConfirmed.objects.get(pk=object_id)
-
+ try:
+ statement = StatementConfirmed.objects.get(pk=object_id)
+ except StatementConfirmed.DoesNotExist:
+ messages.error(request, _('Statement not found.'))
+ return HttpResponseRedirect(reverse('admin:%s_%s_changelist' % (self.opts.app_label, self.opts.model_name)))
+
if not statement.confirmed:
messages.error(request,
_("%(name)s is not yet confirmed.") % {'name': str(statement)})
return HttpResponseRedirect(reverse('admin:%s_%s_change' % (self.opts.app_label, self.opts.model_name), args=(statement.pk,)))
excursion = statement.excursion
context = dict(statement=statement.template_context(), excursion=excursion, settings=settings)
-
+
pdf_filename = f"{excursion.code}_{excursion.name}_Zuschussbeleg" if excursion else f"Abrechnungsbeleg"
attachments = [bill.proof.path for bill in statement.bills_covered]
return render_tex_with_attachments(pdf_filename, 'finance/statement_summary.tex', context, attachments)
-
+
statement_summary_view.short_description = _('Download summary')
diff --git a/jdav_web/finance/locale/de/LC_MESSAGES/django.po b/jdav_web/finance/locale/de/LC_MESSAGES/django.po
index 26c7c24..e3377b4 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-04-06 15:55+0200\n"
+"POT-Creation-Date: 2025-04-06 16:58+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
@@ -40,6 +40,10 @@ msgstr "Kostenübersicht"
msgid "Submit statement"
msgstr "Rechnung einreichen"
+#: finance/admin.py
+msgid "Length"
+msgstr "Länge"
+
#: finance/admin.py
#, python-format
msgid "%(name)s is not yet submitted."
@@ -61,6 +65,13 @@ msgstr ""
"Erfolgreich %(name)s abgewickelt. Ich hoffe du hast die zugehörigen "
"Überweisungen ausgeführt, ich werde dich nicht nochmal erinnern."
+#: finance/admin.py
+#, python-format
+msgid "You can download a receipt."
+msgstr ""
+"Hier kannst du den Abrechnungsbeleg herunterladen."
+
#: finance/admin.py
msgid "Statement confirmed"
msgstr "Abrechnung abgewickelt"
@@ -152,6 +163,10 @@ msgstr ""
msgid "Unconfirm statement"
msgstr "Bestätigung zurücknehmen"
+#: finance/admin.py
+msgid "Statement not found."
+msgstr "Abrechnung existiert nicht."
+
#: finance/admin.py
msgid "Download summary"
msgstr "Beleg herunterladen"
@@ -556,12 +571,9 @@ msgstr "Aufwandsentschädigung"
msgid "Contributions by the association"
msgstr "Sektionszuschüsse"
-
#: finance/templates/admin/overview_submitted_statement.html
-#, fuzzy
-#| msgid "Pay ljp contributions to"
msgid "ljp contributions"
-msgstr "LJP-Zuschüsse auszahlen an"
+msgstr "LJP-Zuschüsse"
#: finance/templates/admin/overview_submitted_statement.html
#, python-format
diff --git a/jdav_web/finance/templates/admin/overview_submitted_statement.html b/jdav_web/finance/templates/admin/overview_submitted_statement.html
index 0bab00d..309fa36 100644
--- a/jdav_web/finance/templates/admin/overview_submitted_statement.html
+++ b/jdav_web/finance/templates/admin/overview_submitted_statement.html
@@ -132,9 +132,6 @@ Once their proposal was approved, the ljp contributions of should be paid to:{%
{% endif %}
-{% endif %}
-
-
{% endif %}
{% trans "Summary" %}
--
2.38.4
From 4041f491a8fbf9f7dca4f98c37a7963489d0a307 Mon Sep 17 00:00:00 2001
From: Christian Merten
Date: Sun, 6 Apr 2025 17:00:59 +0200
Subject: [PATCH 11/18] fix error when bill has no proof
---
jdav_web/finance/admin.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/jdav_web/finance/admin.py b/jdav_web/finance/admin.py
index ed70b8f..80b740a 100644
--- a/jdav_web/finance/admin.py
+++ b/jdav_web/finance/admin.py
@@ -368,7 +368,7 @@ class StatementConfirmedAdmin(admin.ModelAdmin):
context = dict(statement=statement.template_context(), excursion=excursion, settings=settings)
pdf_filename = f"{excursion.code}_{excursion.name}_Zuschussbeleg" if excursion else f"Abrechnungsbeleg"
- attachments = [bill.proof.path for bill in statement.bills_covered]
+ attachments = [bill.proof.path for bill in statement.bills_covered if bill.proof]
return render_tex_with_attachments(pdf_filename, 'finance/statement_summary.tex', context, attachments)
statement_summary_view.short_description = _('Download summary')
--
2.38.4
From c0af4a6bb80429febd7fc12b04763f16d251a59a Mon Sep 17 00:00:00 2001
From: mariusrklein <47218379+mariusrklein@users.noreply.github.com>
Date: Sun, 6 Apr 2025 17:34:31 +0200
Subject: [PATCH 12/18] feat(excursion/ljp): added check for proof on bills,
fixed finance overview
---
jdav_web/finance/models.py | 5 +++++
.../admin/overview_submitted_statement.html | 1 +
jdav_web/members/admin.py | 6 ++++++
.../members/locale/de/LC_MESSAGES/django.po | 20 +++++++++++++++----
.../admin/freizeit_finance_overview.html | 15 +++++++++++++-
5 files changed, 42 insertions(+), 5 deletions(-)
diff --git a/jdav_web/finance/models.py b/jdav_web/finance/models.py
index 86d369a..4b8c6ba 100644
--- a/jdav_web/finance/models.py
+++ b/jdav_web/finance/models.py
@@ -289,6 +289,11 @@ class Statement(CommonModel):
"""Returns the bills that are marked for reimbursement by the finance officer"""
return [bill for bill in self.bill_set.all() if bill.costs_covered]
+ @property
+ def bills_without_proof(self):
+ """Returns the bills that lack a proof file"""
+ return [bill for bill in self.bill_set.all() if not bill.proof]
+
@property
def total_bills_theoretic(self):
return sum([bill.amount for bill in self.bill_set.all()])
diff --git a/jdav_web/finance/templates/admin/overview_submitted_statement.html b/jdav_web/finance/templates/admin/overview_submitted_statement.html
index 309fa36..34d69f8 100644
--- a/jdav_web/finance/templates/admin/overview_submitted_statement.html
+++ b/jdav_web/finance/templates/admin/overview_submitted_statement.html
@@ -134,6 +134,7 @@ Once their proposal was approved, the ljp contributions of should be paid to:{%
{% endif %}
+
{% trans "Summary" %}
diff --git a/jdav_web/members/admin.py b/jdav_web/members/admin.py
index e4822df..c61fb6b 100644
--- a/jdav_web/members/admin.py
+++ b/jdav_web/members/admin.py
@@ -1230,6 +1230,12 @@ class FreizeitAdmin(CommonAdminMixin, nested_admin.NestedModelAdmin):
messages.error(request,
_("The configured recipients of the allowance don't match the regulations. Please correct this and try again."))
return HttpResponseRedirect(reverse('admin:%s_%s_change' % (self.opts.app_label, self.opts.model_name), args=(memberlist.pk,)))
+
+ if memberlist.statement.ljp_to and len(memberlist.statement.bills_without_proof) > 0:
+ messages.error(request,
+ _("The excursion is configured to claim LJP contributions. In that case, for all bills, a proof must be uploaded. Please correct this and try again."))
+ return HttpResponseRedirect(reverse('admin:%s_%s_change' % (self.opts.app_label, self.opts.model_name), args=(memberlist.pk,)))
+
memberlist.statement.submit(get_member(request))
messages.success(request,
_("Successfully submited statement. The finance department will notify you as soon as possible."))
diff --git a/jdav_web/members/locale/de/LC_MESSAGES/django.po b/jdav_web/members/locale/de/LC_MESSAGES/django.po
index c232828..ee41cf5 100644
--- a/jdav_web/members/locale/de/LC_MESSAGES/django.po
+++ b/jdav_web/members/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-04-06 14:02+0200\n"
+"POT-Creation-Date: 2025-04-06 17:30+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
@@ -417,6 +417,14 @@ msgstr ""
"Die ausgewählten Empfänger*innen der Aufwandsentschädigung stimmen nicht mit "
"den Richtlinien überein. Bitte korrigiere das und versuche es erneut. "
+#: members/admin.py
+msgid ""
+"The excursion is configured to claim LJP contributions. In that case, for "
+"all bills, a proof must be uploaded. Please correct this and try again."
+msgstr ""
+"Für die Ausfahrt werden LJP-Zuschüsse beantragt. Dafür müssen für alle Ausgaben Belege hochgeladen werden. "
+"Bitte lade für alle Ausgaben einen Beleg hoch und versuche es erneut. "
+
#: members/admin.py
msgid ""
"Successfully submited statement. The finance department will notify you as "
@@ -1357,7 +1365,7 @@ msgid ""
" submit the LJP-Proposal within 3 weeks after your excursion and have it "
"approved by the finance office. "
msgstr ""
-"Wenn du den erstellten LJP-Antrag einreichst, erhältst du LJP-Zuschüsse.Du "
+"Wenn du den erstellten LJP-Antrag einreichst, erhältst du LJP-Zuschüsse. Du "
"hast Lehreinheiten für insgesamt %(total_seminar_days)s Seminartage und für "
"%(participant_count)s Teilnehmende dokumentiert.\n"
" Daraus ergibt sich ein auszahlbarer LJP-Zuschuss von "
@@ -1365,6 +1373,10 @@ msgstr ""
"innerhalb von 3 Wochen nach de Ausfahrt beim Jugendreferat einreichen und "
"formal genehmigt bekommen."
+#: members/templates/admin/freizeit_finance_overview.html
+msgid "The LJP contributions are configured to be paid to:"
+msgstr "Die LJP-Zuschüsse werden ausgezahlt an:"
+
#: members/templates/admin/freizeit_finance_overview.html
#, python-format
msgid ""
@@ -1381,8 +1393,8 @@ msgstr ""
"Tage für %(participant_count)s Teilnehmende, aber nicht mehr als 90%% der "
"Gesamtausgaben erhalten. Das resultiert in einem Gesamtzuschuss von "
"%(ljp_contributions)s€. Wenn du schon einen Seminarbericht erstellt hast, "
-"musst du im Tab 'Abrechnungen' noch angeben, an wen die LJP-Zuschüsse ausgezahlt "
-"werden sollen."
+"musst du im Tab 'Abrechnungen' noch angeben, an wen die LJP-Zuschüsse "
+"ausgezahlt werden sollen."
#: members/templates/admin/freizeit_finance_overview.html
msgid "Summary"
diff --git a/jdav_web/members/templates/admin/freizeit_finance_overview.html b/jdav_web/members/templates/admin/freizeit_finance_overview.html
index a7fd43c..d3dca14 100644
--- a/jdav_web/members/templates/admin/freizeit_finance_overview.html
+++ b/jdav_web/members/templates/admin/freizeit_finance_overview.html
@@ -130,9 +130,9 @@ cost plan!
{% endif %}
-{% trans "LJP contributions" %}
{% if memberlist.statement.ljp_to %}
+{% trans "LJP contributions" %}
{% blocktrans %}By submitting the given seminar report, you will receive LJP contributions. You have
@@ -141,6 +141,19 @@ cost plan!
submit the LJP-Proposal within 3 weeks after your excursion and have it approved by the finance office. {% endblocktrans %}
+
+ {% blocktrans %}The LJP contributions are configured to be paid to:{% endblocktrans %}
+
+ |
+ | {% trans "IBAN valid" %} |
+
+
+ | {{ memberlist.statement.ljp_to.name }} |
+ {{ memberlist.statement.ljp_to.iban_valid|render_bool }} |
+
+
+
+
{% else %}
{% blocktrans %}By submitting a seminar report, you may apply for LJP contributions. In this case,
--
2.38.4
From 8e9e0f630c8a1beb3acb61b5ed29a1a616f46679 Mon Sep 17 00:00:00 2001
From: Christian Merten
Date: Sun, 6 Apr 2025 18:53:04 +0200
Subject: [PATCH 13/18] use new permission and existence decorator
---
jdav_web/finance/admin.py | 9 ++-------
1 file changed, 2 insertions(+), 7 deletions(-)
diff --git a/jdav_web/finance/admin.py b/jdav_web/finance/admin.py
index c5b071f..f9a9e2a 100644
--- a/jdav_web/finance/admin.py
+++ b/jdav_web/finance/admin.py
@@ -370,13 +370,8 @@ class StatementConfirmedAdmin(admin.ModelAdmin):
return render(request, 'admin/unconfirm_statement.html', context=context)
- def statement_summary_view(self, request, object_id):
- try:
- statement = StatementConfirmed.objects.get(pk=object_id)
- except StatementConfirmed.DoesNotExist:
- messages.error(request, _('Statement not found.'))
- return HttpResponseRedirect(reverse('admin:%s_%s_changelist' % (self.opts.app_label, self.opts.model_name)))
-
+ @decorate_statement_view(StatementConfirmed, perm='finance.may_manage_confirmed_statements')
+ def statement_summary_view(self, request, statement):
if not statement.confirmed:
messages.error(request,
_("%(name)s is not yet confirmed.") % {'name': str(statement)})
--
2.38.4
From 84ed08a20ba265c0467dd64b156e269a7abd6dea Mon Sep 17 00:00:00 2001
From: Christian Merten
Date: Sun, 6 Apr 2025 19:09:25 +0200
Subject: [PATCH 14/18] various whitespace fixes
---
jdav_web/finance/models.py | 10 ++++----
.../members/locale/de/LC_MESSAGES/django.po | 25 +++++++++----------
.../admin/freizeit_finance_overview.html | 10 ++++----
3 files changed, 22 insertions(+), 23 deletions(-)
diff --git a/jdav_web/finance/models.py b/jdav_web/finance/models.py
index 4b8c6ba..ba55e97 100644
--- a/jdav_web/finance/models.py
+++ b/jdav_web/finance/models.py
@@ -71,11 +71,11 @@ class Statement(CommonModel):
help_text=_('The person that should receive the subsidy for night and travel costs. Typically the person who paid for them.'))
ljp_to = models.ForeignKey(Member, verbose_name=_('Pay ljp contributions to'),
- null=True,
- blank=True,
- on_delete=models.SET_NULL,
- related_name='receives_ljp_for_statements',
- help_text=_('The person that should receive the ljp contributions for the participants. Should be only selected if an ljp request was submitted.'))
+ null=True,
+ blank=True,
+ on_delete=models.SET_NULL,
+ related_name='receives_ljp_for_statements',
+ help_text=_('The person that should receive the ljp contributions for the participants. Should be only selected if an ljp request was submitted.'))
night_cost = models.DecimalField(verbose_name=_('Price per night'), default=0, decimal_places=2, max_digits=5)
diff --git a/jdav_web/members/locale/de/LC_MESSAGES/django.po b/jdav_web/members/locale/de/LC_MESSAGES/django.po
index cc52f01..2e9381a 100644
--- a/jdav_web/members/locale/de/LC_MESSAGES/django.po
+++ b/jdav_web/members/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-04-06 17:30+0200\n"
+"POT-Creation-Date: 2025-04-06 18:57+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
@@ -422,8 +422,9 @@ msgid ""
"The excursion is configured to claim LJP contributions. In that case, for "
"all bills, a proof must be uploaded. Please correct this and try again."
msgstr ""
-"Für die Ausfahrt werden LJP-Zuschüsse beantragt. Dafür müssen für alle Ausgaben Belege hochgeladen werden. "
-"Bitte lade für alle Ausgaben einen Beleg hoch und versuche es erneut. "
+"Für die Ausfahrt werden LJP-Zuschüsse beantragt. Dafür müssen für alle "
+"Ausgaben Belege hochgeladen werden. Bitte lade für alle Ausgaben einen Beleg "
+"hoch und versuche es erneut. "
#: members/admin.py
msgid ""
@@ -1365,21 +1366,19 @@ msgstr "LJP Zuschüsse"
#: members/templates/admin/freizeit_finance_overview.html
#, python-format
msgid ""
-"By submitting the given seminar report, you will receive LJP contributions. "
-"You have\n"
-" documented interventions worth of %(total_seminar_days)s seminar days "
-"for %(participant_count)s participants.\n"
-" This results in a total contribution of %(ljp_contributions)s€. To "
-"receive them, you need to \n"
-" submit the LJP-Proposal within 3 weeks after your excursion and have it "
-"approved by the finance office. "
+"By submitting the given seminar report, you will receive LJP contributions.\n"
+"You have documented interventions worth of %(total_seminar_days)s seminar "
+"days for %(participant_count)s participants.\n"
+"This results in a total contribution of %(ljp_contributions)s€.\n"
+"To receive them, you need to submit the LJP-Proposal within 3 weeks after "
+"your excursion and have it approved by the finance office."
msgstr ""
"Wenn du den erstellten LJP-Antrag einreichst, erhältst du LJP-Zuschüsse. Du "
"hast Lehreinheiten für insgesamt %(total_seminar_days)s Seminartage und für "
"%(participant_count)s Teilnehmende dokumentiert.\n"
-" Daraus ergibt sich ein auszahlbarer LJP-Zuschuss von "
+"Daraus ergibt sich ein auszahlbarer LJP-Zuschuss von "
"%(ljp_contributions)s€. Um den zu erhalten, musst du den LJP-Antrag "
-"innerhalb von 3 Wochen nach de Ausfahrt beim Jugendreferat einreichen und "
+"innerhalb von 3 Wochen nach der Ausfahrt beim Jugendreferat einreichen und "
"formal genehmigt bekommen."
#: members/templates/admin/freizeit_finance_overview.html
diff --git a/jdav_web/members/templates/admin/freizeit_finance_overview.html b/jdav_web/members/templates/admin/freizeit_finance_overview.html
index d3dca14..aecc588 100644
--- a/jdav_web/members/templates/admin/freizeit_finance_overview.html
+++ b/jdav_web/members/templates/admin/freizeit_finance_overview.html
@@ -135,11 +135,11 @@ cost plan!
{% trans "LJP contributions" %}
- {% blocktrans %}By submitting the given seminar report, you will receive LJP contributions. You have
- documented interventions worth of {{ total_seminar_days }} seminar days for {{ participant_count }} participants.
- This results in a total contribution of {{ ljp_contributions }}€. To receive them, you need to
- submit the LJP-Proposal within 3 weeks after your excursion and have it approved by the finance office. {% endblocktrans %}
-
+{% blocktrans %}By submitting the given seminar report, you will receive LJP contributions.
+You have documented interventions worth of {{ total_seminar_days }} seminar days for {{ participant_count }} participants.
+This results in a total contribution of {{ ljp_contributions }}€.
+To receive them, you need to submit the LJP-Proposal within 3 weeks after your excursion and have it approved by the finance office.{% endblocktrans %}
+
{% blocktrans %}The LJP contributions are configured to be paid to:{% endblocktrans %}
--
2.38.4
From 2ede2040cea69e8f3279923ac14f28eac7c0923b Mon Sep 17 00:00:00 2001
From: Christian Merten
Date: Sun, 6 Apr 2025 19:11:52 +0200
Subject: [PATCH 15/18] fix diff
---
jdav_web/locale/de/LC_MESSAGES/django.po | 100 +----------------------
1 file changed, 1 insertion(+), 99 deletions(-)
diff --git a/jdav_web/locale/de/LC_MESSAGES/django.po b/jdav_web/locale/de/LC_MESSAGES/django.po
index 3cae0ca..1adcf4e 100644
--- a/jdav_web/locale/de/LC_MESSAGES/django.po
+++ b/jdav_web/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-04-06 18:50+0200\n"
+"POT-Creation-Date: 2025-04-06 19:10+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
@@ -135,104 +135,6 @@ msgstr ""
msgid "You entered a wrong password."
msgstr "Das eingegebene Passwort ist falsch."
-#: templates/admin/base.html
-#, fuzzy
-#| msgid "Welcome, "
-msgid "Welcome,"
-msgstr "Willkommen, "
-
-#: templates/admin/base.html
-#, fuzzy
-#| msgid "View on site"
-msgid "View site"
-msgstr "Auf der Website anzeigen"
-
-#: templates/admin/base.html
-#, fuzzy
-#| msgid "Authentication"
-msgid "Documentation"
-msgstr "Authentifizierung"
-
-#: templates/admin/base.html
-#, fuzzy
-#| msgid "Password"
-msgid "Change password"
-msgstr "Passwort"
-
-#: templates/admin/base.html
-msgid "Log out"
-msgstr ""
-
-#: templates/admin/base.html
-msgid "Home"
-msgstr ""
-
-#: templates/admin/base.html
-msgid "back"
-msgstr ""
-
-#: templates/admin/base.html
-#, fuzzy
-#| msgid "Authentication"
-msgid "Applications"
-msgstr "Authentifizierung"
-
-#: templates/admin/base.html
-#, fuzzy
-#| msgid "Generate SJR application"
-msgid "Hide applications"
-msgstr "SJR Antrag erstellen"
-
-#: templates/admin/base.html
-msgid "Show hidden"
-msgstr ""
-
-#: templates/admin/base.html
-msgid "Add bookmark"
-msgstr ""
-
-#: templates/admin/base.html
-msgid "Title"
-msgstr ""
-
-#: templates/admin/base.html
-msgid "URL"
-msgstr ""
-
-#: templates/admin/base.html
-msgid "Delete bookmark"
-msgstr ""
-
-#: templates/admin/base.html
-#, fuzzy
-#| msgid ""
-#| "Are you sure you want to delete the %(object_name)s "
-#| "\"%(escaped_object)s\"?"
-msgid "Are you sure want to delete this bookmark?"
-msgstr ""
-"Bist du sicher, dass du %(object_name)s \"%(escaped_object)s\" und alle "
-"davon abhängigen Objekte löschen möchtest? "
-
-#: templates/admin/base.html
-msgid "bookmarks"
-msgstr ""
-
-#: templates/admin/base.html
-msgid "Remove"
-msgstr ""
-
-#: templates/admin/base.html
-msgid "Search"
-msgstr ""
-
-#: templates/admin/base.html
-msgid "Application page"
-msgstr ""
-
-#: templates/admin/base.html
-msgid "current theme"
-msgstr ""
-
#: templates/admin/delete_confirmation.html
#, python-format
msgid ""
--
2.38.4
From 4f38b5bd3c089acae77c00875c2e12ff9e87c219 Mon Sep 17 00:00:00 2001
From: Christian Merten
Date: Sun, 6 Apr 2025 19:25:28 +0200
Subject: [PATCH 16/18] minimal simplification
---
jdav_web/members/models.py | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/jdav_web/members/models.py b/jdav_web/members/models.py
index eec124d..dd617f2 100644
--- a/jdav_web/members/models.py
+++ b/jdav_web/members/models.py
@@ -1278,14 +1278,14 @@ class Freizeit(CommonModel):
return sum([i.duration for i in self.ljpproposal.intervention_set.all()])
else:
return 0
-
+
@property
def total_seminar_days(self):
"""calculate seminar days based on intervention hours in every day"""
+ # TODO: add tests for this
if hasattr(self, 'ljpproposal'):
hours_per_day = (
- Intervention.objects
- .filter(ljp_proposal_id=self.ljpproposal.id)
+ self.ljpproposal.intervention_set
.annotate(day=TruncDate('date_start')) # Extract the date (without time)
.values('day') # Group by day
.annotate(total_duration=Sum('duration')) # Sum durations for each day
@@ -1302,17 +1302,17 @@ class Freizeit(CommonModel):
def ljp_duration(self):
"""calculate the duration in days for the LJP"""
return min(self.duration, self.total_seminar_days)
-
+
@property
def staff_count(self):
return self.jugendleiter.count()
-
+
@property
def staff_on_memberlist(self):
ps = set(map(lambda x: x.member, self.membersonlist.distinct()))
jls = set(self.jugendleiter.distinct())
return ps.intersection(jls)
-
+
@property
def staff_on_memberlist_count(self):
return len(self.staff_on_memberlist)
@@ -1322,7 +1322,7 @@ class Freizeit(CommonModel):
ps = set(map(lambda x: x.member, self.membersonlist.distinct()))
jls = set(self.jugendleiter.distinct())
return len(ps - jls)
-
+
@property
def head_count(self):
return self.staff_on_memberlist_count + self.participant_count
--
2.38.4
From 8929951212c9aae0c3ec4c3a1889b9cc8356d59a Mon Sep 17 00:00:00 2001
From: Christian Merten
Date: Sun, 6 Apr 2025 19:33:56 +0200
Subject: [PATCH 17/18] add total_allowance to context
---
jdav_web/finance/models.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/jdav_web/finance/models.py b/jdav_web/finance/models.py
index ba55e97..4783009 100644
--- a/jdav_web/finance/models.py
+++ b/jdav_web/finance/models.py
@@ -453,6 +453,7 @@ class Statement(CommonModel):
'transportation_per_yl': self.transportation_per_yl,
'total_per_yl': self.total_per_yl,
'total_staff': self.total_staff,
+ 'total_allowance': self.total_allowance,
'theoretical_total_staff': self.theoretical_total_staff,
'real_staff_count': self.real_staff_count,
'total_subsidies': self.total_subsidies,
--
2.38.4
From 1f5710829990a16fdec70a87d95e204557cf7cfa Mon Sep 17 00:00:00 2001
From: mariusrklein <47218379+mariusrklein@users.noreply.github.com>
Date: Sun, 6 Apr 2025 22:54:45 +0200
Subject: [PATCH 18/18] fixed ljp calculation
---
jdav_web/finance/models.py | 15 +++++++++++++--
.../admin/overview_submitted_statement.html | 2 +-
2 files changed, 14 insertions(+), 3 deletions(-)
diff --git a/jdav_web/finance/models.py b/jdav_web/finance/models.py
index 4783009..5765aef 100644
--- a/jdav_web/finance/models.py
+++ b/jdav_web/finance/models.py
@@ -298,6 +298,11 @@ class Statement(CommonModel):
def total_bills_theoretic(self):
return sum([bill.amount for bill in self.bill_set.all()])
+ @property
+ def total_bills_not_covered(self):
+ """Returns the sum of bills that are not marked for reimbursement by the finance officer"""
+ return sum([bill.amount for bill in self.bill_set.all()]) - self.total_bills
+
@property
def euro_per_km(self):
if self.excursion is None:
@@ -407,8 +412,13 @@ class Statement(CommonModel):
@property
def paid_ljp_contributions(self):
if hasattr(self.excursion, 'ljpproposal') and self.ljp_to:
- return cvt_to_decimal((1-settings.LJP_TAX) * min(settings.LJP_CONTRIBUTION_PER_DAY * self.excursion.ljp_participant_count * self.excursion.ljp_duration,
- 0.9 * float(self.total_bills_theoretic) + float(self.total_staff)))
+ return cvt_to_decimal(
+ min(
+ (1-settings.LJP_TAX) * settings.LJP_CONTRIBUTION_PER_DAY * self.excursion.ljp_participant_count * self.excursion.ljp_duration,
+ (1-settings.LJP_TAX) * 0.9 * (float(self.total_bills_not_covered) + float(self.total_staff) ),
+ float(self.total_bills_not_covered)
+ )
+ )
else:
return 0
@@ -457,6 +467,7 @@ class Statement(CommonModel):
'theoretical_total_staff': self.theoretical_total_staff,
'real_staff_count': self.real_staff_count,
'total_subsidies': self.total_subsidies,
+ 'total_allowance': self.total_allowance,
'subsidy_to': self.subsidy_to,
'allowance_to': self.allowance_to,
'paid_ljp_contributions': self.paid_ljp_contributions,
diff --git a/jdav_web/finance/templates/admin/overview_submitted_statement.html b/jdav_web/finance/templates/admin/overview_submitted_statement.html
index 34d69f8..0d97736 100644
--- a/jdav_web/finance/templates/admin/overview_submitted_statement.html
+++ b/jdav_web/finance/templates/admin/overview_submitted_statement.html
@@ -160,7 +160,7 @@ Once their proposal was approved, the ljp contributions of should be paid to:{%
{% trans "Contributions by the association" %}
|
- {{ total_subsidies_theoretical }}€
+ {{ total_subsidies }}€
|
--
2.38.4