# Changelog
## [0.3.1] — 2026-10-13 — Mahnschreiben nur im Reiter der Rechnung
Eddys Regel (13.10.2026, KB #1641): Verknüpfungen, angehängte Dateien und Archiv-Dokumente stehen
nur im Reiter am Beleg, nie vorne im Kasten der Belegkarte — „außer Dolibarr packt sie darein".
Das Mahnschreiben liegt bewusst im Rechnungsordner (damit es im Reiter der Rechnung erscheint),
war aber nicht indexiert; Dolibarr führte es als `unknown`.
- `ModelePDFMahnung::alsBeilageIndexieren()`: Eintrag mit `gen_or_uploaded = 'uploaded'` und Bezug
auf die Rechnung; vorhandener Eintrag (Mahnung neu erzeugt) wird aktualisiert, nicht verdoppelt.
- Aufgerufen von beiden Vorlagen (`pdf_standard_mahnung`, `doc_generic_mahnung_odt` — dort für die
ODT und, mit `MAIN_ODT_AS_PDF`, die PDF gleichen Namens).
- Vorne auf der Rechnungskarte blendet FileArchive 4.15.1 das Schreiben bereits über den Namen aus
(`MAHN…`/`mahnung-…` ist kein Belegreferenz-Name) — auch für vorhandene Mahnungen.
## [Unreleased]
### Widget-Kopf: kein rohes HTML mehr im Tooltip
Beim Überfahren des Widgets „Überfällige Rechnungen mit Mahnstufe" erschien
`…Mahnstufe2` — die Zahl las sich wie eine Mahnstufe, war aber die
Anzahl offener Rechnungen. Dolibarr 22 schreibt den kompletten Titel samt Badge-HTML ins
title-Attribut des Kopfs und in die Bezeichnung der Widget-Auswahl. `mahnungBoxKopfOhneHtml()`
setzt dort jetzt den reinen Titel; Badge und Link im Kopf bleiben, wie sie sind.
### Reiter am Kunden nur noch, wenn es Mahnungen gibt
Der Reiter „Mahnwesen" stand auf **jeder** Kundenkarte, auch wenn dort nie gemahnt wurde. Die
Reiterleiste trägt dort 21 Einträge; auf Prod gibt es 4 Mahnungen bei 3 von 111 Kunden — bei 108
Kunden zeigte der Reiter also nichts und kostete trotzdem Platz. `completeTabsHead` blendet ihn
am Kunden jetzt nur noch bei vorhandenen Mahnungen ein (die Zählung lief ohnehin schon, für die
Zahl im Reiter). **An der Rechnung bleibt er unverändert immer sichtbar** — dort wird gemahnt,
und dort ist „keine Mahnung" eine Aussage.
### Zahlungserinnerung (Stufe 0) + frei konfigurierbare Mahnstufen
- **Vor der ersten echten Mahnung steht jetzt eine kostenlose Zahlungserinnerung.** Ob eine Stufe eine Erinnerung ist, entscheidet ausschließlich das neue Flag `llx_mahnung_stufe.ist_erinnerung` — **nicht** die Stufennummer. Eine Erinnerung kostet nichts: Mahngebühr, §288-Abs.-5-Pauschale und Verzugszinsen werden in `mahnungBaueVorgang()` hart auf 0 gesetzt, unabhängig davon, was in der Stufe konfiguriert ist.
- **Keine feste Obergrenze bei Stufe 3 mehr.** Stufen sind frei konfigurierbar (Nummer 0–127, Lücken erlaubt). Einstieg ist die kleinste aktive Stufe (`minStufe()`), die Folgestufe liefert `naechsteStufeNach()` — Lücken werden übersprungen. Auch der Button „Als uneinbringlich klassifizieren" hängt nicht mehr an `stufe === 3`, sondern daran, dass **keine weitere aktive Stufe** mehr folgt.
- **Wartefrist korrigiert:** maßgeblich ist `frist_tage` der **Ziel**stufe, nicht `neue_frist_tage` der Vorstufe. Vorher war `frist_tage` der Stufen 2/3 ein toter Konfigurationswert. Bezugspunkt ist `date_versand`, Fallback `date_mahnung` (Bestandsdaten haben kein Versanddatum).
- **Neue Spalte `llx_mahnung_mahnung.kosten_vorstufen`**: die Gebühren und Pauschalen bereits gemahnter Vorstufen fielen bisher aus der Forderung heraus, weil `rechneSumme()` nur die aktuelle Stufe addierte.
- **Zinssatz-Spalten von `DECIMAL(5,4)` auf `DECIMAL(6,3)`**: `5,4` konnte maximal 9,9999 abbilden — der B2B-Standardsatz (Basiszins + 9 %) passte damit gar nicht hinein.
- **`modMahnung::ensureSchema()`** zieht das Schema nach einem reinen Datei-Deploy nach (Aufruf am Kopf von `list.php`, `card.php` und den AJAX-Endpoints). Ohne das schlug nach jedem Deploy jedes `create()` fehl, bis jemand die Setup-Seite aufgerufen hatte.
- **Label-Vereindeutigung** läuft genau einmal (Marker `MAHNUNG_LABEL_MIGR_DONE`) und trifft nur unveränderte Seed-Texte: Stufe 2 „1. Mahnung" → „2. Mahnung", danach Stufe 1 „Zahlungserinnerung" → „1. Mahnung". Die Reihenfolge ist zwingend, sonst hieße „1. Mahnung" doppelt. Bestandsdaten (Mahnvorgänge, Beträge) bleiben unangetastet.
### E-Mail-Versand über das Dolibarr-Mailformular (FormMail)
- **Der Versand der Zahlungserinnerung läuft jetzt über das Standard-Mailformular auf der Mahnungskarte** (`card.php?id=…&action=presend`). Empfänger, Betreff, Text und Anhang sind vor dem Absenden **sichtbar und änderbar** — vorher ging die Mail nach einem bloßen Ja/Nein-Dialog ungesehen raus.
- **Empfängerauswahl** enthält die Firmenadresse **und alle aktiven Ansprechpartner** des Kunden (`thirdparty_and_contact_email_array(1)`), dazu ein Freitextfeld und ein CC-Feld. Aufgelöst wird ein Empfänger nur, wenn sein Schlüssel wirklich aus der Liste **dieses** Kunden stammt: `Societe::contact_get_property()` fragt allein über die `rowid` ab und prüft die Firmenzugehörigkeit **nicht** — ein manipuliertes `receiver[]` hätte die Erinnerung sonst an einen fremden Kontakt schicken können.
- **Absender ist die Firmenadresse**, nicht der angemeldete Benutzer — eine Zahlungserinnerung geht im Namen des Betriebs raus. Sie wird nur angezeigt, nicht zur Auswahl gestellt; ein trotzdem geposteter Wert wird ignoriert.
- **Angehängt wird die unveränderte Original-Rechnungs-PDF**, vorbelegt beim Öffnen des Formulars (Parameter `mailinit=1`) und bei Bedarf über `Facture::generateDocument()` nacherzeugt. Weitere Dateien lassen sich anhängen und wieder entfernen. Bewusst **nicht** Dolibarrs `mode=init`: `FormMail::get_form()` leert bei diesem Parameter selbst die Anhangsliste und hätte die eingehängte Rechnung sofort wieder entfernt.
- Betreff und Text kommen aus der Stufen-Konfiguration mit **bereits ersetzten Platzhaltern** (`{rechnung}`, `{summe}`, `{frist}`, `{kunde}`, `{ref}`, `{stufe}`) — im Formular stehen keine `{…}`-Marken mehr. Nachträglich ergänzte Platzhalter werden beim Absenden erneut aufgelöst.
- **`ajax/sendmail.php` ist kein Endpoint mehr, sondern eine Funktionsbibliothek.** Es gibt genau **einen** Sendeweg (`mahnungSendeErinnerungsMail()`); zwei nebeneinander hätten auseinanderlaufen können. Ein Direktaufruf der Datei per HTTP verschickt nichts, sondern leitet auf das Formular um (und antwortet einem alten jQuery-Aufruf sauber mit JSON statt mit HTML).
- **Doppelversand ist blockiert**: der Vorgang wird **vor** dem Senden per bedingtem `UPDATE` atomar auf VERSENDET reserviert — senden darf nur der Request, dessen UPDATE tatsächlich eine Zeile trifft. Schlägt der Versand fehl, wird ausschließlich die **eigene** Reservierung wieder zurückgenommen.
- **Beträge und Frist werden zum Versandzeitpunkt frisch geprüft**: ist die Rechnung inzwischen bezahlt, storniert oder abgeschrieben, wird der Versand abgelehnt; bei einer Teilzahlung werden `betrag_offen`/`summe_mahnung` nachgezogen, damit Mail und Datensatz denselben Betrag nennen. Die neue Zahlungsfrist wird aus `neue_frist_tage` neu gerechnet — sonst nennt eine später abgeschickte Erinnerung eine bereits verstrichene Frist.
- **Fachliche Sperre bleibt**: versendbar ist ausschließlich eine Stufe mit `ist_erinnerung = 1`. Echte Mahnungen gehen per Post bzw. Einschreiben raus, weil eine E-Mail nicht beweisbar zugestellt ist.
### Versandprotokoll: was ging wann an wen raus
- Jede versendete Zahlungserinnerung wird protokolliert — **Empfänger, Kopie, Blindkopie, Absender, Betreff, der komplette Text und die Namen der Anhänge**, jeweils im Stand des Sendezeitpunkts. Ändert jemand später die Vorlage in der Stufen-Konfiguration, bleibt der Nachweis davon unberührt.
- Einsehbar auf der Mahnungskarte unter **Versandstatus → „Versendete E-Mails"**: zugeklappt stehen Zeitpunkt und Empfänger, aufgeklappt der vollständige Mailinhalt. HTML-Mails werden formatiert dargestellt (durch `dol_string_onlythesehtmltags()`, da der Inhalt im Browser ausgegeben wird), Klartext mit erhaltenen Zeilenumbrüchen.
- **Eigene Tabelle `llx_mahnung_mailprotokoll` statt Spalten am Mahnvorgang**: seit dem Wiederholversand kann dieselbe Erinnerung mehrfach rausgehen — Spalten am Vorgang würden den vorherigen Versand überschreiben, also genau den Nachweis zerstören, um den es geht. Jeder Versand ist ein eigener Eintrag, neueste zuerst.
- Geschrieben wird ausschließlich nach **erfolgreichem** Versand, direkt in `mahnungSendeErinnerungsMail()` — damit kann kein Sendeweg das Protokollieren umgehen. Scheitert das Protokollieren selbst, wird das nur ins Syslog geschrieben und der Versand nicht nachträglich als gescheitert gemeldet.
- Die Tabelle entsteht auf Bestandsinstallationen über die Lazy-Migration (`ensureSchema()` → `ensureMailProtokollTabelle()`), `DB_VERSION` steht jetzt auf **0.4.0**. Ein reiner Datei-Deploy genügt, es braucht keine Modul-Neuaktivierung.
### Setup: PDF-Einleitungstext nur noch, wo es ein PDF gibt
- Bei Versandart **E-Mail** und bei jeder **Zahlungserinnerung** stand im Block „Texte für das Schreiben" weiterhin der **PDF-Einleitungstext** — dabei entsteht in beiden Fällen gar kein Mahnschreiben, in das er könnte (eine Erinnerung hat nie ein eigenes PDF, angehängt wird die Original-Rechnung). Das Feld wird jetzt spiegelbildlich zu den E-Mail-Feldern ein- und ausgeblendet, auch beim Umschalten der Versandart ohne Neuladen (`syncEmailRows()`).
- Bewusst ausgeblendet statt entfernt: ein bereits gepflegter Einleitungstext bleibt im Formular und damit erhalten, falls die Stufe später wieder auf Postversand umgestellt wird.
### Zahnrad in die Einstellungen
- Vorschlagsliste, Archiv und Mahnungskarte haben oben rechts neben dem Titel ein **Zahnrad**, das direkt in die Modul-Einstellungen führt (`mahnungSetupLink()` in `lib/mahnung_ui.lib.php`, gesetzt über den `morehtmlright`-Parameter von `load_fiche_titre()`). Sichtbar nur mit dem Recht `mahnung.setup` oder für Administratoren — wer die Seite ohnehin nicht öffnen darf, sieht kein Icon, das ihn in eine Fehlermeldung laufen lässt.
### HTML-Mails, Wiederholversand, Rechnung in der Dokumentenliste
- **Mailtexte lassen sich als HTML pflegen — mit dem gewohnten Dolibarr-Editor.** Im Setup hing bisher ein nacktes `