bericht/README.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

290 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Bericht — Arbeitsberichte für Dolibarr
Erstellt aus den Anhängen einer Rechnung (oder eines Auftrags / Angebots) einen Arbeitsbericht als PDF.
Bilder und PDFs lassen sich im Browser annotieren (Pfeile, Kreise, Rechtecke, Text, Freihand) — der fertige Bericht wird unter *Verknüpfte Dokumente* der Rechnung abgelegt.
## Funktionen
- **Reiter „Bericht"** auf Rechnungen, Aufträgen, Angeboten und Lieferungen (jeweils per Konstante deaktivierbar)
- **Anhänge-Spalte als Kachelraster** mit Vorschaubildern — zeigt alle Dateien des aktuellen Dokuments **und** der direkt verknüpften Objekte (z. B. der Auftrag zur Rechnung). Bild-Miniaturen entstehen serverseitig (GD, gecacht), PDF-Anhänge zeigen Seite 1 (PDF.js). Klick öffnet die Großansicht — Bilder mit Zoom und Wischen, PDFs im PDF-Viewer des Browsers.
- **Sortieren und Filtern** der Anhänge nach Aufnahmezeit (EXIF), Name oder Größe · nur Bilder / nur PDFs / noch nicht verwendete. Spaltenbreite ziehbar, Spalte einklappbar, Kachelgröße in drei Stufen.
- **Auswahl per Kachel** — die Reihenfolge der Auswahl (① ② ③) bestimmt auch die Verteilung auf die Plätze im Raster-Layout. Bereits verwendete Dateien sind markiert.
- **Browser-Editor** mit PDF.js + Fabric.js: Pfeile, Kreise, Rechtecke, Freihand, Text, Farbe, Strichstärke, Undo/Redo — mit Tastenkürzeln (V/P/R/K/A/T, Entf, Strg+Z/Y/S, ↑/↓)
- **Arbeitstisch zeigt die echte Seite**: Kopfbereich (Logo/Titel), Seitenränder und Fußbereich sind sichtbar; die Arbeitsfläche entspricht genau dem Bereich, der gedruckt wird — das PDF übernimmt sie 1:1
- **Automatisches Speichern** kurz nach der letzten Änderung, beim Seitenwechsel und beim Wegschalten des Tabs; beim Verlassen mit offenen Änderungen warnt der Browser
- **Seiten-Verwaltung** per Drag&Drop (SortableJS): umordnen, löschen, drehen, neue Seiten hochladen. Mehrere Seiten markieren (Klick auf die Seitennummer, Umschalt für einen Bereich) und gesammelt löschen oder verdoppeln.
- **Bild einer Seite austauschen** ohne sie zu löschen — Reihenfolge, Notiz und Anmerkungen bleiben erhalten. Bei Raster-Layouts lässt sich jeder Platz einzeln tauschen, direkt auf der Seite anklickbar.
- **Ziehen und Ablegen**: Kachel auf die Seitenliste legt neue Seiten an, Kachel auf die Arbeitsfläche ersetzt das Bild der offenen Seite, Kachel auf einen Raster-Platz setzt genau diesen. Dateien vom Rechner auf die Anhänge-Spalte werden hochgeladen.
- **Mobiler Upload per QR-Code** — Handy scannt, lädt Fotos direkt in den offenen Bericht (zeitlich begrenzte Token)
- **Notizen pro Seite** — werden im finalen PDF unten auf der Seite gedruckt
- **Deckblatt aus ODT-Vorlage** mit Platzhaltern (`{auftragsnummer}`, `{kunde_name}`, `{datum}`, …)
- **ODT-Templates** im Admin-Bereich verwaltbar (mehrere Vorlagen, Standard wählbar)
- **Auftragsnummer** wird automatisch aus dem Extrafield `options_auftragsnummer` der Rechnung gezogen
- **Mehrere Berichte pro Dokument** möglich
- Berichte als **Entwurf** speichern (jederzeit wieder editierbar) oder **finalisieren** (PDF erzeugen)
- **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)
## Voraussetzungen
- Dolibarr ≥ 19.0
- PHP ≥ 7.4
- TCPDF (in Dolibarr enthalten)
- **FPDI** (für PDF-Anhänge in den Bericht zu mergen) — empfohlen, optional
- **LibreOffice headless** (für ODT→PDF Konvertierung der Deckblätter)
- **GD** (PHP-Extension) für die Vorschaubilder der Anhänge und die Raster-Vorschau
- Optional: `pdfinfo` oder `imagick` für PDF-Seitenanzahl-Erkennung
> Imagick und die PHP-Extension `exif` sind im Produktions-Container **nicht** installiert.
> PDF-Vorschauen laufen deshalb über PDF.js im Browser, EXIF-Daten liest das Modul mit einem
> eigenen Parser (`bericht_jpeg_taken_at()` / `bericht_jpeg_orientation()`).
## Installation
1. Modul-Verzeichnis nach `dolibarr/htdocs/custom/bericht/` (oder per Symlink aus dem Module-Mount-Pfad) kopieren
2. In Dolibarr unter **Konfiguration → Module/Anwendungen** das Modul **Bericht** aktivieren
3. Beim Aktivieren werden die SQL-Tabellen `llx_bericht` und `llx_bericht_page` angelegt
4. Vorhandene Extrafields auf `llx_facture_extrafields` (`auftragsnummer`, `angebotsnummer`, …) werden erkannt und nicht überschrieben — fehlende werden angelegt
5. Im Admin-Bereich (`/bericht/admin/setup.php`) die ODT-Templates hochladen und Standard-Template setzen
## Verwendung
1. Eine Rechnung öffnen (`/compta/facture/card.php?id=…`)
2. Reiter **Bericht** auswählen
3. **+ Neuer Bericht** klicken — die Auftragsnummer wird automatisch übernommen
4. Im Editor links die gewünschten Anhänge auswählen (Kachel-Checkbox; die Reihenfolge der Auswahl zählt) → **Auswahl in Bericht übernehmen**. Alternativ eine Kachel direkt in die Seitenliste ziehen.
5. Im mittleren Editor mit den Werkzeugen Pfeile, Texte etc. zeichnen — der graue Bereich oben und unten zeigt, wo Logo, Titel und Seitenzahl gedruckt werden. Änderungen werden automatisch gespeichert.
6. Seiten rechts per Drag&Drop sortieren, einzelne Seiten löschen oder über die Seitennummer mehrere markieren und gesammelt löschen/verdoppeln. Ein falsches Bild lässt sich über **🔄** auf der Seitenminiatur austauschen.
7. **Bericht finalisieren** — PDF wird erzeugt, Deckblatt aus der ODT-Vorlage gerendert und unter den verknüpften Dokumenten der Rechnung abgelegt
## ODT-Template Platzhalter
| Platzhalter | Inhalt |
|---|---|
| `{auftragsnummer}` | Aus extrafield `options_auftragsnummer` der Rechnung |
| `{angebotsnummer}` | Aus extrafield `options_angebotsnummer` |
| `{rechnungsnummer}` | `ref` der Rechnung |
| `{kunde_name}` | Name des Kunden (Société) |
| `{kunde_adresse}` | Adresse des Kunden, mehrzeilig |
| `{datum}` | Heutiges Datum |
| `{beschreibung}` | extrafield `options_beschreibung` |
| `{hinweis}` | extrafield `options_hinweis` |
| `{bericht_titel}` | Titel des Berichts |
| `{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)
Die **Baustelle-PWA** (`https://awl.data-it-solution.de/baustelle/`) nutzt die folgenden API-Endpoints des Bericht-Moduls:
### Authentifizierung
```
POST /custom/bericht/api/auth.php
Body: { login: string, password: string }
Response: { ok: true, user: {...} } + HttpOnly-Cookie awl_sso
```
Seit der SSO-Migration läuft die Anmeldung über das Modul **awlauth**: `auth.php` setzt das
HttpOnly-Cookie `awl_sso` (Single-Sign-on/-out über alle AWL-Apps), Folge-Requests
authentisieren sich same-origin über dieses Cookie. **Kein Bearer-Token und kein `?jwt=`
mehr** — auch `<img>`- und `<audio>`-Tags kommen ohne Query-Parameter aus.
Endpoints mit `awlauth_require` erwarten zusätzlich den Header `X-Requested-With:
XMLHttpRequest` (CSRF-Schutz); reine GET-Reads wie `photo.php` und `pdf.php` nutzen
`awlauth_verify` ohne diese Anforderung, damit sie auch per `window.location` gehen.
### Order-APIs
```
GET /custom/bericht/api/orders.php
GET /custom/bericht/api/orders.php?id=<id>
GET /custom/bericht/api/orders.php?id=<id>&action=photos
Je Datei: filename, size, mime, date (mtime), taken_at (EXIF-Aufnahmezeit, sonst
Dateidatum), relpath. Sortiert nach taken_at, neuste zuerst.
POST /custom/bericht/api/orders.php?action=create
Body: { socid, ref_client, title?, note_private?, date?, date_livraison?, validate? }
ref_client ("Ihr Zeichen") ist PFLICHT. validate=true gibt den Auftrag direkt frei —
OHNE Position (Commande::valid() verlangt keine, verifiziert gegen Dolibarr 22.0.2).
Die Leistungen kommen später aus dem Stundenzettel.
```
### Dateien
```
GET /custom/bericht/api/photo.php?relpath=<path>
Liefert Dateien aus DOL_DATA_ROOT (Whitelist: facture/, commande/, propal/, bericht/)
Auth über das awl_sso-Cookie (same-origin), auch für <img>-Tags.
?size=thumb&w=<px> Serverseitig erzeugtes Thumbnail (GD, gecacht unter
bericht/thumbs/, mit ETag aus der mtime → Folgeaufrufe 304).
w: 40800, Default 320. Das ist der Weg für Kachel-Ansichten.
Ist die Datei kein Bild, kommt das Original.
?size=small|mini Altes Verhalten: nutzt ein von Dolibarr vorgefertigtes
thumbs/<name>_small.<ext>. Für Dateien, die über die API
hochgeladen wurden, existiert so eines NICHT (kein vignette()) —
dort kam bis 1.5.1 immer das Original zurück.
?download=1 Attachment-Header
```
### PDF-Ansicht
```
GET /custom/bericht/api/pdf.php?id=<bericht_id>&jwt=<token>
Liefert finalisiertes Bericht-PDF als Blob
```
### Seiten-Verwaltung (PWA)
```
DELETE /custom/bericht/api/pages.php?id=<page_id>
POST /custom/bericht/api/pages.php?id=<page_id> Body: { note, title, rotation, layout }
POST /custom/bericht/api/pages.php?action=signature&bericht_id=<id>
Body: FormData mit file=<PNG-Blob>, signer_name, gps_lat, gps_lon
```
`reports.php?id=<id>` gibt je Seite `composite_path` und `title` mit aus. **Clients sollen
`composite_path` anzeigen, wenn es gesetzt ist** — das ist das im Editor gebaute Seitenbild
mit Anmerkungen. `source_path` ist nur die Quelldatei: bei Raster-Layouts eines von bis zu
sechs Bildern, bei `title_only` leer.
### Textnotizen zum Auftrag (PWA)
```
GET /custom/bericht/api/note.php?order_id=<id> Liste
GET /custom/bericht/api/note.php?order_id=<id>&file=<name> eine Notiz mit Text
POST /custom/bericht/api/note.php?order_id=<id> anlegen { subject, text }
POST /custom/bericht/api/note.php?order_id=<id>&file=<name> ändern { subject, text }
POST /custom/bericht/api/note.php?order_id=<id>&file=<name>&delete=1
```
Notizen liegen als `notiz_<betreff>_<datum>.txt` im Auftrags-Verzeichnis — dasselbe Muster
wie die Sprachnotiz (`voice.php`). Sie sind damit auch im Dolibarr-Auftrag und in der
Anhänge-Spalte des Editors zu sehen. Der Dateikopf (bis zur ersten Leerzeile) trägt
`Betreff:`, `Erfasst:` und `Geändert:`, darunter steht der freie Text. `file` wird gegen
`^notiz_[A-Za-z0-9_.\-]*\.txt$` geprüft — ohne das ließe sich über `../` jede Datei unter
`DOL_DATA_ROOT` überschreiben.
### 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` | Bilder der einzelnen Plätze bei Raster-Layouts (grid_2 … grid_6, before_after) |
| `llx_bericht_upload_token` | Zeitlich begrenzte Tokens für den QR-Mobile-Upload |
| `llx_bericht_signature_box` | Pro Lieferschein-Template gespeicherte Signatur-Box-Geometrie (mm) |
## Architektur
```
bericht/
├── core/modules/modBericht.class.php Modul-Descriptor, Tabs, Extrafields-Init, Konstanten
├── class/
│ ├── bericht.class.php Bericht + BerichtPage CRUD, Slot-Geometrie
│ ├── upload_token.class.php Tokens für den QR-Mobile-Upload
│ └── actions_bericht.class.php Hook: beforeODTSave setzt {signature} + Meta-Variablen
├── lib/bericht.lib.php Helper: Anhänge sammeln, Vorschaubilder, EXIF-Parser,
│ Seitengeometrie (bericht_page_geometry), PDF-Rendering,
│ Render-Funktionen für Seiten-/Anhängeliste,
│ Signature-Box, FPDI-Stempel-Fallback
├── bericht_card.php Editor-Seite (Tab-Inhalt)
├── 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
├── bericht_batch.php Sammel-Erstellung von Berichten
├── bericht_thirdparty.php Read-only Übersicht auf der Kundenkarte
├── mobile_upload.php Upload-Seite für den QR-Code (Handy)
├── ajax/ Endpoints für den Editor (Token-geschützt)
│ ├── _inc.php Gemeinsamer Header (Rechte, JSON, Fatal-Handler)
│ ├── add_attachment.php Anhang als Seite hinzufügen
│ ├── attachment_thumb.php Vorschaubild eines Anhangs (GD, gecacht, ETag)
│ ├── create_grid_page.php Raster-Seite aus mehreren Bildern anlegen
│ ├── create_upload_token.php Token für den QR-Mobile-Upload
│ ├── delete_attachment.php Datei aus den Dokumenten des Belegs löschen
│ ├── delete_page.php Einzelne Seite löschen
│ ├── fragments.php Seiten-/Anhängeliste als HTML (Arbeiten ohne Reload)
│ ├── generate_pdf.php Finalisierung: TCPDF + FPDI + ODT-Deckblatt
│ ├── get_photo.php / list_photos.php Fotos für die PWA
│ ├── list_pages.php Seitenliste als JSON (Polling)
│ ├── page_bulk.php Mehrere Seiten löschen oder verdoppeln
│ ├── page_image.php Seitenbild/PDF ausliefern (Raster: Composite via GD)
│ ├── page_meta.php Annotationen + Notiz laden
│ ├── preview_pdf.php PDF-Vorschau ohne zu finalisieren
│ ├── process_document.php Import hochgeladener Dokumente
│ ├── reorder_pages.php Reihenfolge speichern
│ ├── replace_page_source.php Bild einer Seite / eines Rasterplatzes austauschen
│ ├── save_annotations.php Fabric-JSON + Composite-PNG speichern
│ ├── save_as_template.php Bericht als Vorlage sichern
│ ├── save_meta.php Titel, Format, Ausrichtung
│ ├── save_page_options.php Layout, Bildgröße, Position je Seite
│ ├── save_signature_box.php UPSERT der Signatur-Box pro Template
│ ├── set_slot_image.php Bild eines Rasterplatzes setzen
│ ├── upload_extra.php Direkter Upload
│ └── verify_signature.php Unterschrift prüfen
├── api/ REST-API (Auth über awl_sso-Cookie, Modul awlauth)
│ ├── _inc.php Dolibarr-Init + awlauth_require + JSON-Helper
│ ├── auth.php Login-Endpoint
│ ├── orders.php Order-Liste, Detail, Fotos, Create
│ ├── shipments.php Lieferungen-Liste, PDF-Stream, Unterschrift-Confirm
│ ├── photo.php Datei-Serving mit Whitelist + Thumbnails (size=thumb)
│ ├── pdf.php Finalized Bericht-PDF
│ ├── pages.php Seiten-Verwaltung (Note, Rotation, Signature)
│ ├── reports.php Bericht-CRUD
│ ├── templates.php ODT-Templates
│ ├── materials.php Materiallisten
│ ├── voice.php Sprachnotizen (Upload + Transkription)
│ ├── note.php Textnotizen zum Auftrag (Liste/Lesen/Anlegen/Ändern)
│ └── transcribe.php Whisper-Transkription
├── js/
│ ├── editor.js PDF.js + Fabric.js Integration, Anhänge-Spalte,
│ │ Seitengeometrie, Drag&Drop, Autosave, Tastenkürzel
│ ├── imageviewer.js Großansicht (Zoom, Wischen, PDF-Modus)
│ └── lib/ PDF.js, Fabric.js, SortableJS, QRCode (lokal)
├── css/
│ ├── bericht.css Editor-Layout, Kacheln, Blatt-Darstellung
│ └── imageviewer.css Großansicht
├── sql/
│ ├── llx_bericht.sql / .key.sql
│ ├── llx_bericht_page.sql / .key.sql
│ ├── llx_bericht_page_image.sql / .key.sql
│ ├── llx_bericht_upload_token.sql / .key.sql
│ └── llx_bericht_signature_box.sql / .key.sql
└── langs/{de_DE,en_US}/bericht.lang
```
## Lizenz
GPL v3+