# 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 - [x] **1.6 Verknüpfte Sicht Auftrag→Rechnung** ✅ - [x] **1.1 Live-PDF-Vorschau** ✅ - [x] **1.2 Anhänge löschen** ✅ - [x] **1.3 Seitengröße A4/A3/Letter + Hoch/Quer** ✅ — global pro Bericht (nicht pro Seite override) - [x] **1.4 Mehrere Bilder pro Seite** ✅ 1.5.0 — Raster im PDF und im Editor; Plätze direkt auf der Seite anklickbar, einzeln tauschbar - [x] **1.5 Bildgröße pro Seite** ✅ — image_scale/image_align mit Auswahl in der Werkzeugleiste - [x] **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//` 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=` → Liste - `GET ?id=` → Detail - `GET ?id=&action=pdf&variant=auto|signed|unsigned` → PDF-Stream - `POST ?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 `-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 ``/``-Tags per `?jwt=`~~ — mit der SSO-Migration hinfällig: das `awl_sso`-Cookie geht same-origin automatisch mit, auch bei ``/`