Dolibarr-Modul: Arbeitsberichte aus Anhängen erstellen, annotieren, als PDF an Rechnungen anhängen
Find a file
Eduard Wisch 3905ad48b9
All checks were successful
Deploy bericht / deploy (push) Successful in 13s
Trigger: Deploy 1.9.0 [deploy]
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-21 20:04:46 +02:00
.forgejo/workflows CI: Ntfy-Benachrichtigungen (Start/Success/Failure) via data/ntfy-action 2026-05-28 06:54:41 +02:00
admin Fotoqualitaet einstellbar, Vorgabe 1:1 statt fest 2000 px (v1.7.0) [deploy] 2026-08-28 19:06:45 +02:00
ajax 1.7.5: WebP beim Hochladen in JPG/PNG umwandeln statt ablehnen [deploy] 2026-10-19 22:03:06 +02:00
api 1.9.0: Auftragsbeschreibung aus der Baustellen-App ändern 2026-10-21 19:57:16 +02:00
class Reiter am Kunden nur bei vorhandenen Berichten (1.7.1) [deploy] 2026-09-07 21:28:28 +02:00
core/modules 1.9.0: Auftragsbeschreibung aus der Baustellen-App ändern 2026-10-21 19:57:16 +02:00
css Editor-Ueberarbeitung 1.5.0: Anhaenge-Kacheln, echte Seitengeometrie, Arbeiten ohne Reload [deploy] 2026-08-22 00:33:20 +02:00
fonts DolEditor für Notiz + Notiz unter Bild + Hack-Font im PDF [deploy] 2026-04-09 15:25:10 +02:00
js 1.7.5: WebP beim Hochladen in JPG/PNG umwandeln statt ablehnen [deploy] 2026-10-19 22:03:06 +02:00
langs 1.7.5: WebP beim Hochladen in JPG/PNG umwandeln statt ablehnen [deploy] 2026-10-19 22:03:06 +02:00
lib 1.8.0: Videos aus der Baustellen-App annehmen und abspielbar ausliefern [deploy] 2026-10-19 23:54:19 +02:00
sql Feature: Lieferschein-Unterschrift via ODT-Hook + PWA-Signatur-Workflow 2026-05-28 06:48:42 +02:00
.gitignore feat: Initiales Release Bericht-Modul v1.0.0 [deploy] 2026-04-08 15:18:59 +02:00
bericht_batch.php PDF-Header mit Logo+Titel, Footer mit Seitenzahl, Hack-Font beschreibbar [deploy] 2026-04-09 15:39:42 +02:00
bericht_card.php 1.7.4: AVIF-Bilder beim Hochladen in JPG/PNG umwandeln [deploy] 2026-10-19 21:43:40 +02:00
bericht_thirdparty.php feat: Phase 1.3 + 1.7 + Schema 1.4/1.5 — Format/Orient + Kunden-Tab 2026-04-08 22:20:09 +02:00
ChangeLog.md 1.9.0: Auftragsbeschreibung aus der Baustellen-App ändern 2026-10-21 19:57:16 +02:00
CLAUDE.md 1.8.0: Videos aus der Baustellen-App annehmen und abspielbar ausliefern [deploy] 2026-10-19 23:54:19 +02:00
mobile_upload.php 1.7.5: WebP beim Hochladen in JPG/PNG umwandeln statt ablehnen [deploy] 2026-10-19 22:03:06 +02:00
modulebuilder.txt fix: modulebuilder.txt Marker hinzugefügt — Modul erscheint jetzt im Module Builder [deploy] 2026-04-08 15:23:05 +02:00
README.md 1.9.0: Auftragsbeschreibung aus der Baustellen-App ändern 2026-10-21 19:57:16 +02:00
ROADMAP.md ROADMAP: tote Konstante BERICHT_TAB_ON_THIRDPARTY und Doku-Drift notiert 2026-09-19 00:00:19 +02:00

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)
  • Auftragsbeschreibung aus der Baustellen-App (1.9.0) — orders.php?action=set_beschreibung schreibt das Zusatzfeld auftragsbeschreibung (Merkzettel zum Auftrag), mit Konfliktschutz (409 bei Büro-Änderung). Steht auch auf dem Stundenzettel-PDF.
  • Videos aus der Baustellen-App (1.8.0) — orders.php?action=upload_photo nimmt mp4/m4v/mov/webm (Inhalt geprüft, Name video_…, Aufnahmezeit aus taken_at bzw. dem MP4-Kopf), photo.php liefert Teilbereiche (Range) zum Abspielen und Spulen, config.php meldet upload_max, zu groß → 413. Im Editor 🎬-Kachel, nicht in Berichte übernehmbar.
  • AVIF- und WebP-Bilder (1.7.4/1.7.5) — beim Hochladen (Editor, PWA, Handy-Upload) am Inhalt erkannt und in JPG bzw. PNG (mit Transparenz) umgewandelt (lib/bericht_avif.lib.php, bericht_upload_bild_umwandeln(), KB #1698, #447). Bewegte WebP werden mit Meldung abgelehnt.
  • Ablage am Beleg als Beilage (1.7.2) — Fotos, Notizen, Sprachnotizen, Scans und Bericht-PDFs, die im Belegordner landen, stehen im ECM-Index als uploaded (lib/bericht_index.lib.php): nur im Reiter „Verknüpfte Dokumente", nie vorne im Kasten der Belegkarte (KB #1641)
  • 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?id=<id>&action=set_beschreibung   (1.9.0)
       Body: { text, vorher? } — Auftragsbeschreibung (Zusatzfeld auftragsbeschreibung) ändern,
       Recht „Aufträge ändern". Weicht der Server-Stand von `vorher` ab → 409 mit aktuellem Text.
       Achtung: die Beschreibung steht auch auf dem Stundenzettel-PDF für den Kunden.
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: 40–800, 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+