Commit graph

83 commits

Author SHA1 Message Date
Eduard Wisch
d271b9a9ad Trigger: Deploy [deploy]
All checks were successful
Deploy bericht / deploy (push) Successful in 14s
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 07:58:43 +02:00
Eduard Wisch
2ec6686118 Doku: README + CLAUDE.md auf Stand 1.2.0 (Lieferschein-Bestätigung)
- README: shipment-Tab, ODT-Platzhalter {signature}/{signer_name}/{signed_at}/{gps},
  /api/shipments.php-Endpoints, Konstanten-Tabelle, llx_bericht_signature_box, neue
  Architektur-Dateien (actions_bericht, signature_box_editor, ajax/save_signature_box)
- CLAUDE.md: Lieferung als 4. Anker, Phase 1.8 Abschnitt mit Fallen (DOL_DATA_ROOT,
  element_element-Richtung, JWT-Query-Fallback, Backup-Roundtrip)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 07:58:04 +02:00
Eduard Wisch
52489ad21d CI: Ntfy-Benachrichtigungen (Start/Success/Failure) via data/ntfy-action
All checks were successful
Deploy bericht / deploy (push) Successful in 13s
[deploy]

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 06:54:41 +02:00
Eduard Wisch
ca2b796b36 Feature: Lieferschein-Unterschrift via ODT-Hook + PWA-Signatur-Workflow
All checks were successful
Deploy bericht / deploy (push) Successful in 6s
- Neuer API-Endpoint api/shipments.php: Liste Lieferungen zu Auftrag, PDF-Stream, confirm (Unterschrift stempeln)
- ODT-Hook actions_bericht.class.php: ersetzt {signature} Platzhalter via odfphp->setImage, setzt {signer_name}/{signed_at}/{gps}
- Backup-Roundtrip: generateDocument-Backup → signed.pdf erzeugen → Original wiederherstellen
- JWT-Fallback in _jwt.php: ?jwt= Query-Param für <img>/<object> ohne Authorization-Header
- Admin: BERICHT_SIGNATURE_IMAGE_RATIO Feld, Toggle BERICHT_TAB_ON_SHIPMENT, Signature-Box-Editor
- DB: llx_bericht_signature_box für pro-Template mm-Box-Geometrie
- element_type='shipment' in modBericht + lib/bericht.lib.php
- element_element Richtung: commande=source, shipping=target (fk_target=expedition_id)
- DOL_DATA_ROOT-Auflösung für EXPEDITION_ADDON_PDF_ODT_PATH
- Sprachen: de_DE + en_US mit neuen Schlüsseln für Signatur-Workflow

[deploy]

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 06:48:42 +02:00
Eduard Wisch
e462134a17 Dokumentation: PWA-API und Phase 4 Block 1 aktualisiert
- CLAUDE.md auf Stand 2026-04-17 gebracht
- Phase 3 (PWA MVP) und Phase 4 Block 1 als  abgeschlossen dokumentiert
- Alle neuen REST-API-Endpoints aufgelistet (auth, orders, photo, pdf, pages)
- README.md: Neue PWA-Integration-Sektion mit API-Dokumentation
- Architektur erweitert um api/-Ordner mit JWT-Auth-Endpoints

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-04-17 17:30:20 +02:00
Eduard Wisch
74e4509eee Fix: Dateigrößen mit glob+filesize statt dol_dir_list (korrigiert 0B Bug) [deploy]
All checks were successful
Deploy bericht / deploy (push) Successful in 5s
2026-04-17 13:06:07 +02:00
Eduard Wisch
0682b6ce37 Debug: Logging für dol_dir_list Ausgabe in orders.php
All checks were successful
Deploy bericht / deploy (push) Has been skipped
2026-04-17 13:05:25 +02:00
Eduard Wisch
e46b2907f2 API: Content-Disposition:attachment Header für Download-Parameter
All checks were successful
Deploy bericht / deploy (push) Has been skipped
2026-04-17 12:56:16 +02:00
Eduard Wisch
641e16a2bc [deploy] API: Schnell-Auftrag-Anlage + Kunden-Defaults + JWT 30 Tage
All checks were successful
Deploy bericht / deploy (push) Successful in 5s
- POST /api/orders.php?action=create: legt Draft-Auftrag an, übernimmt
  Kunden-Defaults (Zahlungsbedingung, Zahlart, Bankkonto, Incoterms,
  Lieferadresse) und setzt Hauptansprechpartner als externen Kontakt.
  Titel wird in Extrafield options_auftragsbeschreibung abgelegt.
- /api/customers.php: liefert cond_reglement_label + mode_reglement_label
  damit die PWA die übernommenen Defaults anzeigen kann.
- JWT-TTL von 7 auf 30 Tage hochgesetzt — deckt Urlaubszeiten ab und
  verhindert häufiges Neu-Anmelden.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 17:42:39 +02:00
Eduard Wisch
062e5c1b50 API: ref_client (Kunden-Bestellnr.) in Auftragsliste hinzugefügt
All checks were successful
Deploy bericht / deploy (push) Successful in 5s
[deploy]

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-16 10:49:47 +02:00
Eduard Wisch
d9c973513c Globale Bericht-Liste entfernt — nur PWA-Zugriff
All checks were successful
Deploy bericht / deploy (push) Successful in 5s
- menus => 0 gesetzt
- $this->menu Array geleert
- Lang-Strings BerichteList, BerichtSource, BerichtLastModified, BerichtAllStatus entfernt

[deploy]

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-16 10:45:23 +02:00
Eduard Wisch
42906a4601 [deploy] Fix: Foto-URL relativ zu mobile_upload.php (ajax/get_photo.php)
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-13 13:18:32 +02:00
Eduard Wisch
1730c9fb00 [deploy] Fix: Foto-URL relativ statt absolut (custom/bericht Problem)
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-13 13:17:35 +02:00
Eduard Wisch
0c6a262fe4 [deploy] PWA Cache-Control Header hinzugefügt
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
Verhindert dass Browser die mobile_upload.php cached
- Cache-Control: no-cache, no-store, must-revalidate
- Pragma: no-cache
- Expires: 0

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-13 13:15:33 +02:00
Eduard Wisch
a31e063e7a PWA: Foto-Galerie mit Zoom und Swipe [deploy]
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
- Neue API: list_photos.php listet vorhandene Bilder
- Neue API: get_photo.php liefert Bilder per Token aus
- PWA zeigt Galerie mit allen vorhandenen Fotos
- Tippen auf Thumbnail öffnet Vollbild-Viewer
- Pinch-to-Zoom, Doppeltap, Swipe-Navigation
- Galerie aktualisiert sich nach Upload automatisch

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-13 13:09:30 +02:00
Eduard Wisch
1512e4d706 PWA: Bild-Viewer mit Pinch-Zoom und Swipe [deploy]
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
- Scanner-Seiten können vor Upload gezoomt werden
- Pinch-to-Zoom (2 Finger)
- Doppeltap für Zoom-Toggle
- Swipe links/rechts für Navigation
- Vollbild-Overlay mit Schliessen-Button

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-13 13:05:01 +02:00
Eduard Wisch
7c21446f98 Bild-Viewer mit Zoom und Swipe-Navigation [deploy]
All checks were successful
Deploy bericht / deploy (push) Successful in 2s
- Neuer Lightbox-Viewer für Bilder in der Anhänge-Liste
- Pinch-to-Zoom (Touch) und Mausrad-Zoom (Desktop)
- Doppelklick/Doppeltap für Zoom-Toggle (1x ↔ 2.5x)
- Swipe links/rechts für Navigation zwischen Bildern
- Tastatur: ← → Esc + - 0
- Bilder pro Gruppe navigierbar

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-13 13:00:17 +02:00
Eduard Wisch
b09030afd5 Deploy: Dokumenten-Scanner mit OCR [deploy]
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-13 12:21:22 +02:00
Eduard Wisch
aa58b5692c Dokumenten-Scanner mit OCR-Unterstützung
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
Neuer "Dokument scannen" Button in der Mobile-Upload-Seite:
- Eigene Scanner-View für mehrseitige Dokumente
- Seiten-Vorschau-Leiste mit Thumbnails
- Seiten hinzufügen/löschen vor dem Upload
- Backend-Verarbeitung zu PDF

OCR-Verarbeitung (wenn verfügbar):
- ocrmypdf für durchsuchbares PDF (deu+eng)
- Automatische Rotation und Schräglagekorrektur
- Fallback auf ImageMagick oder TCPDF

Server-Konfiguration (optional für OCR):
  apt install tesseract-ocr tesseract-ocr-deu ocrmypdf

[deploy]

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-13 09:59:14 +02:00
Eduard Wisch
04fdd87ffd Kamera-View: Mehrere Fotos ohne Kamera-Neustart
All checks were successful
Deploy bericht / deploy (push) Successful in 2s
Eigene Kamera-View mit getUserMedia() statt nativer Kamera-App:
- Vollbild-Overlay mit Live-Stream
- Auslöser, Vorschau, Speichern/Verwerfen
- Kamera bleibt nach Speichern offen
- Front/Back-Wechsel
- Session-Counter

[deploy]

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-13 09:18:50 +02:00
5fc6338ced API: DELETE /reports.php?id=X — Bericht löschen (für PWA) [deploy]
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 15:32:33 +02:00
7e199a79ad Trigger: Deploy Lösch-Dialog [deploy]
All checks were successful
Deploy bericht / deploy (push) Successful in 2s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 15:25:06 +02:00
85005e3335 Lösch-Dialog in Bericht-Übersicht: eigener Modal statt fehlender Handler
All checks were successful
Deploy bericht / deploy (push) Has been skipped
In der Übersicht (ohne Editor) wurde data-dolconfirm nicht abgefangen,
weil editor.js dort nicht geladen wird. Inline-Script mit gleichem
bericht-dolmodal-Stil ergänzt — kein browser-confirm().

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 15:23:00 +02:00
9271ec279f Foto-Upload vom Bericht entkoppeln: Fotos landen im Auftragsordner
All checks were successful
Deploy bericht / deploy (push) Successful in 2s
- Token-Tabelle: fk_bericht → fk_element + element_type (generisch)
- Migration: bestehende Tokens auf neue Spalten migrieren
- upload_photo API: Foto direkt nach commande/{ref}/, kein Bericht/BerichtPage mehr
- mobile_upload.php: Upload-Ziel über Token-Methode getUploadDir() ermitteln
- Token-Erstellung: element_id + element_type statt berichtid (abwärtskompatibel)
- QR-Modal: Token für Auftrag statt für Bericht; Polling auf Anhänge-Änderung [deploy]

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 13:48:09 +02:00
b9e204f81c Hack-Font deaktiviert, fallback auf Helvetica [deploy]
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
TCPDF::addTTFfont produziert bei allen 4 Hack-TTFs den gleichen Key 'hack'
(PostScript-Family-Kollision) und erzeugt eine hack.php die sich selbst
überschreibt. Der HTML-Parser löst bei <b>/<i>-Tags dann SetFont-Calls aus,
die auf nicht-existente Style-Varianten fallen zurück und mit
'Could not include font definition file: hack' abbrechen — keine Menge
an SetFont-Overrides fängt das zuverlässig ab.

Lösung: hack_font_key = helvetica. PDF-Generation funktioniert garantiert.
Hack wird später mit vorab-offline-konvertierten .php-Definitionen neu
eingebunden.
2026-04-09 16:01:43 +02:00
3ec303c48e Hack-Font: nur Regular, Styles komplett ignorieren [deploy]
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
Problem: TCPDF_FONTS::addTTFfont leitet den Font-Key aus dem PostScript-
Family-Namen der TTF ab. Alle 4 Hack-TTFs teilen den internen Namen 'Hack'
und erzeugen dieselbe hack.php, die sich gegenseitig überschreibt.
SetFont(hack, 'B') sucht dann hackB.php → existiert nicht →
'Could not include font definition file: hack'.

Lösung: nur Hack-Regular.ttf registrieren, SetFont-Override erzwingt
bei der Hack-Family immer style=''. Header-Titel nutzt nur Schriftgröße
statt echter Bold-Variante.
2026-04-09 15:58:09 +02:00
b338d4e369 BerichtPdf: SetFont-Override routet Hack-Styles auf die richtige Family [deploy]
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
TCPDF_FONTS::addTTFfont registriert jede TTF als eigene Family ohne
Styles (hackregular, hackbold, hackitalic, hackbolditalic). SetFont(key,'B')
suchte dann nach 'hack.php' und brach mit 'Could not include font
definition file' ab.

Fix: hack_font_styles-Map ['' / B / I / BI] => family, SetFont-Override
mapped den angeforderten Style auf die passende Family mit leerem Style
und fällt kaskadierend zurück (BI→B→'', I→'').
2026-04-09 15:53:02 +02:00
3a7ed278e5 Ajax-Endpoints: Fatal-Handler + Output-Buffer, mysoc defensiv [deploy]
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
- ajax/_inc.php: ob_start() + register_shutdown_function fangen PHP Notices
  und Fatals auf, geben strukturiertes JSON zurück (vorher Server-Fehler
  'kein JSON' weil PHP-Warning mitten im Body stand).
- generate_pdf.php/preview_pdf.php: mysoc, logo-Pfad defensiv geprüft.
2026-04-09 15:50:36 +02:00
d5b89747be Leere Dolibarr-Confirm-Box entfernen (MutationObserver) [deploy]
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
2026-04-09 15:47:40 +02:00
bf368122aa Keine Browser-Dialoge mehr — eigene dolModal-Helper [deploy]
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
Eddys Browser blockiert alert/confirm/prompt, deshalb sind die Dialoge
unsichtbar. Komplett umgestellt:

- js/editor.js: dolAlert/dolConfirm/dolPrompt als Promise-basierte Modals
  mit passendem Dark-Theme-Styling. Globaler click-Handler für
  data-dolconfirm an Links/Buttons.
- Alle alert()/confirm()/prompt() in editor.js ersetzt (Template speichern,
  Finalize, Upload, Löschen, Signatur-Verify usw.).
- bericht_card.php + admin/setup.php: onclick="return confirm(...)" durch
  data-dolconfirm="..." ersetzt.
- btn-finalize: butActionConfirm → butAction (Dolibarr erzeugt sonst
  automatisch einen leeren jQuery-UI-Confirm-Dialog
  #confirm-dialog-box-btn-finalize, unser eigener Handler macht das jetzt).
- css/bericht.css: Styling für .bericht-dolmodal*.
2026-04-09 15:46:25 +02:00
d40587845f PDF-Header mit Logo+Titel, Footer mit Seitenzahl, Hack-Font beschreibbar [deploy]
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
- Neue Klasse BerichtPdf / BerichtPdfFpdi (Trait-basiert):
  * Header: links Bericht-Titel (Bold) + Firmenname, rechts Firmen-Logo (max 40x18mm),
    Trennlinie. Top-Margin jetzt 30mm für den Header-Bereich.
  * Footer: zentriert "Seite X / Y" mit TCPDF-Aliases.
  * berichtInit(): kompiliert Hack-TTFs nach DOL_DATA_ROOT/bericht/tcpdf_fonts/
    (beschreibbar) und bindet sie per AddFont an die PDF-Instance.
    Vorher schlug addTTFfont still fehl weil K_PATH_FONTS read-only war —
    deshalb kam weder Titel noch Notiz in Hack.
- bericht_ensure_hack_font($pdf) zieht den Font-Key jetzt aus der Instance
  (BerichtPdfTrait), sonst Fallback helvetica.
- bericht_write_note_html() wrapped das CKEditor-HTML in
  <span style="font-family:hack...;"> damit writeHTMLCell den Hack-Font
  tatsächlich verwendet.
- Composite-Branch: $mT=30 / $mB=16 damit Bilder nicht unter dem Header
  sitzen.
- ajax/generate_pdf, ajax/preview_pdf, api/pdf, api/reports, bericht_batch:
  alle nutzen jetzt BerichtPdf(Fpdi), setzen SetMargins(10,30,10),
  setPrintHeader(true) und berichtInit() mit Titel, mysoc->name und Logo.
2026-04-09 15:39:42 +02:00
36aad9539a Fix: weiße Seiten nach Reload — bgImage nicht mehr als blob-URL serialisieren [deploy]
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
Die fabric.Image der Quell-Bilder wurde mit toJSON([bgImage]) inkl. ihrer
blob:-URL gespeichert. Nach Reload ist die Blob-URL ungültig, fabric kann
das Bild nicht holen → weißer Canvas.

Fix: bgImage-Objekte beim Speichern aus dem JSON rausfiltern. Beim Laden
immer rerenderCurrent() aufrufen (frisches Quellbild aus page_image.php),
dann nur die Overlay-Shapes via enlivenObjects restoren.
2026-04-09 15:28:45 +02:00
021ef1cbb2 DolEditor für Notiz + Notiz unter Bild + Hack-Font im PDF [deploy]
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
- bericht_card.php: Plain-Textarea durch DolEditor (CKEditor, dolibarr_notes-Toolbar)
  ersetzt, damit Notizen formatiert werden können.
- editor.js: getNoteValue()/setNoteValue() Helper für transparentes
  CKEditor/Textarea-Handling an allen Zugriffsstellen.
- lib/bericht.lib.php:
  * bericht_write_note_html() rendert CKEditor-HTML via TCPDF::writeHTMLCell
  * Composite-Branch: Notiz direkt unter dem Bild statt mit SetY(-20) unten
  * bericht_ensure_hack_font() registriert Hack-TTFs beim ersten PDF-Run
  * Alle helvetica-SetFont-Calls auf Hack umgestellt (Corporate-Font)
- fonts/: Hack-Regular/Bold/Italic/BoldItalic.ttf (Eddys Corporate-Font)
- bericht_batch.php: ebenfalls Hack-Font
2026-04-09 15:25:10 +02:00
9c7ef73061 fix: Image-Load robust + Schutz gegen Speichern ohne Bild
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
Diagnose: bei alten Berichten ohne composite_path wurde das bgImage
beim Öffnen nicht geladen (fabric.Image.fromURL mit crossOrigin bei
Blob-URL hing sich auf). Danach hat jeder Save das Composite ohne
Bild gespeichert → PDF-Seite leer.

Fix 1 — renderImage:
- Native Image() statt fabric.Image.fromURL
- Kein crossOrigin bei Blob-URLs (überflüssig, verursachte Hänger)
- Timeout 10s mit console.warn
- onerror mit Debug-Info (mime, bufSize)
- applyTool() nach Einfügen damit Tool-Lock greift

Fix 2 — savePageAnnotations:
- Wenn KEIN Bild im Canvas ist (weder bgImage=true noch type=image),
  wird der Save ABGEBROCHEN statt ein leeres Composite zu speichern.
- Dadurch kann ein alter Bericht nicht versehentlich mit einem
  leeren Composite überschrieben werden wenn der Bild-Load hakt.
- Toast-Warnung für den User, console.warn für Debug.

Fix 3 — finalize-Handler:
- Native confirm() vor dem Finalisieren
- Bessere Fehler-Diagnose: lese Response als text(), parse JSON,
  logge bei JSON-Parse-Fehler den Raw-Body
- toast() statt alert() für Fehler

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
2026-04-09 14:58:18 +02:00
01fbd72310 fix: alte Berichte ohne bgImage im JSON laden Quellbild nach
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
Wenn ein Bericht vor Phase 6 gespeichert wurde, enthält fabric_json
nur Shapes (Pfeile, Text etc.) — das Quellbild war damals noch
pdfCanvas-Hintergrund. Beim Restore war jsonHasObjects=true und
rerenderCurrent wurde nicht aufgerufen → Canvas weiß.

Fix: Zusätzlicher Check jsonHasBgImage. Wenn kein Image im JSON
(weder bgImage=true noch type=image), wird zusätzlich zu den
geladenen Shapes rerenderCurrent() aufgerufen, das das Quellbild
als fabric.Image hinter die Shapes legt.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
2026-04-09 14:42:06 +02:00
e0ae936811 fix: Bericht öffnen — Canvas war weiß obwohl Seiten Bilder hatten
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
Drei Probleme behoben:

1. Wenn fabric_json existierte aber leer oder ohne Objekte war, wurde
   rerenderCurrent() NICHT aufgerufen und das Canvas blieb leer.
2. Canvas-Dimensionen wurden nur im fabric_json-Zweig gesetzt — bei
   altem Berichten ohne JSON fehlte der Init und das Canvas war 1x1.
3. loadFromJSON-Callback hat zwar das Promise resolved, aber die
   nachfolgende applyTool() für Tool-Locking fehlte, dadurch waren
   die geladenen Bilder nach Reload immer ziehbar.

Fix:
- Canvas-Dimensionen IMMER zuerst auf A4 setzen
- Nur laden wenn fabric_json tatsächlich Objekte enthält
- Sonst (oder bei Parse-Fehler) Fallback auf rerenderCurrent() das
  das Quell-Bild frisch als fabric.Image lädt
- applyTool() nach loadFromJSON damit Tool-Lock-Status korrekt ist

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
2026-04-09 14:39:56 +02:00
9e96063bbc fix: Button-Style-Selector in Anhänge-Spalte beschränken
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
Vorheriger Fix '.bericht-attachments button { width:100% }' hat auch
die kleinen Icons in den Anhang-Items (Mülleimer zum Löschen, Buttons
in Listen-Items) erwischt und auf volle Breite gepumpt. Dadurch wurde
der Dateiname nicht mehr sichtbar.

Fix: Selector auf .bericht-add-selected + .bericht-upload beschränken
(die beiden Action-Blöcke am Ende der Spalte). Die Item-Buttons
innerhalb der Liste behalten ihr Default-Styling.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
2026-04-09 14:38:16 +02:00
c98fcc829c fix: Anhänge-Buttons einheitlich + Bilder bei Zeichnen-Tools nicht mehr ziehbar
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
Anhänge-Spalte:
- Buttons und Labels waren unterschiedlich breit → Scrollbalken querte
  die Box. Fix: alle Controls (.butAction, button, select) in
  .bericht-attachments auf display:block + width:100% + box-sizing
  border-box, einheitliches Padding, ellipsis bei Überlänge.
- overflow-x: hidden verhindert horizontale Scrollbar im Container.
- min-width: 0 damit grid-Container nicht überlaufen.

Zeichen-Tools:
- Problem: Beim Malen wurden die Bilder (fabric.Image) mitbewegt
  weil sie immer selectable/evented waren.
- Fix: applyTool() setzt auf ALLEN Canvas-Objekten selectable=false
  und evented=false sobald ein Zeichen-Tool aktiv ist. Nur bei
  'select' werden sie wieder aktiviert.
- discardActiveObject() nach Tool-Wechsel damit keine Selektion
  aktiv bleibt.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
2026-04-09 14:18:49 +02:00
efa94217eb fix: Fabric canvas-container absolut positioniert — weißer Bereich rechts weg
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
Fabric.js wickelt #fabric-canvas in einen .canvas-container ein. Meine
bisherige CSS-Regel '#fabric-canvas.fabric-overlay position:absolute'
wirkte nur auf das innere Canvas, nicht auf den Container. Dadurch saß
der Container im normalen Flex-Flow neben dem pdfCanvas und verdrängte
es nach links — der rechte Bereich wurde vom Container als weiße Fläche
überlagert und verdeckte Bild-Inhalte.

Fix: neue CSS-Regel '.bericht-canvas-wrap .canvas-container position:
absolute !important' damit der Container wie vorgesehen absolut
positioniert wird und den pdfCanvas nicht aus dem Flow drückt.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
2026-04-09 14:11:09 +02:00
bf1ad1bafa fix: Layout-Selector für Mehrfach-Übernahme aus Anhänge-Liste
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
Der Layout-Dropdown in der Toolbar bezog sich nur auf die aktuell
angezeigte Seite, nicht auf die Übernahme-Aktion. Ergebnis: User
stellten 2x2 ein, kreuzten 4 Bilder an, klickten 'Übernehmen' — und
bekamen 4 einzelne Seiten statt einer Grid-Seite.

Fix: Neben dem Übernahme-Button eigener Layout-Selector mit Optionen
Einzeln / 2 / 2v / 4 / 6 / Vorher-Nachher. Bei Grid-Layout werden
die ausgewählten Bilder automatisch in Gruppen à slotCount aufgeteilt
und entsprechend viele Seiten angelegt (z.B. 10 Bilder + grid_4 = 3
Seiten mit 4+4+2). Die einzelnen ▭▭/▦/VN-Buttons entfallen damit.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
2026-04-09 13:31:26 +02:00
f55da13234 fix: Empty-State-Hinweis nur wenn wirklich keine Seite existiert
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
Vorher blieb 'Seite wählen oder Fotos hinzufügen' auch sichtbar wenn
eine Seite geladen wurde — die .empty-Klasse wurde nur beim Seiten-
wechsel per loadPage() entfernt, nicht beim initialen Load. Resultat:
bei einem Bericht mit bestehender Seite erschien der Text mitten im
weißen Canvas.

Fix: beim init() wird die Klasse nur gesetzt wenn überhaupt keine
Page-Thumbs existieren (Bericht wirklich leer).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
2026-04-09 13:29:46 +02:00
195942a2f9 feat: Phase 6 — Client-WYSIWYG via Composite-PNG + Text-BG + Dark-Input-Fix
All checks were successful
Deploy bericht / deploy (push) Successful in 2s
Paradigmen-Wechsel: Editor rendert bei jedem Save sein Fabric-Canvas
als PNG und lädt es hoch. PDF nutzt dieses PNG 1:1 statt die Shapes
serverseitig nachzuzeichnen.

Damit ist garantiert: was du im Editor siehst, ist EXAKT das was im PDF
landet. Alle Pfeil/Text/Shape-Rendering-Bugs zwischen Fabric-JSON und
PHP-Nachzeichnung sind Geschichte.

Kernänderungen:

1. DB: Neue Spalte bericht_page.composite_path (Migration im init())
2. ajax/save_annotations.php: nimmt multipart file 'composite' entgegen,
   speichert es unter bericht/work/<fkb>/composite_<pid>.png
3. lib/bericht.lib.php: bericht_render_page_to_pdf prüft composite_path
   zuerst — wenn vorhanden, wird eine Seite mit genau diesem PNG als
   volles Bild gerendert, fertig. Fallback auf alte Logik bei alten
   Berichten ohne Composite.
4. editor.js renderImage: Quellbild wird NICHT mehr auf pdfCanvas
   gezeichnet, sondern als fabric.Image ins Fabric-Canvas geladen —
   ZIEHBAR, SKALIERBAR, ROTIERBAR wie jedes andere Objekt.
   Mehrere Bilder auf einer Seite kein Problem mehr.
5. editor.js savePageAnnotations: nach Shape-State wird toDataURL
   mit multiplier:2 aufgerufen, PNG-Blob hochgeladen zusammen mit
   fabric_json (für spätere Edits) und note.
6. editor.js loadPage: wenn fabric_json existiert, wird dieses
   clientseitig wieder eingeladen (inkl. eingebettete Bilder) — das
   Quell-Bild wird nicht mehr neu aus der Quelle geholt. Bei leerer
   Seite läuft der alte Render-Flow.

Phase 6 Bonus — Text mit Hintergrund:
- Neuer color-picker 'BG:' in der Toolbar + 'Ø'-Button (kein BG)
- Fabric IText bekommt textBackgroundColor + padding:6
- Bei selektiertem Text-Objekt wird BG live angewendet
- Dataset-Flag 'active' toggelt zwischen ein/aus

Dark-Input-Fix:
- Textarea in .bericht-page-note nutzte --inputbackgroundcolor
  (existiert in awl-dark nicht → Fallback #fff = weiße Fläche mit
  schwarzer Schrift auf Dark-Theme)
- Jetzt: --colorbackbody + --colortext + --colorboxbordertitle1
- Generischer Input-Style für alle Text-Eingaben in .bericht-editor

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
2026-04-09 13:26:57 +02:00
4dad496788 fix: Leerer Bericht-Editor zeigt DIN A4 Hochformat-Platzhalter
All checks were successful
Deploy bericht / deploy (push) Successful in 2s
Wenn noch keine Seite geladen ist, wirkte der Editor mit dem winzigen
1x1-Canvas sehr abgebastelt. Jetzt zeigt der Canvas-Wrap im leeren
Zustand eine echte A4-Hochformat-Fläche (max 900px, aspect-ratio 1:1.414)
mit Hinweistext 'Seite wählen oder Fotos hinzufügen'.

- .bericht-canvas-wrap.empty setzt aspect-ratio auf A4
- ::after Pseudo-Element als Hinweistext
- init() markiert den Wrap als 'empty'
- loadPage() entfernt die Klasse sobald ein Bild geladen wird
- min-height 400px damit auch ohne Content Platz da ist

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
2026-04-09 13:08:38 +02:00
d043dfaf46 fix: Bericht create() backwards-kompatibel gegen fehlende Migrations-Spalten
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
Nach dem Deploy sind die neuen Spalten version/fk_bericht_parent
(Phase 5.3) erst da wenn das Modul einmal reaktiviert wurde.
Bis dahin knallte der INSERT mit 'Unknown column version' und man
konnte keine neuen Berichte anlegen.

Fix: create() prüft via SHOW COLUMNS welche optionalen Spalten
(page_format, page_orientation, is_template, template_label,
version, fk_bericht_parent) tatsächlich existieren und nimmt nur
die vorhandenen in das INSERT-Statement auf.

Damit laufen auch Systeme, bei denen die Migration noch nicht
durchgelaufen ist, ohne Fehler weiter. Nach der Reaktivierung
werden automatisch alle Spalten befüllt.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
2026-04-09 09:19:17 +02:00
c8f7d7d527 feat: Phase 5.9 Materialliste API + DB + 5.8 Vorbereitung
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
- Neue Tabelle llx_bericht_material (element_type, fk_element, label,
  qty, unit, note, fk_user_creat, datec) via Migration
- api/materials.php: GET list, POST anlegen, DELETE löschen

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
2026-04-09 09:18:29 +02:00
dbddee7791 feat: Block C — Editor-Polish, Vorher/Nachher, Versionierung, Batch-Modus
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
Desktop-Editor Polish:
- Aktives Seiten-Thumbnail deutlich markiert (scale + shadow + blauer
  Label-Hintergrund)
- Titel-Feld pro Seite in der DB (llx_bericht_page.title)

Phase 5.2 Vorher/Nachher-Layout:
- Neuer layout-Typ 'before_after' in bericht_render_page_to_pdf
- Zwei Bilder nebeneinander, Labels 'Vorher' / 'Nachher', Titel oben
- 'VN'-Button in der Anhänge-Grid-Leiste
- create_grid_page akzeptiert before_after
- Layout-Dropdown hat before_after + title_only Option

Phase 5.2b Title-Only Seiten:
- Reine Titel/Zwischentitel-Seiten ohne Bild
- 32pt Titel zentriert, optional Notiz darunter
- bericht_render_page_to_pdf behandelt title_only separat

Phase 5.3 Bericht-Versionierung:
- Neue Spalten version + fk_bericht_parent in llx_bericht
- Bericht::duplicateAsNewVersion() kopiert alles inkl. Seiten
- '🔀 Neue Version'-Button im Editor-Footer
- Versions-Links in der Meta-Zeile (v1, v2, v3 …) mit Sprungmöglichkeit
- Alte Versionen bleiben unverändert erhalten

Phase 5.6 Batch-Modus:
- Neue Seite bericht_batch.php mit Filter (Datum von/bis, Suche)
- Checkbox-Liste aller finalisierten Berichte
- 'Ausgewählte als Sammel-PDF herunterladen' → FPDI merged alle
  final_pdf_path in ein neues PDF mit Inhaltsverzeichnis-Seite
- Link im Admin-Setup

api/pages.php:
- POST-Update akzeptiert jetzt auch title und layout zusätzlich zu
  note und rotation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
2026-04-09 09:10:56 +02:00
1705744809 fix: Cronjob entfernt, Cleanup opportunistisch beim Token-Create
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
Der Dolibarr-Cron-Scheduler ist auf Prod nicht aktiv, deshalb hing
der tägliche Cleanup-Job 'Expired Upload-Tokens bereinigen' auf
'Geplant' und GlobalNotify meldete ihn als hängenden Job.

Lösung: Cronjob komplett aus dem Modul entfernt. Das Cleanup läuft
jetzt opportunistisch bei jedem neuen Token-Insert:

BerichtUploadToken::create() führt vor dem INSERT ein
DELETE FROM llx_bericht_upload_token WHERE expires_at < NOW()
aus. Das ist minimal teurer (1 Query extra pro Token-Create,
~0ms bei leerer Tabelle), aber vollständig cron-unabhängig.

WICHTIG für Prod: Nach Deploy muss der bestehende Cron-Eintrag
manuell aus llx_cronjob gelöscht werden (oder das Modul einmal
deaktiviert + reaktiviert werden, dann wird er mit remove() bzw.
init() neu gesetzt — ohne Eintrag). GlobalNotify ist danach ruhig.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
2026-04-09 08:58:39 +02:00
344f884a0f feat: Bericht-Vorlagen + Whisper-Transkription + Cron-Fix
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
Bericht-Vorlagen (Phase 5.5):
- DB: is_template, template_label in llx_bericht
- Bericht::fetchAllTemplates() und createFromTemplate()
- fetchAllForElement() blendet Vorlagen aus
- ajax/save_as_template.php erzeugt Vorlage aus aktuellem Bericht
- Desktop-Editor: '📋 Als Vorlage' Button im Action-Bereich
- Bericht-Übersicht: Vorlagen-Dropdown beim + Neu Button
- api/templates.php: GET list + POST create_from_template

Schnell-Bericht (Phase 4.a/4.i):
- api/reports.php?action=create POST-Endpoint: Titel, Format,
  Orientation, ODT-Template, optional template_id
- api/odt_templates.php: Liste der Deckblatt-Vorlagen

Whisper-Transkription (Phase 5.7):
- api/transcribe.php: POST mit relpath, nutzt externen Whisper-
  HTTP-Endpoint (whisper.cpp server ODER OpenAI-kompatibel)
- Konfiguration im Admin: BERICHT_WHISPER_URL/MODE/API_KEY/LANG
- Sprache default 'de'

Cron-Fix:
- BerichtUploadToken::cleanupExpired() ist jetzt Instanz-Methode
  (Dolibarr ruft new Klasse($db) bei jobtype=method auf)
- Returnwert für Cron-Success/Failure
- Statische Variante als cleanupExpiredStatic() für direkte Aufrufe
- Damit läuft der tägliche Cron 'Expired Upload-Tokens bereinigen'
  nicht mehr hängend

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
2026-04-09 08:27:45 +02:00
f37445ac9c feat: Unterschriften-Verifikation + Seiten-Reorder API
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
ajax/verify_signature.php:
- Liest Metadaten aus .meta.json neben einer Unterschrift-Seite
- Rechnet SHA256 über die zum Signaturzeitpunkt existierenden Seiten
  (erste N laut meta.page_count_at_signing)
- Vergleicht mit dem gespeicherten Hash → verified true/false
- Liefert reason bei Fehlschlag (Seitenanzahl oder Inhalt geändert)

bericht_card.php:
- Seiten mit .meta.json kriegen 🔒-Badge + grünen Rand
- 🔍-Button neben dem Löschen-Button öffnet Verifikations-Modal

editor.js:
- showSignatureVerifyResult-Modal zeigt alle Metadaten,
  beide Hashes, GPS als OpenStreetMap-Link, IP, User, Zeit
- Grün/Rot je nach Verifikationsergebnis

api/pages.php:
- Neuer Action reorder: POST mit {order:[ids]} schreibt neue
  page_order-Werte als Transaktion

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
2026-04-09 08:18:51 +02:00
5db126210c feat: Unterschriften-Härtung + Kundenkarten-API
All checks were successful
Deploy bericht / deploy (push) Successful in 2s
Unterschrift (api/pages.php?action=signature):
- Metadaten einbrennen: Überschrift, Bericht-Ref, Parent-Ref, Kunde,
  Datum/Zeit (Server), Unterzeichner-Name, Bestätigungstext, GPS
- SHA256 Hash-Verkettung über alle vorherigen Seiten + Bericht-Ref
  → nachträgliches Austauschen von Seiten fällt auf
- Composite-Canvas: Header mit Metadaten, Unterschrift mittig,
  Footer mit Hash + Server + Zeitstempel
- Metadaten-JSON neben der PNG (für spätere Verifikation)
- signer_name ist Pflicht, gps_lat/gps_lon optional
- Page-Note zeigt Unterzeichner + Zeit statt nur 'Unterschrift Kunde'

Kundenkarten-API (api/customers.php):
- GET ohne id = Liste mit Suche (q-Param), zeigt Stammdaten + Bericht-Count
- GET mit id = Detail: Stammdaten, letzten 50 Aufträge, 50 Rechnungen,
  100 Berichte über UNION aus commande/facture JOIN
- Nur echte Kunden (client IN (1,2,3)), keine reinen Lieferanten

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
2026-04-09 08:06:21 +02:00