Handle failed direct debits through the same reminder mechanism

A Rücklastschrift is still the member's own failure to ensure cover or
provide valid bank details, so it belongs in the same escalation ladder
as a regular reminder -- it now occupies whatever level the claim is next
due for, alongside a "Rücklastschrift" preset next to the existing
Mahnstufe presets.

Mahnung vorbereiten no longer lets the board pick a level (it's computed
automatically from what's already been sent, since the sequencing was
already server-enforced) and replaces the single fee field with a small
items table (Beschreibung + Betrag), so a reminder can carry several
charges at once -- e.g. Rücklastschriftgebühr plus Bankgebühr plus Porto
-- each landing on the claim as its own line item instead of one lump
sum under a generic label. The description field offers the configured
standard texts but stays free-editable.

Those standard texts and amounts, plus the dunning levels themselves
(name/fee/deadline), are now configurable from Optionen -> Mahnungen
instead of only being editable by hand-editing repository.json, which
was the case for the whole reminder policy until now. Cleaned up
reminder_fee/failed_debit_fee on contribution rules while at it -- both
were unused leftovers superseded by this.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Marcel Peterkau
2026-08-15 03:13:04 +02:00
co-authored by Claude Sonnet 5
parent 46795fc110
commit a1719cad5e
9 changed files with 684 additions and 71 deletions
+3 -3
View File
@@ -345,13 +345,13 @@ def test_reminder_fee_increases_claim_and_is_audited(tmp_path) -> None:
name="Zahlungserinnerung",
payment_deadline_days=14,
detail="Per E-Mail versandt",
fee="5.00",
items=[{"description": "Mahngebühr", "amount": "5.00"}],
)
data, claim = repository.get_claim(member.member_id, "claim-1")
assert claim_total(claim) == Decimal("100.00")
assert reminder["status"] == "draft"
assert reminder["fee_item_id"] is None
assert reminder["fee_item_ids"] == []
repository.mark_reminder_sent(member.member_id, "claim-1", reminder["reminder_id"])
data, claim = repository.get_claim(member.member_id, "claim-1")
@@ -360,7 +360,7 @@ def test_reminder_fee_increases_claim_and_is_audited(tmp_path) -> None:
assert claim_total(claim) == Decimal("105.00")
assert sent["status"] == "sent"
assert sent["payment_deadline"]
assert sent["fee_item_id"]
assert sent["fee_item_ids"]
assert data.reminders[0]["detail"] == "Per E-Mail versandt"
assert repository.get_events(member.member_id)[-1].event_type == "reminder_sent"