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>
This commit is contained in:
Eduard Wisch 2026-05-28 07:58:04 +02:00
parent 52489ad21d
commit 2ec6686118
2 changed files with 116 additions and 13 deletions

View file

@ -1,8 +1,8 @@
# Bericht-Modul — Projekt-Status & Architektur # Bericht-Modul — Projekt-Status & Architektur
## Stand 2026-04-17 ## Stand 2026-05-27
Dolibarr-Custom-Modul für Arbeitsberichte mit Browser-PDF-Editor + PWA-API-Layer. Dolibarr-Custom-Modul für Arbeitsberichte mit Browser-PDF-Editor + PWA-API-Layer + Lieferschein-Unterschrift.
## Architektur (final) ## Architektur (final)
@ -10,6 +10,7 @@ Dolibarr-Custom-Modul für Arbeitsberichte mit Browser-PDF-Editor + PWA-API-Laye
- **Auftrag (commande)** — primärer Erstellungsort, Berichte mit `element_type='order'`, Auftragsnummer = `commande->ref` direkt - **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']` - **Rechnung (facture)** — Berichte mit `element_type='invoice'`, Auftragsnummer aus `array_options['options_auftragsnummer']`
- **Angebot (propal)** — möglich, gleiche Logik - **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 - **Kundenkarte (thirdparty)** — read-only Übersicht (Phase 1.7), zeigt alle Berichte des Kunden über Joins, **kein** Anlegen, **kein** Speicher-Ort
### Verknüpfung Auftrag → Rechnung ### Verknüpfung Auftrag → Rechnung
@ -19,6 +20,7 @@ Berichte gehören 1:1 zu einem Parent (`element_type` + `fk_element`). Auf einer
- `llx_bericht` — Bericht (rowid, ref, titel, element_type, fk_element, auftragsnummer, template_odt, status, final_pdf_path, format, orientation, ...) - `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_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_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 ### Permissions
- `bericht/read` (Standard für alle) - `bericht/read` (Standard für alle)
@ -124,6 +126,51 @@ PWA neue Komponenten (app.js):
Service Worker v5: Offline-Queue, Sync, Share Target API. 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: Query-Param `?jwt=<token>` als Fallback in `api/_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) ## Phase 5 — PWA Erweiterungen (geplant)
- Sprachnotizen-Transkription via Whisper (POST /api/transcribe.php) - Sprachnotizen-Transkription via Whisper (POST /api/transcribe.php)
- PIN-Schutz / WebAuthn - PIN-Schutz / WebAuthn

View file

@ -5,7 +5,7 @@ Bilder und PDFs lassen sich im Browser annotieren (Pfeile, Kreise, Rechtecke, Te
## Funktionen ## Funktionen
- **Reiter „Bericht"** auf Rechnungen, Aufträgen und Angeboten (jeweils per Konstante deaktivierbar) - **Reiter „Bericht"** auf Rechnungen, Aufträgen, Angeboten und Lieferungen (jeweils per Konstante deaktivierbar)
- **Anhänge-Browser** zeigt alle Dateien des aktuellen Dokuments **und** der direkt verknüpften Objekte (z. B. der Auftrag zur Rechnung) - **Anhänge-Browser** zeigt alle Dateien des aktuellen Dokuments **und** der direkt verknüpften Objekte (z. B. der Auftrag zur Rechnung)
- **Auswahl per Checkbox** — markierte Dateien werden als Seiten in den Bericht übernommen - **Auswahl per Checkbox** — markierte Dateien werden als Seiten in den Bericht übernommen
- **Browser-Editor** mit PDF.js + Fabric.js: Pfeile, Kreise, Rechtecke, Freihand, Text, Farbe, Strichstärke, Undo/Redo - **Browser-Editor** mit PDF.js + Fabric.js: Pfeile, Kreise, Rechtecke, Freihand, Text, Farbe, Strichstärke, Undo/Redo
@ -16,7 +16,8 @@ Bilder und PDFs lassen sich im Browser annotieren (Pfeile, Kreise, Rechtecke, Te
- **Auftragsnummer** wird automatisch aus dem Extrafield `options_auftragsnummer` der Rechnung gezogen - **Auftragsnummer** wird automatisch aus dem Extrafield `options_auftragsnummer` der Rechnung gezogen
- **Mehrere Berichte pro Dokument** möglich - **Mehrere Berichte pro Dokument** möglich
- Berichte als **Entwurf** speichern (jederzeit wieder editierbar) oder **finalisieren** (PDF erzeugen) - Berichte als **Entwurf** speichern (jederzeit wieder editierbar) oder **finalisieren** (PDF erzeugen)
- **PWA-API** für Mobile-Nutzung: Aufträge, Fotos, Sprachnotizen, Materiallisten, Signaturen - **Lieferschein-Bestätigung mit Kunden-Unterschrift**: Vollbild-Querformat-Signatur in der PWA, Unterschrift wird via ODT-Hook (Platzhalter `{signature}`) ins Lieferschein-PDF gestempelt, Expedition wird automatisch validiert und geschlossen
- **PWA-API** für Mobile-Nutzung: Aufträge, Fotos, Sprachnotizen, Materiallisten, Lieferungen, Signaturen
- **PDF-Viewer in PWA** mit PDF.js Canvas-Rendering (Zoom, Seitennummerierung, Download) - **PDF-Viewer in PWA** mit PDF.js Canvas-Rendering (Zoom, Seitennummerierung, Download)
## Voraussetzungen ## Voraussetzungen
@ -60,6 +61,10 @@ Bilder und PDFs lassen sich im Browser annotieren (Pfeile, Kreise, Rechtecke, Te
| `{hinweis}` | extrafield `options_hinweis` | | `{hinweis}` | extrafield `options_hinweis` |
| `{bericht_titel}` | Titel des Berichts | | `{bericht_titel}` | Titel des Berichts |
| `{ersteller}` | Login-Name des erstellenden Users | | `{ersteller}` | Login-Name des erstellenden Users |
| `{signature}` | Kunden-Unterschrift als Bild (nur Lieferschein-Workflow, ersetzt Text-Platzhalter durch eingebettetes PNG; Größe via `BERICHT_SIGNATURE_IMAGE_RATIO`) |
| `{signer_name}` | Name des unterschreibenden Kunden |
| `{signed_at}` | Zeitstempel der Unterschrift |
| `{gps}` | GPS-Koordinaten zum Zeitpunkt der Unterschrift (falls erlaubt) |
## PWA-Integration (Baustelle Mobile App) ## PWA-Integration (Baustelle Mobile App)
@ -103,20 +108,71 @@ POST /custom/bericht/api/pages.php?action=signature&bericht_id=<id>
Body: FormData mit file=<PNG-Blob>, signer_name, gps_lat, gps_lon Body: FormData mit file=<PNG-Blob>, signer_name, gps_lat, gps_lon
``` ```
### Lieferungen + Unterschrift (PWA)
```
GET /custom/bericht/api/shipments.php?order_id=<id>
Liste aller Expeditionen zum Auftrag (id, ref, date_delivery, status, signed_status, has_bericht)
GET /custom/bericht/api/shipments.php?id=<id>
Detail einer Lieferung inkl. bericht_id
GET /custom/bericht/api/shipments.php?id=<id>&action=pdf[&variant=auto|signed|unsigned]
Liefert Lieferschein-PDF (Default auto: signed wenn vorhanden, sonst Original)
POST /custom/bericht/api/shipments.php?id=<id>&action=confirm
FormData mit signature_png, signer_name, gps_lat, gps_lon, signed_at
Stempelt Unterschrift via ODT-Hook ({signature}-Platzhalter), legt <ref>-signed.pdf
in documents/expedition/<ref>/, setzt signed_status=1, validiert+schließt Expedition
wenn noch Draft. Response: { ok: true, pdf_url, bericht_id }
```
## Konfigurations-Konstanten
Per `admin/setup.php` oder `llx_const`:
| Konstante | Default | Zweck |
|---|---|---|
| `BERICHT_TAB_ON_INVOICE` | 1 | Reiter "Bericht" auf Rechnungen anzeigen |
| `BERICHT_TAB_ON_ORDER` | 1 | Reiter "Bericht" auf Aufträgen anzeigen |
| `BERICHT_TAB_ON_PROPAL` | 1 | Reiter "Bericht" auf Angeboten anzeigen |
| `BERICHT_TAB_ON_SHIPMENT` | 1 | Reiter "Bericht" auf Lieferungen anzeigen |
| `BERICHT_TAB_ON_THIRDPARTY` | 0 | Read-only Bericht-Tab auf Kundenkarte |
| `BERICHT_SIGNATURE_IMAGE_RATIO` | 0.35 | Größen-Faktor für `{signature}`-Platzhalter im ODT (höher = größer) |
| `BERICHT_SIGNATURE_BOX_DEFAULT` | JSON | Default-Geometrie für FPDI-Stempel-Fallback (`{"page":"last","x_mm":120,"y_mm":230,"w_mm":70,"h_mm":35,"label":"Unterschrift Kunde"}`) |
| `BERICHT_BURN_ANNOTATIONS` | 0 | Annotationen ins PDF einbrennen statt als PDF-Annotation einbetten |
| `BERICHT_LIBREOFFICE_BIN` | `soffice` | Pfad zur LibreOffice-Binary (für ODT→PDF) |
## Datenbank
| Tabelle | Zweck |
|---|---|
| `llx_bericht` | Bericht-Header (element_type ∈ {invoice, order, propal, shipment}, fk_element, status, …) |
| `llx_bericht_page` | Einzelne Seiten mit Fabric-JSON-Annotationen, Layout, Notiz |
| `llx_bericht_page_image` | Multi-Image-Layout pro Seite (Phase 1.4) |
| `llx_bericht_upload_token` | Mobile-Upload-Tokens (geplant) |
| `llx_bericht_signature_box` | Pro Lieferschein-Template gespeicherte Signatur-Box-Geometrie (mm) |
## Architektur ## Architektur
``` ```
bericht/ bericht/
├── core/modules/modBericht.class.php Modul-Descriptor, Tabs, Extrafields-Init ├── core/modules/modBericht.class.php Modul-Descriptor, Tabs, Extrafields-Init, Konstanten
├── class/bericht.class.php Bericht + BerichtPage CRUD ├── class/
├── lib/bericht.lib.php Helper (Anhänge sammeln, Auftragsnr., Templates) │ ├── bericht.class.php Bericht + BerichtPage CRUD
│ └── actions_bericht.class.php Hook: beforeODTSave setzt {signature} + Meta-Variablen
├── lib/bericht.lib.php Helper (Anhänge, Auftragsnr., Templates, Shipment-PDF,
│ Signature-Box, FPDI-Stempel-Fallback)
├── bericht_card.php Editor-Seite (Tab-Inhalt) ├── bericht_card.php Editor-Seite (Tab-Inhalt)
├── admin/setup.php Admin: ODT-Templates, Konstanten ├── admin/
│ ├── setup.php Admin: ODT-Templates, Konstanten, Signatur-Größe
│ ├── signature_box_editor.php Visueller PDF-Editor für Signatur-Box-Position
│ └── signature_box_preview.php Beispiel-PDF-Renderer für den Editor
├── ajax/ Legacy Endpoints (Token-geschützt) ├── ajax/ Legacy Endpoints (Token-geschützt)
│ ├── _inc.php Gemeinsamer Header │ ├── _inc.php Gemeinsamer Header
│ ├── add_attachment.php Anhang als Seite hinzufügen │ ├── add_attachment.php Anhang als Seite hinzufügen
│ ├── upload_extra.php Direkter Upload │ ├── upload_extra.php Direkter Upload
│ ├── save_annotations.php Fabric-JSON speichern │ ├── save_annotations.php Fabric-JSON speichern
│ ├── save_signature_box.php UPSERT der Signatur-Box pro Template
│ ├── page_meta.php Annotationen + Notiz laden │ ├── page_meta.php Annotationen + Notiz laden
│ ├── page_image.php Seitenbild/PDF ausliefern │ ├── page_image.php Seitenbild/PDF ausliefern
│ ├── delete_page.php │ ├── delete_page.php
@ -124,9 +180,10 @@ bericht/
│ └── generate_pdf.php Finalisierung: TCPDF + FPDI + ODT-Deckblatt │ └── generate_pdf.php Finalisierung: TCPDF + FPDI + ODT-Deckblatt
├── api/ REST-API (JWT-Auth) ├── api/ REST-API (JWT-Auth)
│ ├── _inc.php JWT-Authentifizierung + Dolibarr-Init │ ├── _inc.php JWT-Authentifizierung + Dolibarr-Init
│ ├── _jwt.php JWT encoding/decoding │ ├── _jwt.php JWT encoding/decoding (mit ?jwt= Query-Fallback)
│ ├── auth.php Login-Endpoint │ ├── auth.php Login-Endpoint
│ ├── orders.php Order-Liste, Detail, Fotos, Create │ ├── orders.php Order-Liste, Detail, Fotos, Create
│ ├── shipments.php Lieferungen-Liste, PDF-Stream, Unterschrift-Confirm
│ ├── photo.php Datei-Serving mit Whitelist │ ├── photo.php Datei-Serving mit Whitelist
│ ├── pdf.php Finalized Bericht-PDF │ ├── pdf.php Finalized Bericht-PDF
│ ├── pages.php Seiten-Verwaltung (Note, Rotation, Signature) │ ├── pages.php Seiten-Verwaltung (Note, Rotation, Signature)
@ -140,10 +197,9 @@ bericht/
│ └── lib/ PDF.js, Fabric.js, SortableJS (lokal) │ └── lib/ PDF.js, Fabric.js, SortableJS (lokal)
├── css/bericht.css ├── css/bericht.css
├── sql/ ├── sql/
│ ├── llx_bericht.sql │ ├── llx_bericht.sql / .key.sql
│ ├── llx_bericht.key.sql │ ├── llx_bericht_page.sql / .key.sql
│ ├── llx_bericht_page.sql │ └── llx_bericht_signature_box.sql / .key.sql
│ └── llx_bericht_page.key.sql
└── langs/{de_DE,en_US}/bericht.lang └── langs/{de_DE,en_US}/bericht.lang
``` ```