Zum Hauptinhalt springen

Mahnwesen — Mahnläufe (Payment Reminder)

Zweck

Das Mahnwesen erkennt überfällige Forderungen automatisch und erzeugt daraus Mahnungen je Kunde — entweder manuell per Knopfdruck oder als täglicher Auto-Lauf. Die Seite /payment-reminder-runs ist die operative Übersicht aller Mahnläufe: Sie zeigt pro Lauf, welche Kunden mit welcher Mahnstufe und welchem Gesamtbetrag erfasst wurden, und erlaubt den Versand der einzelnen Mahnungen per E-Mail.

Ein Mahnlauf bucht nichts und verändert keine offenen Posten — er ist eine Auswertung über die bestehenden Offenen Posten plus Versand-Workflow. Mahngebühren (surchargeAmount) werden in der Mahn-E-Mail ausgewiesen, aber nicht gebucht.

Die zugehörige Konfiguration (globale Aktivierung, Absender-Mailbox, Auto-Lauf-Zeit, Mahnstufen) liegt auf der eigenen Seite Mahnwesen — Einstellungen.

Voraussetzungen

- Mahnwesen ist in den [Einstellungen](/settings/paymentreminder) global aktiviert (`enabled`). - Mindestens **eine Mahnstufe** ist aktiv (`active = true`) und hat eine Fälligkeitsschwelle (`daysOverdue`). - Eine **Absender-Mailbox** ist in den Einstellungen hinterlegt (sonst schlägt der Versand fehl). - Es existieren überfällige [Offene Posten](/open-items) aus [Verkaufsbelegen](/sales-documents). - Berechtigung `view:FE_PaymentReminder` + `view:PaymentReminder`.

Berechtigungen (CASL)

Frontend-Page-Guard (requiredAbility in src/routes.tsx):

ActionSubjectWirkung
viewFE_PaymentReminderRoute /payment-reminder-runs und /settings/paymentreminder aufrufbar
viewPaymentReminderInhalte (Läufe, Mahnungen) sichtbar

API-Datenzugriff (caslMiddleware im BE-Router):

ActionSubjectEndpoint(s)Wirkung
viewPaymentReminderGET /payment-reminder-settings, …-levels, …-runs, …-notices, …/previewEinstellungen, Stufen, Läufe, Mahnungen lesen
createPaymentReminderPOST /payment-reminder-runsMahnlauf manuell starten
updatePaymentReminderPATCH /payment-reminder-settings, …-levels/:id, POST …/send-all, POST …/:id/sendEinstellungen/Stufen ändern, Mahnungen versenden

Die Subjects PaymentReminderLevel, PaymentReminderRun, PaymentReminderNotice und PaymentReminderNoticeItem existieren zusätzlich als feingranulare Lese-Subjects; im Standard reicht die Gruppe PaymentReminder.

Funktionsweise

Der Ablauf läuft in vier Stufen: Mahnstufen konfigurieren → Vorschlag berechnen → Mahnlauf erzeugen → Mahnungen versenden.

Vorschlag (computeDunningProposals)

Die Vorschlagsberechnung ist read-only und persistiert nichts. Sie:

  1. Lädt alle aktiven Mahnstufen (absteigend nach daysOverdue).
  2. Lädt alle Offenen Posten mit parentType = SalesDocument und filtert auf überfällig: outstandingAmount > 0, settlementStatus ≠ settled, dueDate < heute.
  3. Löst Beleg, Belegnummer und Kunde (bzw. Standort) auf.
  4. Mahnsperre-Filter: Kunden bzw. Standorte mit dunningBlock = true werden komplett übersprungen.
  5. Bestimmt je Kunde die höchste erreichte Mahnstufe anhand der maximalen Überfälligkeit aller seiner offenen Rechnungen.
  6. Ermittelt Aufschlag (surchargeAmount der Stufe) und Empfänger-Adresse.

Mahnlauf (createDunningRun)

POST /payment-reminder-runs ruft die Vorschlagsberechnung auf und persistiert das Ergebnis atomar: einen PaymentReminderRun (status = completed), je Kunde eine PaymentReminderNotice (status = pending) und je überfälliger Rechnung eine PaymentReminderNoticeItem. Der Lauf trägt triggeredBy = manual (Button) oder auto (Scheduler).

Versand (sendDunningNotice)

Der Versand ist idempotent (eine bereits gesendete Mahnung wird nicht erneut verschickt). Pro Mahnung:

  • Empfänger wird aus den Kunden-Attributen gezogen — Priorität „Email (Dunning)", sonst „Email (General)". Fehlt eine Adresse, wird die Mahnung auf skipped gesetzt (kein Fehler).
  • Der E-Mail-Text kommt aus einem Textbaustein-Template (emailCategory = dunning, Position = Mahnstufe); fehlt es, greift ein deutscher Standardtext.
  • Ist attachInvoices der Stufe aktiv, werden die Rechnungs-PDFs angehängt.
  • Versendet wird über die in den Einstellungen hinterlegte Absender-Mailbox; die erzeugte Mail wird mit Mahnung, Lauf, Kunde und Beleg verknüpft.
  • Erfolg → status = sent (mit sentMailId, sentAt, sentByEmployeeId); technischer Fehler → status = failed mit errorMessage.

Schritt-für-Schritt-Anleitung

Mahnlauf starten und versenden

  1. Mahnläufe (/payment-reminder-runs) öffnen.
  2. Optional zuerst die Vorschau prüfen (GET /payment-reminders/preview) — sie zeigt ohne Persistenz, wer aktuell gemahnt würde.
  3. Mahnlauf starten → es entsteht ein neuer Lauf mit je einer Mahnung pro betroffenem Kunden (Status pending).
  4. Lauf öffnen → Liste der Mahnungen je Kunde mit Stufe, Gesamtbetrag und Empfänger-Adresse.
  5. Einzelne Mahnung Senden oder im Lauf Alle senden — die E-Mails gehen über die hinterlegte Absender-Mailbox raus.
  6. Status je Mahnung kontrollieren: sent, skipped (keine Adresse) oder failed (Fehlertext prüfen).

Mahnsperre für einen Kunden setzen

Damit ein Kunde (oder ein einzelner Standort) nicht gemahnt wird, setzen Sie im Kunden- bzw. Standort-Formular den Schalter Mahnsperre (dunningBlock). Gesperrte Kunden/Standorte werden in jedem Vorschlag und Lauf übersprungen — siehe Mahnsperre.

Datenmodell

Vier Modelle bilden das Mahnwesen ab (alle mit id, sequenceId, Soft-Delete deletedAt und Standard-Zeitstempeln):

PaymentReminderLevel — Mahnstufe

FeldnamePflichtDatentypBeschreibungWirkung beim AusfüllenVoraussetzung
leveljaInteger (unique)Mahnstufe (1, 2, 3 …)Reihenfolge der Eskalationje Stufe genau ein Eintrag
activejaBoolean (Default false)Stufe aktiv?nur aktive Stufen werden berücksichtigt
daysOverduejaInteger (Default 14)Schwelle: Tage nach Fälligkeitab so vielen Tagen Überfälligkeit greift diese Stufe
surchargeAmountneinDecimal(10,2)Mahngebühr (€)wird in der E-Mail ausgewiesen, nicht gebucht
attachInvoicesneinBooleanRechnungs-PDFs anhängen?hängt die Belege an die Mahn-E-Mail
autoSendneinBooleanim Auto-Lauf direkt versenden?beim täglichen Lauf wird diese Stufe sofort gesendet, sonst bleibt sie pending

PaymentReminderRun — Mahnlauf

FeldnameDatentypBeschreibung
runDateDateDatum des Laufs
statusEnum (draft, completed)bei Erstellung immer completed
triggeredByEnum (manual, auto)manuell (Button) oder Scheduler
employeeIdUUID, nullablewer hat den Lauf ausgelöst (bei manual)
customerCountIntegerAnzahl gemahnter Kunden
noticeCountIntegerAnzahl Mahnungen
totalAmountDecimal(12,2)Gesamtbetrag (offene Posten + Aufschläge)

PaymentReminderNotice — Mahnung (je Kunde)

FeldnameDatentypBeschreibung
runIdUUIDFK → PaymentReminderRun
customerIdUUIDFK → Customer
levelIntegerMahnstufe dieser Mahnung
totalOverdueDecimal(12,2)offener Gesamtbetrag des Kunden
surchargeAmountDecimal(10,2)Aufschlag dieser Mahnung
recipientEmailString, nullableEmpfänger (aus Kunden-Attribut)
statusEnum (pending, sent, skipped, failed)Versand-Status
sentMailIdUUID, nullableFK → Mail (bei Versand)
sentAt / sentByEmployeeIdDate / UUIDVersand-Zeitpunkt und -Person
errorMessageText, nullableFehlertext bei failed

PaymentReminderNoticeItem — Position (je Rechnung)

FeldnameDatentypBeschreibung
noticeIdUUIDFK → PaymentReminderNotice
salesDocumentIdUUIDFK → SalesDocument (die Rechnung)
openItemIdUUID, nullableFK → OpenItem
amountDecimal(12,2)offener Betrag dieser Rechnung
dueDateDate, nullableFälligkeitsdatum
daysOverdueIntegerberechnete Überfälligkeit in Tagen

Mahnsperre (dunningBlock)

Die Mahnsperre ist ein Boolean-Feld dunningBlock (Default false) auf zwei Entitäten:

EntitätFeldWirkung
KundedunningBlockalle Rechnungen des Kunden werden vom Mahnwesen ausgeschlossen
StandortdunningBlockRechnungen, die an diesen Standort hängen, werden ausgeschlossen

Im Frontend erscheint das Feld als Schalter (Label „Mahnsperre") im Kunden- bzw. Standort-Formular. Der Ausschluss greift sowohl in der Vorschau als auch in jedem Mahnlauf (manuell und automatisch).

Automatischer Mahnlauf (Scheduler)

Ist in den Einstellungen sowohl enabled als auch autoDailyRun aktiv, läuft täglich zur konfigurierten Uhrzeit (runTime, Default 06:00, Zeitzone Europe/Berlin) ein automatischer Mahnlauf über node-cron:

  1. Der Lauf wird mit triggeredBy = auto erzeugt.
  2. Nur Mahnungen, deren Stufe autoSend = true hat, werden direkt versendet — alle übrigen bleiben pending und warten auf manuelle Freigabe.
  3. Fehler beim Einzelversand werden protokolliert, der Lauf selbst wird nicht zurückgerollt.
Eine Änderung der Auto-Lauf-Uhrzeit (`runTime`) wird im Scheduler erst nach einem Server-Neustart wirksam. Die Stufen-Flags (`autoSend`) und die Aktivierung greifen dagegen sofort beim nächsten Lauf.

Verknüpfungen zu anderen Modulen

Wiederverwendbare Konzepte

Häufige Fehler und Lösungen

FehlerLösung
Mahnung bleibt auf skippedKunde hat keine Adresse im Attribut „Email (Dunning)" oder „Email (General)". Adresse hinterlegen.
Mahnung wird failedKeine Absender-Mailbox in den Einstellungen oder Mailversand-Fehler — errorMessage prüfen.
Kunde fehlt im LaufMahnsperre (dunningBlock) gesetzt, keine aktive Stufe für die Überfälligkeit erreicht, oder Posten bereits settled.
Auto-Lauf startet nichtenabled oder autoDailyRun aus, keine aktive Stufe, oder geänderte runTime ohne Server-Neustart.
Aufschlag taucht in der Buchhaltung nicht aufKorrekt — surchargeAmount wird nur in der E-Mail ausgewiesen, nicht gebucht.

API/Schnittstellen

MethodeEndpointZweckCASL
GET/payment-reminder-settingsEinstellungen lesenview PaymentReminder
PATCH/payment-reminder-settingsEinstellungen ändernupdate PaymentReminder
GET/payment-reminder-levelsMahnstufen auflistenview PaymentReminder
PATCH/payment-reminder-levels/:idMahnstufe ändernupdate PaymentReminder
GET/payment-reminders/previewVorschau (read-only)view PaymentReminder
GET/payment-reminder-runsMahnläufe auflistenview PaymentReminder
POST/payment-reminder-runsMahnlauf startencreate PaymentReminder
GET/payment-reminder-runs/:idLauf-Detailview PaymentReminder
POST/payment-reminder-runs/:id/send-allalle offenen Mahnungen versendenupdate PaymentReminder
GET/payment-reminder-noticesMahnungen auflistenview PaymentReminder
GET/payment-reminder-notice-itemsPositionen auflistenview PaymentReminder
POST/payment-reminder-notices/:id/sendeinzelne Mahnung versendenupdate PaymentReminder

Versionshinweise

  • 2026-06-11: Initiale Veröffentlichung — Mahnwesen-Modul (Mahnstufen, Mahnläufe, Mahnungen, Versand, Mahnsperre, Auto-Lauf). Verifiziert an paymentReminder.service.ts, paymentReminder.router.ts, den vier paymentReminder*-Modellen und routes.tsx.