Verweis auf KB #682: bei $num==0 muss Platzhalter-Zeile in
info_box_contents gesetzt werden, sonst verschwindet das Widget.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wenn alle Rechnungen bezahlt sind, blieb info_box_contents leer und
ModeleBoxes::showBox renderte keinen Widget-Rahmen mehr. Widget kam
auch nach neuen Rechnungen nicht zurück. Fix: bei 0 Treffern eine
Platzhalter-Zeile "Keine offenen Kundenrechnungen" einfügen.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
tms war als reines TIMESTAMP angelegt -> unter explicit_defaults_for_timestamp
NULL DEFAULT NULL, blieb daher bei jedem UPDATE leer. Jetzt Dolibarr-Standard
DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP fuer mahnung/stufe/
trackingpattern. Neue idempotente Migration migrateTimestampSpalten() im init()
befuellt Alt-NULLs aus datec und stellt die Spalte per ALTER TABLE um.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Vorher: Flex-Spacer-Trick (flex-basis:100%) sollte den Button in zweite Zeile
zwingen — funktioniert aber nur wenn .tabsAction ein Flexbox-Container mit
flex-wrap ist. Im AwlDark-Theme ist das nicht der Fall → Button landete einfach
am Anfang der Aktionsleiste.
Jetzt: kurzes JS-Snippet verschiebt #btn-mahnung-create per insertBefore vor
den ersten .butActionRefused (= disabled "Löschen" / "Auf anderen Kunden
übertragen" — typisch sichtbar bei freigegebener Rechnung). Fallback: appendTo
ans Ende der .tabsAction.
So landet "Mahnung erstellen" optisch direkt bei den Verwaltungs-Buttons am
rechten/unteren Ende, wie Eddy es wollte.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
REGRESSION aus letztem Commit: $this->resprints wurde nicht gerendert.
addMoreActionsButtons-Hook in compta/facture/card.php wertet zwar $reshook aus
(empty → Default-Buttons), printet aber $hookmanager->resPrint an dieser Stelle
nicht — anders als bei formObjectOptions. Der Hook muss seinen HTML-Output
direkt selbst per print ausgeben.
Direkter print ist hier sicher: der Hook wird innerhalb des dafür vorgesehenen
<div class="tabsAction"> aufgerufen, kein HTML-Layoutbruch möglich.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Vorher: Button wurde immer gezeigt — auch bei Entwurf, bezahlt, storniert
(zwar disabled mit Tooltip, aber sichtbar).
Jetzt: Hook gibt nichts aus wenn:
- Entwurf (statut == 0)
- Bezahlt (paye != 0)
- Storniert / abandoned (statut == 2 / 3)
Nur bei statut >= 1 UND paye === 0 erscheint der Button. Überfällig-Logik
entscheidet danach nur noch ob aktiv (mit URL) oder disabled (mit Tooltip).
Zusätzlich: Flex-Spacer (flex-basis:100%) wird DIREKT vor den Button gestellt
→ Button landet in eigener zweiter Zeile unterhalb der primären Aktionen,
neben "Import Zeilen / Löschen / Auf anderen Kunden übertragen".
Output via \$this->resprints statt direktem print — verhindert HTML-Layoutbruch
bei Hook-Aufrufen mitten in Tabellen.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mahnungen im Status "Erstellt" (noch nicht versandt) werden beim Aufruf
der card.php mit der aktuellen Stufen-Konfiguration neu berechnet.
Ändert sich der Zinssatz-Override in den Einstellungen, wirkt das
sofort auf alle offenen Mahnungen.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
$effB2c/$effB2b enthielt den Override-Wert statt den Standard.
Jetzt wird immer Basiszins + Aufschlag angezeigt (z.B. "1,27 + 5,0 % = 6,27 %").
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Eingabefelder zeigen Placeholder mit Standard-Zinssatz (Basiszins + Aufschlag)
- Grauer Hilfetext: "Leer = Standard (1,27 + 5,0 % = 6,27 %), 0 = keine Zinsen"
- Effektiver Zinssatz wird live aus globalen Einstellungen berechnet
- Prod-DB: Stufe 1 Override von 0.0000 auf NULL korrigiert (direkt in DB)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
apply_tracking setzt jetzt auch date_versand (heute) und
versandweg (abgeleitet vom Provider) wenn noch leer.
Kein extra Speichern-Klick mehr nötig.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
$upload_dir wird erst nach den Action-Handlern definiert.
scan_belege-Block berechnet den Pfad jetzt selbst.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
pdftotext gibt bei Bild-PDFs 0x0C (Form Feed) zurück — trim() entfernt
das nicht. Dadurch wurde der OCR-Fallback nie ausgelöst.
Jetzt trim() mit expliziter Zeichenliste inkl. \x0C.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
fichehalfleft entfernt — Stammdaten und Versand-Tabelle
nutzen jetzt die volle Seitenbreite statt nur 55%.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- fichehalfleft Container für korrekte Dolibarr-Rahmen bei Stammdaten + Versand
- Einschreiben-Regex in DB updated: optionale Leerzeichen (OCR-freundlich)
- detectFromText() entfernt Leerzeichen aus erkannten Nummern (OCR-Normalisierung)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Wenn pdftotext keinen Text findet, wird ocrmypdf (Tesseract OCR)
auf das PDF angewendet bevor erneut nach Sendungsnummern gesucht wird.
Bei leerem Ergebnis erscheint jetzt eine Hinweismeldung statt
stummem Seiten-Reload.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Dolibarr-Standard-Klassen (fichecenter, underbanner, tableforfield)
für konsistentes Card-Layout wie bei Rechnungen/Bestellungen.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
showdocuments() gibt String zurück — fehlte print davor.
Versand & Belege Bereich mit fichecenter/underbanner gerahmt.
Sendebelege-Titel als load_fiche_titre statt h3.
hideifempty=0 damit Sektion immer sichtbar.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Umlaute in allen lang-Dateien korrigiert. Alle hardcodierten deutschen Strings
in 22 PHP-Dateien durch $langs->trans('Key') ersetzt. Neue Schlüssel für
Cron-Meldungen, Dokument-Aktionen, Bonität, Vorschlag-Status, Template-Vars u.a.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Bonitaets-Anzeige:
- Hook tabContentViewThirdparty rendert prominente rote Warnbox auf der
Kundenkarte wenn fk_statut=3 + close_code=badcustomer existiert. Zeigt
Anzahl, Gesamtsumme, Datum letzter Abschreibung + Link zur Detail-Liste.
- Hook formObjectOptions zeigt kompakte Warn-Zeile auf ordercard und
invoicecard wenn der Kunde Forderungsausfaelle hat.
- ordercard zum module_parts.hooks.data ergaenzt.
Uneinbringlich-Button:
- Auf Mahnung-Karten der Stufe 3, Status >= ERSTELLT, nicht storniert,
Rechnung noch nicht abandoned.
- Bestaetigungs-Dialog mit Begruendungs-Textfeld (Default-Text setzt
das aktuelle Datum ein).
- Ruft Facture::setCanceled mit CommonInvoice::CLOSECODE_BADDEBT.
- Mahnung wird storniert + Begruendung in note_private festgehalten.
Steuer-Modul kompatibel: EÜR liest nur llx_paiement (keine Zahlung =
keine Einnahme), UStVA filtert fk_statut IN (1,2) — abandoned Rechnungen
werden automatisch korrekt ausgeschlossen. Bei Ist-Versteuerung damit
buchhalterisch sauber, kein manueller Eingriff noetig.
Lang-Keys: 16 neu (de_DE + en_US) fuer Bonitaets-Box + Uneinbringlich-Workflow.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bug:
- Mein letzter Commit hat einen plain-SQL-Filter an select_company uebergeben.
Dolibarrs select_thirdparty_list reicht den Filter durch
forgeSQLFromUniversalSearchCriteria, das erwartet aber USC-Syntax
feld:operator:wert in Klammern. Plain-SQL fuehrt zu SQL-Syntax-Error
und 500-Antwort.
Fix:
- B2B-Filter: (s.tva_intra:isnot:NULL) AND (s.tva_intra:!=:'')
- B2C-Filter: (s.tva_intra:is:NULL) OR (s.tva_intra:=:'')
Zusatz-Fixes aus dem Log:
- search_socid=-1 (von select_company als "nichts ausgewaehlt" gerendert)
wurde irrtuemlich als Filter auf fk_soc=-1 angewendet. Jetzt nur als
Filter genutzt wenn > 0.
- Beim Auto-Submit des Kundentyp-Selects wird search_socid auf "" gesetzt,
damit eine zuvor ausgewaehlte (jetzt evtl. ausgefilterte) Kunden-ID
nicht stehen bleibt.
KB-Eintrag #602 zur USC-Syntax angelegt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Kundentyp filtert das Kunden-Dropdown:
- select_company bekommt SQL-Filter "s.tva_intra IS NOT NULL AND s.tva_intra <> ''"
bzw. "s.tva_intra IS NULL OR s.tva_intra = ''" je nach B2B/B2C-Wahl.
- Das Kundentyp-Select hat onchange=this.form.submit, sodass das Dropdown
ohne extra Klick auf "Suche" direkt neu geladen wird.
Skip-Grund-Zelle:
- opacitymedium-Klasse von td auf inneres span verschoben. Manche Themes
rendern td.opacitymedium mit eigenem Border-Verhalten — das hatte zu
sichtbarem Rahmen-Unterschied in der Uebersprungen-Tabelle gefuehrt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bug: ${{ github.event.head_commit.message }} wurde direkt in den
Bash-Script-Text eingefuegt. Sobald die Commit-Message Klammern,
Backticks oder andere Shell-Sonderzeichen enthielt, kam Syntaxfehler
"line 9: syntax error near unexpected token" und der ganze Deploy
fiel um. Phase 2, 3 und 5 waren betroffen, Phase 4 ging durch
weil zufaellig keine Klammern in der Message waren.
Zusaetzlich Sicherheits-Aspekt: Shell-Injection war moeglich, wenn
jemand eine Commit-Message mit Befehlssubstitution committet.
Fix:
- COMMIT_MSG via env: an den Step uebergeben statt im Run-Block
per Expression einzusetzen.
- RUN_NUMBER, NTFY_AUTH und GIT_TOKEN gleichzeitig via env: harten
fuer Konsistenz und Sicherheit.
- printf %s statt echo fuer mehrzeilige Messages.
- Klammern-frei verifiziert in dieser Commit-Message.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Neuer Cron-Job MahnungCronVersandReminder (taeglich):
- Sucht in llx_mahnung_mahnung Status=ERSTELLT (1) + date_versand IS NULL
+ datec < NOW() - INTERVAL N DAY.
- N steht in der Konstante MAHNUNG_VERSAND_REMINDER_DAYS (Default 2).
- Bei Treffern: Ntfy-Push (Topic MAHNUNG_NTFY_TOPIC) mit Titel + Liste
der bis zu 8 Mahnungen ("MAHN2026-0042 (Stufe 2, 3 Tage alt) — Kunde").
- Optional GlobalNotify-Badge "mahnung_versand" wenn GlobalNotify aktiv.
Modul-Descriptor:
- Cronjobs-Array um Reminder ergaenzt (frequency 1d, priority 55, status 1).
Lang-Keys: 2x (de_DE + en_US) fuer Cron-Label + Description.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Neuer Button "Belege scannen" im Versand-Block der Mahnungs-Karte:
- Action scan_belege durchlaeuft alle Files in DOL_DATA_ROOT/mahnung/<MAHN-Ref>/
- PDFs werden via pdftotext (CLI, mit Verfuegbarkeits-Check) extrahiert
- txt/html werden direkt eingelesen (HTML mit strip_tags)
- Pro Datei wird MahnungTrackingPattern::detectFromText() angewendet —
matched gegen alle aktiven Patterns nach priority DESC
- Treffer landen in $_SESSION als Vorschlag (file, provider, nr, url, label)
UX:
- Vorschlags-Banner mit gruener Linke ueber dem Beleg-Bereich
- Pro Vorschlag: Datei-Icon, Pattern-Label, Sendungsnummer als <code>,
externer Link zur Sendungsverfolgung, "Uebernehmen"-Button
- "Uebernehmen" (action=apply_tracking) speichert tracking_nr +
tracking_provider an der Mahnung und leert Session
- "Verwerfen" (action=dismiss_tracking) entfernt nur Session-Eintrag
Fallback:
- Wenn pdftotext nicht im Container verfuegbar: Warnmeldung im UI,
txt/html werden trotzdem verarbeitet.
OCR fuer Bilder (PNG/JPG) bewusst noch nicht enthalten — separater Schritt
mit Container-Anpassung (Tesseract) wenn gewuenscht.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Filter-Zeile:
- Kunden-Filter "rowid"-Input ersetzt durch $form->select_company()
(Ajax-Suche falls COMPANY_USE_SEARCH_TO_SELECT gesetzt, sonst klassisches
Dropdown). Direkt-Links ?search_socid=74 von der Kundenkarte bleiben
weiterhin funktional (htmlname=search_socid, Backward-kompatibel).
- Neuer Filter "Mindestbetrag" (EUR, Komma zugelassen).
- Neuer Filter "Kundentyp" (alle / B2B / B2C).
Tabelle:
- Neue Spalte "Kontakt" mit Telefon- und Mail-Direktlink-Icons (tel: / mailto:).
- Spalte erscheint sowohl in der Vorschlags- als auch in der Uebersprungen-Tabelle.
MahnungVorschlag::getVorschlaege() + buildAlleVorschlaege():
- SELECT erweitert um s.phone + s.email; werden als soc_phone/soc_email
pro Eintrag mitgegeben.
- Neue PHP-side Filter min_betrag und kundentyp.
Lang-Keys: MahnungKontakt (de_DE + en_US).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Dolibarr ruft complete_head_from_modules() pro societe_prepare_head() mehrmals
auf (core/lib/company.lib.php:365 fuer 'core', 487 fuer 'external', 489 fuer
'remove'). Jeder Aufruf feuert completeTabsHead — der Hook haengte den Tab
entsprechend mehrfach an.
Fix: im Hook auf $parameters['mode']='add' UND filterorigmodule='external'
filtern, damit der Tab genau einmal pro Karte erscheint.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Rechnungen ohne Mahnung: "—" als Zeichen
- Rechnungen mit Mahnung: farbiger Badge verlinkt auf card.php der Mahnung
- Mahnung-ID per Subquery geholt
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Widget basiert jetzt 1:1 auf box_factures_imp.php:
- Alle offenen Rechnungen (nicht nur ueberfaellige)
- Status-Icon wie Original (LibStatut)
- Summenzeile ueber alle offenen (separate Query)
- Link zur Rechnungsliste im Header
Plus: Extra-Spalte Mahnstufe mit farbigem Badge (wenn vorhanden)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- box_mahnung_offen.php: require_once mahnung.class.php nach oben verschoben,
da Mahnung::STATUS_STORNIERT in der SQL-Query vor dem bisherigen Include benutzt wurde
- setup.php: actions_setmoduleoptions.inc.php vor llxHeader() verschoben
(sonst scheitert der ODT-Upload wegen "headers already sent"), Duplikat entfernt
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>