bericht/CLAUDE.md
Eduard Wisch a8d8291c3a API 1.5.1: Thumbnails, Aufnahmezeit, Seitenfelder, Textnotizen
Die API-Seite zur Baustelle-PWA holt nach, was der Editor mit 1.5.0 bekommen hat.

photo.php kann jetzt size=thumb&w=<px> und rendert ueber bericht_attachment_thumb()
(GD, Cache, EXIF-Rotation) statt die Originaldatei auszuliefern. size=small konnte nur
ein von Dolibarr vorgefertigtes thumbs/<name>_small.<ext> liefern - fuer Uploads ueber
orders.php?action=upload_photo gibt es das nicht, dort laeuft kein vignette(). Die PWA
lud dadurch fuer jede Kachel das komplette Foto: gemessen 9,8 KB statt 235 KB je Kachel.
Antwort mit ETag aus der mtime, Folgeaufrufe enden mit 304.

orders.php?action=photos liefert taken_at (EXIF-Aufnahmezeit ueber bericht_file_taken_at,
sonst Dateidatum) und sortiert danach statt nach filemtime.

reports.php gibt je Seite title und composite_path aus. title konnte ueber pages.php
gesetzt werden, kam aber nie zurueck. composite_path ist das im Editor gebaute
Seitenbild - ohne das zeigen Clients nur das Rohbild ohne Anmerkungen, bei
Raster-Layouts eines von bis zu sechs Bildern und bei title_only gar keins.

Neu: api/note.php - Textnotizen zum Auftrag, aufgebaut wie die Sprachnotiz. Die Notiz
liegt als notiz_<betreff>_<datum>.txt im Auftragsverzeichnis und ist damit auch im
Dolibarr-Auftrag und in der Anhaenge-Spalte des Editors sichtbar. Liste, Lesen, Anlegen,
Aendern, Loeschen. Der file-Parameter wird gegen ^notiz_[A-Za-z0-9_.-]*\.txt$ geprueft;
gegen Pfad-Ausbrueche getestet.

ROADMAP.md wiederhergestellt - sie war mit eb37a4b geloescht worden, weil alle Punkte
abgehakt waren. Sie bleibt ab jetzt liegen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 18:11:42 +02:00

17 KiB

Bericht-Modul — Projekt-Status & Architektur

Stand 2026-08-22 (Version 1.5.1)

Dolibarr-Custom-Modul für Arbeitsberichte mit Browser-PDF-Editor + PWA-API-Layer + Lieferschein-Unterschrift.

Architektur (final)

Tab-Verteilung

  • Auftrag (commande) — primärer Erstellungsort, Berichte mit element_type='order', Auftragsnummer = commande->ref direkt
  • Rechnung (facture) — Berichte mit element_type='invoice', Auftragsnummer aus array_options['options_auftragsnummer']
  • Angebot (propal) — möglich, gleiche Logik
  • Lieferung (shipping/expedition)element_type='shipment', fk_element = llx_expedition.rowid. Wird ausschließlich vom Signatur-Workflow der PWA verwendet (Anker für Audit-Spur), nicht für klassisches Berichte-Anlegen. Verknüpfung zum Auftrag über llx_element_element (sourcetype='commande', targettype='shipping').
  • Kundenkarte (thirdparty) — read-only Übersicht (Phase 1.7), zeigt alle Berichte des Kunden über Joins, kein Anlegen, kein Speicher-Ort

Verknüpfung Auftrag → Rechnung

Berichte gehören 1:1 zu einem Parent (element_type + fk_element). Auf einer Rechnungs-Seite werden ZUSÄTZLICH die Berichte der verknüpften Aufträge angezeigt — über fetchObjectLinked() der Rechnung. Mit Button "→ Dieser Rechnung zuordnen" erzeugt man einen Eintrag in llx_element_element (Standard-Dolibarr-n:m-Verknüpfung), damit der Bericht beim Finalisieren auch im ECM der Rechnung landet.

DB-Tabellen

  • llx_bericht — Bericht (rowid, ref, titel, element_type, fk_element, auftragsnummer, template_odt, status, final_pdf_path, format, orientation, ...)
  • llx_bericht_page — Seite (rowid, fk_bericht, page_order, source_type, source_path, source_page, rotation, fabric_json, note, layout, image_scale, image_align, ...)
  • llx_bericht_upload_token — Phase 2: Mobile-Upload-Tokens (rowid, token, fk_bericht, expires_at, created_by)
  • llx_bericht_signature_box — Pro PDF-Template gespeicherte Signatur-Box-Geometrie (rowid, template_name, page, x_mm, y_mm, w_mm, h_mm, label, tms). Nur relevant für FPDI-Stempel-Fallback; der ODT-Workflow nutzt stattdessen den {signature}-Platzhalter.

Permissions

  • bericht/read (Standard für alle)
  • bericht/write (Standard für alle)
  • bericht/delete (Standard für alle)
  • bericht/admin (nur explizit)

WICHTIG: $this->rights[$r][4] = perms-Name ('read'), [5] = subperms (leer). NICHT Modul-Name in [4]!

Modul-Numero

500033 — kollidiert NICHT mit BankImport (500021). Permission-IDs sind 500033 + n.

CSS-Variablen (Dolibarr awl-dark Theme)

Verwendete: --colorbacktitle1, --colortext, --colorbackbody, --colorboxbordertitle1, --colortextlink, --colorbackhmenu1, --colortextbackhmenu, --colorbackvmenu1, --colorbacklinepair1, --colorbacklinebreak, --colorbacklinepairchecked, --colortexttitlenotab, --colortexttitlenotab2, --inputbackgroundcolor, --inputbordercolor, --btncolorborderhover

Korrektur 2026-08-21: Hier stand, --inputbackgroundcolor und --inputtextcolor gebe es im awl-dark nicht. Das stimmt für --inputbackgroundcolor nicht — die Variable ist gesetzt (rgb(20,22,24)), ebenso --inputbordercolor (rgb(54,57,62)). Wegen der falschen Notiz waren die Eingabefelder im Editor mit --colorbackbody gestylt und sahen anders aus als Dolibarrs eigene Felder. Vollständige, per DevTools geprüfte Liste: KB #512.

--colorbackhmenu1 (rgb(39,44,49)) taugt nicht für Aktiv-Zustände — praktisch derselbe Ton wie der Toolbar-Hintergrund. Dafür --colorbacklinepairchecked (rgb(30,87,116)) nehmen.

JS-Libraries (lokal in js/lib/)

  • pdf.min.js (PDF.js 3.11)
  • pdf.worker.min.js — Pfad NIE hartkodieren, sonst fehlt /custom und PDF.js fällt auf den „fake worker" zurück (rendert dann im Hauptthread). Kommt über cfg.urls.pdf_worker aus dol_buildpath().
  • fabric.min.js (Fabric.js 5.3)
  • Sortable.min.js (SortableJS 1.15) — mit group: {put: false}, sonst kollidiert das Sortieren mit dem eigenen Ablegen von Anhang-Kacheln
  • qrcode.min.js (QR-Code für den Mobile-Upload)

LocalStorage Settings

Key Inhalt
bericht.editor.settings.v1 color, stroke, fontFamily, fontSize, bold, italic, zoom
bericht.attachments.view.v1 Kachel- oder Listenansicht der Anhänge
bericht.attachments.sort.v1 Sortierung (taken / taken_desc / name / size_desc)
bericht.attachments.filter.v1 Filter (all / image / pdf / unused)
bericht.attachments.tilesize.v1 Kachelgröße (s / m / l)
bericht.attachments.width.v1 Breite der Anhänge-Spalte in px
bericht.attachments.collapsed.v1 Spalte eingeklappt (1/0)
bericht.editor.margins.v1 Seitenränder im Arbeitstisch anzeigen (1/0)

Forgejo

  • Repo: data/bericht (NICHT data-it/bericht — Token hat keine org-write rechte)
  • Workflow: [deploy]-Tag triggert rsync nach /mnt/appdata/firma/dolibarr-202509/modules/bericht
  • Lokaler Symlink: /var/www/dolibarr/custom/bericht/mnt/17 - Entwicklungen/30 - Scripts/php/Dolibarr - Module/Bericht/repo
  • Lokales Apache läuft als User data (NICHT http)

Phase 1 — Bericht-Modul-Erweiterungen (abgeschlossen mit 1.5.0)

Erledigt vor Phase 1

  • Modul-Scaffold (modBericht, SQL, Lang, Rechte korrekt nach Stundenzettel-Format)
  • Editor mit PDF.js + Fabric.js
  • Toolbar 2-zeilig, einheitliche Höhe 30px
  • Schriftart/Größe/Bold/Italic mit localStorage-Persistenz
  • Zoom (-/+/Reset), Seitenrotation, Pfeil mit Spitze (drag, drehbar)
  • Seiten-Thumbnails als echte Vorschau (PDF.js + Image), Hell/Dunkel-Toggle
  • ResizeObserver für Console-Open-Resize
  • ODT-Template-Verwaltung im Admin
  • Generate-PDF mit FPDI + Annotationen einbrennen
  • Mobile-Upload-Idee dokumentiert (Phase 2)

Phase 1 Features

  • 1.6 Verknüpfte Sicht Auftrag→Rechnung
  • 1.1 Live-PDF-Vorschau
  • 1.2 Anhänge löschen
  • 1.3 Seitengröße A4/A3/Letter + Hoch/Quer — global pro Bericht (nicht pro Seite override)
  • 1.4 Mehrere Bilder pro Seite 1.5.0 — Raster im PDF und im Editor; Plätze direkt auf der Seite anklickbar, einzeln tauschbar
  • 1.5 Bildgröße pro Seite — image_scale/image_align mit Auswahl in der Werkzeugleiste
  • 1.7 Kunden-Tab bericht_thirdparty.php, Konstante BERICHT_TAB_ON_THIRDPARTY

Seitengeometrie — der wichtigste Begriff im Modul (seit 1.5.0)

bericht_page_geometry($format, $orientation, $has_note) in lib/bericht.lib.php ist die einzige Quelle für Seitenmaße und Ränder. Editor und PDF-Erzeugung rechnen damit:

  • Ränder 10 mm, Kopfbereich 30 mm (dort druckt TCPDF Logo/Titel, Trennlinie bei y=27), Fußbereich 16 mm, Notizzone 25 % der Seitenhöhe (nur wenn die Seite eine Notiz hat)
  • Der Editor-Canvas ist der Inhaltsbereich, nicht das Blatt. Das PDF setzt das gespeicherte Composite-PNG 1:1 dorthin.
  • Nicht erneut einpassen — genau das war vor 1.5.0 der Fehler (Inhalt wurde zweimal verkleinert, breiter Rand, Vorschau stimmte nicht).
  • Composites von vor 1.5.0 haben Blatt-Proportion; sie werden an der Proportion erkannt und proportional eingepasst statt verzerrt. Diese Weiche nicht entfernen, solange Altberichte neu erzeugt werden könnten.
  • Raster-Plätze über BerichtPage::slotRectsInBox() (PHP) bzw. slotRectsPercent() (JS) — beide teilen denselben Inhaltsbereich auf und müssen zusammenpassen.

Weitere Fallen (2026-08-21 verifiziert)

  • dol_dir_list() braucht $mode = 1 für size/date und $nohook = 1 — sonst läuft der getDirList-Hook fremder Module mit, der global $object auswertet (im AJAX-Kontext nicht da). Siehe KB #1060.
  • document.php braucht &attachment=0, sonst wird das PDF heruntergeladen statt angezeigt.
  • Prod-Container hat kein exif und kein Imagick — EXIF liest das Modul selbst (bericht_jpeg_taken_at()), PDF-Vorschauen laufen über PDF.js im Browser.
  • Seitenwechsel sind serialisiert (Kette + Token in loadPage()). Beim schnellen Durchklicken überlappten sich sonst zwei Ladevorgänge und die Notiz landete auf der falschen Seite.
  • Fabric-Ereignisse (object:added …) feuern auch beim Aufbauen einer Seite — deshalb buildingPage-Flag, sonst meldet das Autosave sofort „nicht gespeichert".

Phase 3 — PWA MVP (2026-04-17)

  • Repo: baustelle-pwa, lokal unter /mnt/17 - Entwicklungen/30 - Scripts/javascript/baustelle-pwa/repo
  • Hosting: awl.data-it-solution.de/baustelle/ (Apache-Alias)
  • Stack: Vanilla JS (app.js, ~160 KB) + index.php + Service Worker + idb-keyval — KEIN SvelteKit (frühere Doku war falsch)
  • Features: Login → Auftragsliste → Order-Detail → Foto/Sprachnotiz/Materialliste → PDF-Viewing
  • PDF-Viewer implementiert: PDF.js Canvas-Rendering, Datei-Typ-Unterscheidung (Bilder/Audio/PDFs/Dokumente)
  • Weitere Dokumente: Order-Dateien aus commande/<ref>/ als Inline-Viewer (PDF.js) oder Download

Phase 4 Block 1 (2026-04-09 bis 2026-04-17)

PWA-API-Layer + Usability-Features:

  • 2.3 API-Layer unter bericht/api/ mit JWT-Auth (Login, Orders, Photos, Reports)
  • 2.4 REST-Endpoints: /auth.php, /orders, /orders/{id}, /orders/{id}/photos, /photo.php (Whitelist)
  • 4.j PDF-Vorschau-Modal + Inline-Viewing (PDF.js Canvas)
  • 4.g Seite löschen in PWA (DELETE /api/pages.php)
  • 4.h Notiz pro Seite (POST /api/pages.php {note})
  • 4.b Touch-Unterschrift (POST /api/pages.php?action=signature)
  • Datei-Typ-Unterscheidung: Bilder (Modal), Audio (Player), PDFs (Canvas), Dokumente (Download)

Auth-Hinweis (Stand 1.5.1): Der Block unten stammt aus der JWT-Zeit. Seit der SSO-Migration laeuft alles ueber das HttpOnly-Cookie awl_sso (Modul awlauth) — kein Bearer-Token, kein ?jwt=-Query-Parameter mehr. api/_jwt.php existiert nicht mehr. Endpoints mit awlauth_require brauchen zusaetzlich den Header X-Requested-With (CSRF); reine GET-Reads (photo.php, pdf.php) nutzen awlauth_verify ohne diesen.

API-Endpoints (neue):

  • POST /api/auth.php — Login (username, password) -> setzt awl_sso-Cookie
  • GET /api/orders.php — Aufträge des Users (filter: q, open)
  • GET /api/orders.php?id= — Order-Detail
  • GET /api/orders.php?id=&action=photos — Order-Dateien (Bilder/Audio/PDFs/Dokumente)
  • POST /api/orders.php?action=create — Neuer Order (socid, title, ref_client, date)
  • GET /api/photo.php?relpath= — Datei-Serving (Whitelist: facture|commande|propal|bericht)
  • GET /api/photo.php?relpath=&size=thumb&w= — Thumbnail (1.5.1, GD + Cache + ETag)
  • POST /api/pages.php?action=signature&bericht_id= — Touch-Unterschrift
  • DELETE /api/pages.php?id= — Seite löschen
  • POST /api/pages.php?id= — Seite-Notiz / Titel / Rotation / Layout
  • GET/POST /api/note.php?order_id= — Textnotizen zum Auftrag (1.5.1)

PWA neue Komponenten (app.js):

  • openFileViewer() — Generischer Dateibetrachter (Bilder/PDFs/Audio/Download)
  • openPdfViewer() — PDF.js Canvas mit Zoom/Seitennavigation/Download
  • docIconFor(), formatFileSize(), formatShortDate() — Hilfsfunktionen
  • Unterschrift-Modal, Notiz-Modal, Seiten-Verwaltung

Service Worker v5: Offline-Queue, Sync, Share Target API.

Phase 1.8 — Lieferschein-Bestätigung (2026-05-27)

Vollständiger Workflow für Kunden-Unterschrift auf dem Handy. Source-Repo: data/baustelle-pwa + data/bericht.

Backend (Bericht-Modul)

  • element_type='shipment' mit fk_element = llx_expedition.rowid
  • Reiter "Bericht" auf Expedition-Card (Konstante BERICHT_TAB_ON_SHIPMENT)
  • Hook-Klasse class/actions_bericht.class.php:
    • beforeODTSave-Hook ersetzt {signature} via $odfHandler->setImage('signature', $path, $ratio) durch eingebettetes PNG-Frame
    • Setzt zusätzlich {signer_name}, {signed_at}, {gps} per setVars
    • Hook MUSS in llx_const als MAIN_MODULE_BERICHT_HOOKS = ["odtgeneration"] registriert sein — wird beim Modul-Activate gesetzt
  • lib/bericht.lib.php:
    • bericht_fetch_shipment_with_order() — Expedition + verknüpfter Auftrag via llx_element_element
    • bericht_get_shipment_pdf($db, $shipment, $include_signed=false) — Standard-Lieferschein-PDF, filtert -signed.pdf raus per Default
    • bericht_get_signature_box($db, $template) — pro-Template-Geometrie aus DB oder Default-JSON
    • bericht_stamp_signature_on_pdf(...) — FPDI-basierter Stempel (Fallback für PDF-Module ohne ODT)
  • API: api/shipments.php
    • GET ?order_id=<id> → Liste
    • GET ?id=<id> → Detail
    • GET ?id=<id>&action=pdf&variant=auto|signed|unsigned → PDF-Stream
    • POST ?id=<id>&action=confirm → Signatur stempeln, signed.pdf erzeugen, Expedition signed_status=1 + ggf. validieren+schließen
  • Backup-Roundtrip im confirm: Original kopieren → generateDocument() mit Hook → Ergebnis als <ref>-signed.pdf kopieren → Original aus Backup wiederherstellen (generateDocument überschreibt sonst das Original-PDF)

Admin

  • admin/setup.php: Toggle Tab on Shipment, Slider BERICHT_SIGNATURE_IMAGE_RATIO (Default 0.35)
  • admin/signature_box_editor.php: visueller PDF-Editor für mm-Box-Geometrie (PDF.js + Fabric.js). Nur relevant für PDF-Module-Fallback; bei ODT-Templates wird stattdessen {signature}-Platzhalter empfohlen.
  • admin/signature_box_preview.php: Beispiel-PDF-Renderer
  • ajax/save_signature_box.php: UPSERT in llx_bericht_signature_box

PWA-Frontend (Baustelle)

  • Routes #/orders/:id/shipments + #/shipments/:id in app.js
  • Fullscreen-Landscape Modal openShipmentSignatureModal():
    • requestFullscreen() + screen.orientation.lock('landscape') (best-effort, iOS ignoriert lock)
    • HiDPI-Canvas (devicePixelRatio), transparent (kein fillRect)
    • trimCanvasToInk() schneidet auf bemalte Fläche (Alpha > 16)
    • Name vorausgefüllt mit Kundenname
    • GPS-Abfrage timeout 3s, graceful bei Verweigerung
  • Direkt nach Bestätigung: PDF-Viewer mit ?variant=signed

Wichtige Fallen

  • EXPEDITION_ADDON_PDF_ODT_PATH enthält wörtlich den String "DOL_DATA_ROOT/..." → muss mit preg_replace('/DOL_DATA_ROOT/', DOL_DATA_ROOT, $d) aufgelöst werden
  • llx_element_element-Richtung: commande = source, shipping = target (NICHT umgekehrt)
  • JWT für <img>/<object>-Tags per ?jwt= — mit der SSO-Migration hinfällig: das awl_sso-Cookie geht same-origin automatisch mit, auch bei <img>/<audio>. Deshalb nutzen photo.php und pdf.php awlauth_verify (ohne CSRF-Header-Pflicht) — sonst könnten sie nicht per window.location oder als src geladen werden.
  • ODT-Templates für Expedition liegen unter DOL_DATA_ROOT/doctemplates/shipments/*.odt

API-Fallen (2026-08-22 verifiziert)

  • photo.php?size=small ist KEIN Thumbnail-Generator. Es liefert nur eine Datei aus, die Dolibarr selbst unter thumbs/<name>_small.<ext> erzeugt hat. Fuer Uploads ueber orders.php?action=upload_photo gibt es die nicht — dort laeuft kein vignette(). Ergebnis war: die PWA lud fuer jede Kachel das Originalfoto. Der richtige Weg ist seit 1.5.1 size=thumb&w=<px> (nutzt bericht_attachment_thumb()).
  • reports.php muss composite_path mitliefern, sonst zeigen Clients das Rohbild statt der bearbeiteten Seite — und bei Raster-Layouts nur eines von bis zu sechs Bildern (title_only hat gar kein source_path).
  • Ein <img loading="lazy">, das noch nicht im DOM haengt, laedt der Browser nicht. Es hat kein Layout und gilt damit nie als sichtbar. Erst einhaengen, dann src setzen — sonst bleibt die Kachel ewig beim Platzhalter (genau das ist beim Umbau der PWA passiert).
  • Dateiname-Parameter immer gegen ein Muster pruefen (note.php: ^notiz_[\w.\-]*\.txt$), nicht nur basename(). Sonst laesst sich ueber ../ jede Datei unter DOL_DATA_ROOT ueberschreiben.

Phase 5 — PWA Erweiterungen (geplant)

  • Sprachnotizen-Transkription via Whisper (POST /api/transcribe.php)
  • PIN-Schutz / WebAuthn
  • Push-Notifications bei neuen Aufträgen
  • Offline-Queue Optimierung (Sync bei Netzwechsel)
  • QR-Code Scanner (Order-Lookup)
  • Batch-Unterschriften (mehrere Orders auf einmal)

Phase 6 — Optional Später

Stamps, Vorher/Nachher, Versionierung, Mess-Werkzeug, Bericht-Vorlagen, Offline-Map, Geofencing


Wichtige Lessons Learned (für andere Dolibarr-Module)

  1. Numero muss kollisionsfrei sein — sonst werden Permissions stillschweigend verworfen
  2. Permission-Array-Format: [4]=perms (action), [5]=subperms (leer) — NICHT [4]=Modulname
  3. CSS-Variablen über Theme nutzen, nicht hardcoded Hex
  4. JS-Libs lokal in js/lib/ (Eddys Regel: kein CDN)
  5. Fabric.js wickelt Canvas in .canvas-container — den positionieren, nicht das innere Canvas
  6. PDF.js Buffer wird konsumiert — beim Re-Render arrayBuffer.slice(0) nutzen
  7. Lokales Dolibarr läuft als User data, mit Symlinks aus /var/www/dolibarr/custom/<modul> zu /mnt/17 - Entwicklungen/...
  8. Niemals direkt in /var/www/dolibarr oder /mnt/appdata editieren — alles über Git
  9. Niemals schreibend in dolibarr_test MySQL — nur lesend für Debugging
  10. Computed Extrafields mit $object->ref brauchen Null-Check, sonst PHP-Output beim eval → "headers already sent"