feat(finance): org fee for old participants (#149)

Allow deducting a configurable organisational fee for participants older than 27 from subsidies. This is calculated per day and old participant.

Reviewed-on: #149
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>
pull/155/head
marius.klein 8 months ago committed by Christian Merten
parent 05f924cdef
commit 3250dc7089

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-04-06 18:46+0200\n"
"POT-Creation-Date: 2025-04-27 23:00+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -297,6 +297,10 @@ 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
msgid "reduced by org fee"
msgstr "reduziert um Org-Beitrag"
#: finance/models.py
#, python-format
msgid "LJP-Contribution %(excu)s"
@ -541,6 +545,27 @@ msgstr ""
"Keine Empfänger*innen für Sektionszuschüsse angegeben. Es werden daher keine "
"Sektionszuschüsse ausbezahlt."
#: finance/templates/admin/overview_submitted_statement.html
msgid "Org fee"
msgstr "Organisationsbeitrag"
#: finance/templates/admin/overview_submitted_statement.html
#, python-format
msgid ""
"Since overaged people where part of the excursion, an organisational fee of "
"%(org_fee)s€ per person per day has to be paid. This totals to "
"%(total_org_fee_theoretical)s€. This organisational fee will be accounted "
"against allowances and subsidies."
msgstr ""
"Da Personen über 27 an der Ausfahrt teilnehommen haben, wird ein "
"Organisationsbeitrag von %(org_fee)s€ pro Person und Tag fällig. Der Gesamtbetrag "
"von %(total_org_fee_theoretical)s€ wird mit Zuschüssen und "
"Aufwandsentschädigungen verrechnet."
#: finance/templates/admin/overview_submitted_statement.html
msgid "LJP contributions"
msgstr "LJP-Zuschüsse"
#: finance/templates/admin/overview_submitted_statement.html
#, python-format
msgid ""

@ -135,6 +135,11 @@ 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))
# only include org fee if either allowance or subsidy is claimed (part of the property)
if self.total_org_fee:
needed_paiments.append((self.org_fee_payant, -self.total_org_fee))
if self.ljp_to:
needed_paiments.append((self.ljp_to, self.paid_ljp_contributions))
@ -251,9 +256,15 @@ 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.total_org_fee:
# if no subsidy receiver is given but org fees have to be paid. Just pick one of allowance receivers
ref = _("reduced by org fee")
Transaction(statement=self, member=self.org_fee_payant, amount=-self.total_org_fee, 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()
Transaction(statement=self, member=self.ljp_to, amount=self.paid_ljp_contributions,
confirmed=False, reference=ref).save()
return True
@ -368,6 +379,24 @@ class Statement(CommonModel):
return cvt_to_decimal(self.total_staff / self.excursion.staff_count)
@property
def total_org_fee_theoretical(self):
"""participants older than 26.99 years need to pay a specified organisation fee per person per day."""
if self.excursion is None:
return 0
return cvt_to_decimal(settings.EXCURSION_ORG_FEE * self.excursion.duration * self.excursion.old_participant_count)
@property
def total_org_fee(self):
"""only calculate org fee if subsidies or allowances are claimed."""
if self.subsidy_to or self.allowances_paid > 0:
return self.total_org_fee_theoretical
return cvt_to_decimal(0)
@property
def org_fee_payant(self):
return self.subsidy_to if self.subsidy_to else self.allowance_to.all()[0]
@property
def total_subsidies(self):
"""
@ -379,6 +408,10 @@ class Statement(CommonModel):
else:
return cvt_to_decimal(0)
@property
def subsidies_paid(self):
return self.total_subsidies - self.total_org_fee
@property
def theoretical_total_staff(self):
"""
@ -393,6 +426,11 @@ class Statement(CommonModel):
"""
return self.total_allowance + self.total_subsidies
@property
def total_staff_paid(self):
return self.total_staff - self.total_org_fee
@property
def real_staff_count(self):
if self.excursion is None:
@ -428,7 +466,7 @@ class Statement(CommonModel):
@property
def total(self):
return self.total_bills + self.total_staff + self.paid_ljp_contributions
return self.total_bills + self.total_staff_paid + self.paid_ljp_contributions
@property
def total_theoretic(self):
@ -464,6 +502,7 @@ class Statement(CommonModel):
'allowances_paid': self.allowances_paid,
'nights_per_yl': self.nights_per_yl,
'allowance_per_yl': self.allowance_per_yl,
'total_allowance': self.total_allowance,
'transportation_per_yl': self.transportation_per_yl,
'total_per_yl': self.total_per_yl,
'total_staff': self.total_staff,
@ -480,6 +519,11 @@ class Statement(CommonModel):
'participant_count': self.excursion.participant_count,
'total_seminar_days': self.excursion.total_seminar_days,
'ljp_tax': settings.LJP_TAX * 100,
'total_org_fee_theoretical': self.total_org_fee_theoretical,
'total_org_fee': self.total_org_fee,
'old_participant_count': self.excursion.old_participant_count,
'total_staff_paid': self.total_staff_paid,
'org_fee': cvt_to_decimal(settings.EXCURSION_ORG_FEE),
}
return dict(context, **excursion_context)
else:

@ -109,11 +109,20 @@
</tr>
</table>
</p>
{% else %}
<p>{% blocktrans %}No receivers of the subsidies were provided. Subsidies will not be used.{% endblocktrans %}</p>
{% endif %}
{% if total_org_fee %}
<h3>{% trans "Org fee" %}</h3>
{% blocktrans %}Since overaged people where part of the excursion, an organisational fee of {{ org_fee }}€ per person per day has to be paid. This totals to {{ total_org_fee_theoretical }}€. This organisational fee will be accounted against allowances and subsidies.{% endblocktrans %}
{% endif %}
{% if statement.ljp_to %}
<h3>{% trans "LJP contributions" %}</h3>
<p>
{% 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
@ -163,6 +172,14 @@ Once their proposal was approved, the ljp contributions of should be paid to:{%
{{ total_subsidies }}€
</td>
</tr>
<tr>
<td>
{% trans "Org fee" %}
</td>
<td>
-{{ total_org_fee }}€
</td>
</tr>
<tr>
<td>
{% trans "ljp contributions" %}

@ -83,6 +83,12 @@ in der Ausgabenübersicht gesondert aufgeführt.
{% endif %}
{% if statement.total_org_fee %}
\noindent\textbf{Organisationsbeitrag}
\noindent An der Ausfahrt haben {{ statement.old_participant_count }} Personen teilgenommen, die 27 Jahre alt oder älter sind. Für sie wird pro Tag ein Organisationsbeitrag von {{ statement.org_fee }} € erhoben und mit den bezahlten Zuschüssen und Aufwandsentschädigungen verrechnet.
{% endif %}
{% else %}
\vspace{110pt}
{% endif %}
@ -116,8 +122,11 @@ in der Ausgabenübersicht gesondert aufgeführt.
{% if statement.subsidy_to %}
\multicolumn{2}{l}{Zuschuss Übernachtung und Anreise für alle Jugendleiter*innen} & {{ statement.subsidy_to.name|esc_all }} & {{ statement.total_subsidies }}\\
{% endif %}
{% if statement.total_org_fee %}
\multicolumn{2}{l}{abzüglich Organisationsbeitrag für {{ statement.old_participant_count }} Teilnehmende über 27 } & & -{{ statement.total_org_fee }}\\
{% endif %}
\midrule
\multicolumn{3}{l}{\textbf{Summe Zuschüsse und Aufwandsentschädigung Jugendleitende}} & \textbf{ {{ statement.total_staff }} }\\
\multicolumn{3}{l}{\textbf{Summe Zuschüsse und Aufwandsentschädigung Jugendleitende}} & \textbf{ {{ statement.total_staff_paid }} }\\
{%endif %}
{% if statement.ljp_to %}
\midrule

@ -55,6 +55,8 @@ DOMAIN = get_var('misc', 'domain', default='example.org')
ALLOWANCE_PER_DAY = get_var('finance', 'allowance_per_day', default=22)
MAX_NIGHT_COST = get_var('finance', 'max_night_cost', default=11)
EXCURSION_ORG_FEE = get_var('finance', 'org_fee', default=10)
# links
CLOUD_LINK = get_var('links', 'cloud', default='https://startpage.com')

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-04-15 22:36+0200\n"
"POT-Creation-Date: 2025-04-27 23:00+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -1379,6 +1379,24 @@ msgstr ""
"Keine Empfänger*innen für Sektionszuschüsse angegeben. Es werden daher keine "
"Sektionszuschüsse ausbezahlt."
#: members/templates/admin/freizeit_finance_overview.html
msgid "Org fee"
msgstr "Organisationsbeitrag"
#: members/templates/admin/freizeit_finance_overview.html
#, python-format
msgid ""
"Warning: %(old_participant_count)s participant(s) of the excursion are 27 or "
"older. For each of them, an organisation fee of %(org_fee)s € per day has to "
"be paid to the account. With a duration of %(duration)s days, a total of "
"%(total_org_fee_theoretical)s € is charged against the other transactions."
msgstr ""
"Achtung: %(old_participant_count)s Teilnehmende der Ausfahrt sind 27 oder "
"älter. Für diese Teilnehmende(n) ist ein Org-Beitrag von %(org_fee)s € pro Tag "
"fällig. Durch die Länge der Ausfahrt von %(duration)s Tagen werden insgesamt "
"%(total_org_fee_theoretical)s € mit den Zuschüssen und "
"Aufwandsentschädigungen verrechnet, sofern diese in Anspruch genommen werden."
#: members/templates/admin/freizeit_finance_overview.html
msgid ""
"Warning: The configured recipients of the allowance don't match the "
@ -1440,10 +1458,10 @@ msgid ""
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 %(theoretic_ljp_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 "
"Tage für %(theoretic_ljp_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."
#: members/templates/admin/freizeit_finance_overview.html
@ -1465,6 +1483,10 @@ msgstr "Zusammenfassung"
msgid "This is the estimated cost and contribution summary:"
msgstr "Das ist die geschätzte Kosten- und Zuschussübersicht."
#: members/templates/admin/freizeit_finance_overview.html
msgid "Organisation fees"
msgstr "Org-Beitrag"
#: members/templates/admin/freizeit_finance_overview.html
msgid "Potential LJP contributions"
msgstr "Mögliche LJP Zuschüsse"

@ -1344,9 +1344,18 @@ class Freizeit(CommonModel):
@property
def participant_count(self):
return len(self.participants)
@property
def participants(self):
ps = set(map(lambda x: x.member, self.membersonlist.distinct()))
jls = set(self.jugendleiter.distinct())
return len(ps - jls)
return list(ps - jls)
@property
def old_participant_count(self):
old_ps = [m for m in self.participants if m.age() >= 27]
return len(old_ps)
@property
def head_count(self):

@ -122,17 +122,24 @@ cost plan!
</p>
{% else %}
<p>{% blocktrans %}No receivers of the subsidies were provided. Subsidies will not be used.{% endblocktrans %}</p>
{% endif %}
{% if total_org_fee %}
<h3>{% trans "Org fee" %}</h3>
<p>
{% blocktrans %}Warning: {{ old_participant_count }} participant(s) of the excursion are 27 or older. For each of them, an organisation fee of {{ org_fee }} € per day has to be paid to the account. With a duration of {{ duration }} days, a total of {{ total_org_fee_theoretical }} € is charged against the other transactions.{% endblocktrans %}
</p>
{% endif %}
{% if not memberlist.statement.allowance_to_valid %}
<p>
{% blocktrans %}Warning: The configured recipients of the allowance don't match the regulations. This might be because the number of recipients is bigger then the number of admissable youth leaders for this excursion.{% endblocktrans %}
</p>
{% endif %}
{% if memberlist.statement.ljp_to %}
<h3>{% trans "LJP contributions" %}</h3>
{% if memberlist.statement.ljp_to %}
<p>
{% blocktrans %}By submitting the given seminar report, you will receive LJP contributions.
@ -204,6 +211,14 @@ you may obtain up to 25€ times {{ duration }} days for {{ theoretic_ljp_partic
{{ total_bills_theoretic }}€
</td>
</tr>
<tr>
<td>
{% trans "Organisation fees" %}
</td>
<td>
{{ total_org_fee }}€
</td>
</tr>
<tr>
<td>
{% trans "Contributions by the association" %}

Loading…
Cancel
Save