README: Anhaenge-Spalte, Arbeitstisch, Autosave, Tastenkuerzel, Bildtausch, Sammelaktionen und Drag & Drop beschrieben; Architektur-Baum vervollstaendigt (15 fehlende ajax-Endpoints, imageviewer, Root-Seiten, SQL-Dateien); GD als Voraussetzung ergaenzt; llx_bericht_upload_token ist nicht mehr "geplant". CLAUDE.md: Abschnitt zur Seitengeometrie als zentralem Begriff, LocalStorage-Keys, Phase 1 abgeschlossen. Korrigiert die falsche Notiz, --inputbackgroundcolor gebe es im awl-dark nicht — die Variable existiert (KB #512), deshalb waren die Eingabefelder im Editor anders gestylt als Dolibarrs eigene. Fix: dol_dir_list() zusaetzlich mit $nohook = 1 (KB #1060) — sonst laeuft der getDirList-Hook fremder Module mit, der global $object auswertet; bericht_collect_attachments() laeuft seit 1.5.0 auch im AJAX-Kontext (ajax/fragments.php), wo es das nicht gibt. ROADMAP.md entfernt: alle Punkte abgearbeitet, Inhalt steht im ChangeLog. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
15 KiB
Bericht-Modul — Projekt-Status & Architektur
Stand 2026-08-21 (Version 1.5.0)
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->refdirekt - Rechnung (facture) — Berichte mit
element_type='invoice', Auftragsnummer ausarray_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 überllx_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
/customund PDF.js fällt auf den „fake worker" zurück (rendert dann im Hauptthread). Kommt übercfg.urls.pdf_workerausdol_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(NICHTdata-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(NICHThttp)
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, KonstanteBERICHT_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 = 1fürsize/dateund$nohook = 1— sonst läuft dergetDirList-Hook fremder Module mit, derglobal $objectauswertet (im AJAX-Kontext nicht da). Siehe KB #1060.document.phpbraucht&attachment=0, sonst wird das PDF heruntergeladen statt angezeigt.- Prod-Container hat kein
exifund 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 — deshalbbuildingPage-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)
API-Endpoints (neue):
- POST /api/auth.php — JWT-Login (username, password)
- 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 (JWT via Header oder Query-Param, Whitelist: facture|commande|propal|bericht)
- 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 / Rotation
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'mitfk_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}persetVars - Hook MUSS in
llx_constalsMAIN_MODULE_BERICHT_HOOKS = ["odtgeneration"]registriert sein — wird beim Modul-Activate gesetzt
lib/bericht.lib.php:bericht_fetch_shipment_with_order()— Expedition + verknüpfter Auftrag viallx_element_elementbericht_get_shipment_pdf($db, $shipment, $include_signed=false)— Standard-Lieferschein-PDF, filtert-signed.pdfraus per Defaultbericht_get_signature_box($db, $template)— pro-Template-Geometrie aus DB oder Default-JSONbericht_stamp_signature_on_pdf(...)— FPDI-basierter Stempel (Fallback für PDF-Module ohne ODT)
- API:
api/shipments.phpGET ?order_id=<id>→ ListeGET ?id=<id>→ DetailGET ?id=<id>&action=pdf&variant=auto|signed|unsigned→ PDF-StreamPOST ?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.pdfkopieren → Original aus Backup wiederherstellen (generateDocumentüberschreibt sonst das Original-PDF)
Admin
admin/setup.php: Toggle Tab on Shipment, SliderBERICHT_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-Rendererajax/save_signature_box.php: UPSERT inllx_bericht_signature_box
PWA-Frontend (Baustelle)
- Routes
#/orders/:id/shipments+#/shipments/:idinapp.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_PATHenthält wörtlich den String"DOL_DATA_ROOT/..."→ muss mitpreg_replace('/DOL_DATA_ROOT/', DOL_DATA_ROOT, $d)aufgelöst werdenllx_element_element-Richtung: commande = source, shipping = target (NICHT umgekehrt)- JWT für
<img>/<object>-Tags: Query-Param?jwt=<token>als Fallback inapi/_jwt.php, da diese Tags keine Authorization-Header senden - ODT-Templates für Expedition liegen unter
DOL_DATA_ROOT/doctemplates/shipments/*.odt
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)
- Numero muss kollisionsfrei sein — sonst werden Permissions stillschweigend verworfen
- Permission-Array-Format: [4]=perms (action), [5]=subperms (leer) — NICHT [4]=Modulname
- CSS-Variablen über Theme nutzen, nicht hardcoded Hex
- JS-Libs lokal in
js/lib/(Eddys Regel: kein CDN) - Fabric.js wickelt Canvas in
.canvas-container— den positionieren, nicht das innere Canvas - PDF.js Buffer wird konsumiert — beim Re-Render
arrayBuffer.slice(0)nutzen - Lokales Dolibarr läuft als User
data, mit Symlinks aus/var/www/dolibarr/custom/<modul>zu/mnt/17 - Entwicklungen/... - Niemals direkt in
/var/www/dolibarroder/mnt/appdataeditieren — alles über Git - Niemals schreibend in
dolibarr_testMySQL — nur lesend für Debugging - Computed Extrafields mit
$object->refbrauchen Null-Check, sonst PHP-Output beim eval → "headers already sent"