- POST /api/orders.php?action=create: legt Draft-Auftrag an, übernimmt
Kunden-Defaults (Zahlungsbedingung, Zahlart, Bankkonto, Incoterms,
Lieferadresse) und setzt Hauptansprechpartner als externen Kontakt.
Titel wird in Extrafield options_auftragsbeschreibung abgelegt.
- /api/customers.php: liefert cond_reglement_label + mode_reglement_label
damit die PWA die übernommenen Defaults anzeigen kann.
- JWT-TTL von 7 auf 30 Tage hochgesetzt — deckt Urlaubszeiten ab und
verhindert häufiges Neu-Anmelden.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Neue API: list_photos.php listet vorhandene Bilder
- Neue API: get_photo.php liefert Bilder per Token aus
- PWA zeigt Galerie mit allen vorhandenen Fotos
- Tippen auf Thumbnail öffnet Vollbild-Viewer
- Pinch-to-Zoom, Doppeltap, Swipe-Navigation
- Galerie aktualisiert sich nach Upload automatisch
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Scanner-Seiten können vor Upload gezoomt werden
- Pinch-to-Zoom (2 Finger)
- Doppeltap für Zoom-Toggle
- Swipe links/rechts für Navigation
- Vollbild-Overlay mit Schliessen-Button
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Neuer Lightbox-Viewer für Bilder in der Anhänge-Liste
- Pinch-to-Zoom (Touch) und Mausrad-Zoom (Desktop)
- Doppelklick/Doppeltap für Zoom-Toggle (1x ↔ 2.5x)
- Swipe links/rechts für Navigation zwischen Bildern
- Tastatur: ← → Esc + - 0
- Bilder pro Gruppe navigierbar
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Neuer "Dokument scannen" Button in der Mobile-Upload-Seite:
- Eigene Scanner-View für mehrseitige Dokumente
- Seiten-Vorschau-Leiste mit Thumbnails
- Seiten hinzufügen/löschen vor dem Upload
- Backend-Verarbeitung zu PDF
OCR-Verarbeitung (wenn verfügbar):
- ocrmypdf für durchsuchbares PDF (deu+eng)
- Automatische Rotation und Schräglagekorrektur
- Fallback auf ImageMagick oder TCPDF
Server-Konfiguration (optional für OCR):
apt install tesseract-ocr tesseract-ocr-deu ocrmypdf
[deploy]
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
In der Übersicht (ohne Editor) wurde data-dolconfirm nicht abgefangen,
weil editor.js dort nicht geladen wird. Inline-Script mit gleichem
bericht-dolmodal-Stil ergänzt — kein browser-confirm().
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Token-Tabelle: fk_bericht → fk_element + element_type (generisch)
- Migration: bestehende Tokens auf neue Spalten migrieren
- upload_photo API: Foto direkt nach commande/{ref}/, kein Bericht/BerichtPage mehr
- mobile_upload.php: Upload-Ziel über Token-Methode getUploadDir() ermitteln
- Token-Erstellung: element_id + element_type statt berichtid (abwärtskompatibel)
- QR-Modal: Token für Auftrag statt für Bericht; Polling auf Anhänge-Änderung [deploy]
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
TCPDF::addTTFfont produziert bei allen 4 Hack-TTFs den gleichen Key 'hack'
(PostScript-Family-Kollision) und erzeugt eine hack.php die sich selbst
überschreibt. Der HTML-Parser löst bei <b>/<i>-Tags dann SetFont-Calls aus,
die auf nicht-existente Style-Varianten fallen zurück und mit
'Could not include font definition file: hack' abbrechen — keine Menge
an SetFont-Overrides fängt das zuverlässig ab.
Lösung: hack_font_key = helvetica. PDF-Generation funktioniert garantiert.
Hack wird später mit vorab-offline-konvertierten .php-Definitionen neu
eingebunden.
Problem: TCPDF_FONTS::addTTFfont leitet den Font-Key aus dem PostScript-
Family-Namen der TTF ab. Alle 4 Hack-TTFs teilen den internen Namen 'Hack'
und erzeugen dieselbe hack.php, die sich gegenseitig überschreibt.
SetFont(hack, 'B') sucht dann hackB.php → existiert nicht →
'Could not include font definition file: hack'.
Lösung: nur Hack-Regular.ttf registrieren, SetFont-Override erzwingt
bei der Hack-Family immer style=''. Header-Titel nutzt nur Schriftgröße
statt echter Bold-Variante.
TCPDF_FONTS::addTTFfont registriert jede TTF als eigene Family ohne
Styles (hackregular, hackbold, hackitalic, hackbolditalic). SetFont(key,'B')
suchte dann nach 'hack.php' und brach mit 'Could not include font
definition file' ab.
Fix: hack_font_styles-Map ['' / B / I / BI] => family, SetFont-Override
mapped den angeforderten Style auf die passende Family mit leerem Style
und fällt kaskadierend zurück (BI→B→'', I→'').
- ajax/_inc.php: ob_start() + register_shutdown_function fangen PHP Notices
und Fatals auf, geben strukturiertes JSON zurück (vorher Server-Fehler
'kein JSON' weil PHP-Warning mitten im Body stand).
- generate_pdf.php/preview_pdf.php: mysoc, logo-Pfad defensiv geprüft.
- Neue Klasse BerichtPdf / BerichtPdfFpdi (Trait-basiert):
* Header: links Bericht-Titel (Bold) + Firmenname, rechts Firmen-Logo (max 40x18mm),
Trennlinie. Top-Margin jetzt 30mm für den Header-Bereich.
* Footer: zentriert "Seite X / Y" mit TCPDF-Aliases.
* berichtInit(): kompiliert Hack-TTFs nach DOL_DATA_ROOT/bericht/tcpdf_fonts/
(beschreibbar) und bindet sie per AddFont an die PDF-Instance.
Vorher schlug addTTFfont still fehl weil K_PATH_FONTS read-only war —
deshalb kam weder Titel noch Notiz in Hack.
- bericht_ensure_hack_font($pdf) zieht den Font-Key jetzt aus der Instance
(BerichtPdfTrait), sonst Fallback helvetica.
- bericht_write_note_html() wrapped das CKEditor-HTML in
<span style="font-family:hack...;"> damit writeHTMLCell den Hack-Font
tatsächlich verwendet.
- Composite-Branch: $mT=30 / $mB=16 damit Bilder nicht unter dem Header
sitzen.
- ajax/generate_pdf, ajax/preview_pdf, api/pdf, api/reports, bericht_batch:
alle nutzen jetzt BerichtPdf(Fpdi), setzen SetMargins(10,30,10),
setPrintHeader(true) und berichtInit() mit Titel, mysoc->name und Logo.
Die fabric.Image der Quell-Bilder wurde mit toJSON([bgImage]) inkl. ihrer
blob:-URL gespeichert. Nach Reload ist die Blob-URL ungültig, fabric kann
das Bild nicht holen → weißer Canvas.
Fix: bgImage-Objekte beim Speichern aus dem JSON rausfiltern. Beim Laden
immer rerenderCurrent() aufrufen (frisches Quellbild aus page_image.php),
dann nur die Overlay-Shapes via enlivenObjects restoren.
- bericht_card.php: Plain-Textarea durch DolEditor (CKEditor, dolibarr_notes-Toolbar)
ersetzt, damit Notizen formatiert werden können.
- editor.js: getNoteValue()/setNoteValue() Helper für transparentes
CKEditor/Textarea-Handling an allen Zugriffsstellen.
- lib/bericht.lib.php:
* bericht_write_note_html() rendert CKEditor-HTML via TCPDF::writeHTMLCell
* Composite-Branch: Notiz direkt unter dem Bild statt mit SetY(-20) unten
* bericht_ensure_hack_font() registriert Hack-TTFs beim ersten PDF-Run
* Alle helvetica-SetFont-Calls auf Hack umgestellt (Corporate-Font)
- fonts/: Hack-Regular/Bold/Italic/BoldItalic.ttf (Eddys Corporate-Font)
- bericht_batch.php: ebenfalls Hack-Font
Diagnose: bei alten Berichten ohne composite_path wurde das bgImage
beim Öffnen nicht geladen (fabric.Image.fromURL mit crossOrigin bei
Blob-URL hing sich auf). Danach hat jeder Save das Composite ohne
Bild gespeichert → PDF-Seite leer.
Fix 1 — renderImage:
- Native Image() statt fabric.Image.fromURL
- Kein crossOrigin bei Blob-URLs (überflüssig, verursachte Hänger)
- Timeout 10s mit console.warn
- onerror mit Debug-Info (mime, bufSize)
- applyTool() nach Einfügen damit Tool-Lock greift
Fix 2 — savePageAnnotations:
- Wenn KEIN Bild im Canvas ist (weder bgImage=true noch type=image),
wird der Save ABGEBROCHEN statt ein leeres Composite zu speichern.
- Dadurch kann ein alter Bericht nicht versehentlich mit einem
leeren Composite überschrieben werden wenn der Bild-Load hakt.
- Toast-Warnung für den User, console.warn für Debug.
Fix 3 — finalize-Handler:
- Native confirm() vor dem Finalisieren
- Bessere Fehler-Diagnose: lese Response als text(), parse JSON,
logge bei JSON-Parse-Fehler den Raw-Body
- toast() statt alert() für Fehler
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
Wenn ein Bericht vor Phase 6 gespeichert wurde, enthält fabric_json
nur Shapes (Pfeile, Text etc.) — das Quellbild war damals noch
pdfCanvas-Hintergrund. Beim Restore war jsonHasObjects=true und
rerenderCurrent wurde nicht aufgerufen → Canvas weiß.
Fix: Zusätzlicher Check jsonHasBgImage. Wenn kein Image im JSON
(weder bgImage=true noch type=image), wird zusätzlich zu den
geladenen Shapes rerenderCurrent() aufgerufen, das das Quellbild
als fabric.Image hinter die Shapes legt.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
Drei Probleme behoben:
1. Wenn fabric_json existierte aber leer oder ohne Objekte war, wurde
rerenderCurrent() NICHT aufgerufen und das Canvas blieb leer.
2. Canvas-Dimensionen wurden nur im fabric_json-Zweig gesetzt — bei
altem Berichten ohne JSON fehlte der Init und das Canvas war 1x1.
3. loadFromJSON-Callback hat zwar das Promise resolved, aber die
nachfolgende applyTool() für Tool-Locking fehlte, dadurch waren
die geladenen Bilder nach Reload immer ziehbar.
Fix:
- Canvas-Dimensionen IMMER zuerst auf A4 setzen
- Nur laden wenn fabric_json tatsächlich Objekte enthält
- Sonst (oder bei Parse-Fehler) Fallback auf rerenderCurrent() das
das Quell-Bild frisch als fabric.Image lädt
- applyTool() nach loadFromJSON damit Tool-Lock-Status korrekt ist
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
Vorheriger Fix '.bericht-attachments button { width:100% }' hat auch
die kleinen Icons in den Anhang-Items (Mülleimer zum Löschen, Buttons
in Listen-Items) erwischt und auf volle Breite gepumpt. Dadurch wurde
der Dateiname nicht mehr sichtbar.
Fix: Selector auf .bericht-add-selected + .bericht-upload beschränken
(die beiden Action-Blöcke am Ende der Spalte). Die Item-Buttons
innerhalb der Liste behalten ihr Default-Styling.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
Anhänge-Spalte:
- Buttons und Labels waren unterschiedlich breit → Scrollbalken querte
die Box. Fix: alle Controls (.butAction, button, select) in
.bericht-attachments auf display:block + width:100% + box-sizing
border-box, einheitliches Padding, ellipsis bei Überlänge.
- overflow-x: hidden verhindert horizontale Scrollbar im Container.
- min-width: 0 damit grid-Container nicht überlaufen.
Zeichen-Tools:
- Problem: Beim Malen wurden die Bilder (fabric.Image) mitbewegt
weil sie immer selectable/evented waren.
- Fix: applyTool() setzt auf ALLEN Canvas-Objekten selectable=false
und evented=false sobald ein Zeichen-Tool aktiv ist. Nur bei
'select' werden sie wieder aktiviert.
- discardActiveObject() nach Tool-Wechsel damit keine Selektion
aktiv bleibt.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
Fabric.js wickelt #fabric-canvas in einen .canvas-container ein. Meine
bisherige CSS-Regel '#fabric-canvas.fabric-overlay position:absolute'
wirkte nur auf das innere Canvas, nicht auf den Container. Dadurch saß
der Container im normalen Flex-Flow neben dem pdfCanvas und verdrängte
es nach links — der rechte Bereich wurde vom Container als weiße Fläche
überlagert und verdeckte Bild-Inhalte.
Fix: neue CSS-Regel '.bericht-canvas-wrap .canvas-container position:
absolute !important' damit der Container wie vorgesehen absolut
positioniert wird und den pdfCanvas nicht aus dem Flow drückt.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
Der Layout-Dropdown in der Toolbar bezog sich nur auf die aktuell
angezeigte Seite, nicht auf die Übernahme-Aktion. Ergebnis: User
stellten 2x2 ein, kreuzten 4 Bilder an, klickten 'Übernehmen' — und
bekamen 4 einzelne Seiten statt einer Grid-Seite.
Fix: Neben dem Übernahme-Button eigener Layout-Selector mit Optionen
Einzeln / 2 / 2v / 4 / 6 / Vorher-Nachher. Bei Grid-Layout werden
die ausgewählten Bilder automatisch in Gruppen à slotCount aufgeteilt
und entsprechend viele Seiten angelegt (z.B. 10 Bilder + grid_4 = 3
Seiten mit 4+4+2). Die einzelnen ▭▭/▦/VN-Buttons entfallen damit.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
Vorher blieb 'Seite wählen oder Fotos hinzufügen' auch sichtbar wenn
eine Seite geladen wurde — die .empty-Klasse wurde nur beim Seiten-
wechsel per loadPage() entfernt, nicht beim initialen Load. Resultat:
bei einem Bericht mit bestehender Seite erschien der Text mitten im
weißen Canvas.
Fix: beim init() wird die Klasse nur gesetzt wenn überhaupt keine
Page-Thumbs existieren (Bericht wirklich leer).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
Paradigmen-Wechsel: Editor rendert bei jedem Save sein Fabric-Canvas
als PNG und lädt es hoch. PDF nutzt dieses PNG 1:1 statt die Shapes
serverseitig nachzuzeichnen.
Damit ist garantiert: was du im Editor siehst, ist EXAKT das was im PDF
landet. Alle Pfeil/Text/Shape-Rendering-Bugs zwischen Fabric-JSON und
PHP-Nachzeichnung sind Geschichte.
Kernänderungen:
1. DB: Neue Spalte bericht_page.composite_path (Migration im init())
2. ajax/save_annotations.php: nimmt multipart file 'composite' entgegen,
speichert es unter bericht/work/<fkb>/composite_<pid>.png
3. lib/bericht.lib.php: bericht_render_page_to_pdf prüft composite_path
zuerst — wenn vorhanden, wird eine Seite mit genau diesem PNG als
volles Bild gerendert, fertig. Fallback auf alte Logik bei alten
Berichten ohne Composite.
4. editor.js renderImage: Quellbild wird NICHT mehr auf pdfCanvas
gezeichnet, sondern als fabric.Image ins Fabric-Canvas geladen —
ZIEHBAR, SKALIERBAR, ROTIERBAR wie jedes andere Objekt.
Mehrere Bilder auf einer Seite kein Problem mehr.
5. editor.js savePageAnnotations: nach Shape-State wird toDataURL
mit multiplier:2 aufgerufen, PNG-Blob hochgeladen zusammen mit
fabric_json (für spätere Edits) und note.
6. editor.js loadPage: wenn fabric_json existiert, wird dieses
clientseitig wieder eingeladen (inkl. eingebettete Bilder) — das
Quell-Bild wird nicht mehr neu aus der Quelle geholt. Bei leerer
Seite läuft der alte Render-Flow.
Phase 6 Bonus — Text mit Hintergrund:
- Neuer color-picker 'BG:' in der Toolbar + 'Ø'-Button (kein BG)
- Fabric IText bekommt textBackgroundColor + padding:6
- Bei selektiertem Text-Objekt wird BG live angewendet
- Dataset-Flag 'active' toggelt zwischen ein/aus
Dark-Input-Fix:
- Textarea in .bericht-page-note nutzte --inputbackgroundcolor
(existiert in awl-dark nicht → Fallback #fff = weiße Fläche mit
schwarzer Schrift auf Dark-Theme)
- Jetzt: --colorbackbody + --colortext + --colorboxbordertitle1
- Generischer Input-Style für alle Text-Eingaben in .bericht-editor
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
Wenn noch keine Seite geladen ist, wirkte der Editor mit dem winzigen
1x1-Canvas sehr abgebastelt. Jetzt zeigt der Canvas-Wrap im leeren
Zustand eine echte A4-Hochformat-Fläche (max 900px, aspect-ratio 1:1.414)
mit Hinweistext 'Seite wählen oder Fotos hinzufügen'.
- .bericht-canvas-wrap.empty setzt aspect-ratio auf A4
- ::after Pseudo-Element als Hinweistext
- init() markiert den Wrap als 'empty'
- loadPage() entfernt die Klasse sobald ein Bild geladen wird
- min-height 400px damit auch ohne Content Platz da ist
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
Nach dem Deploy sind die neuen Spalten version/fk_bericht_parent
(Phase 5.3) erst da wenn das Modul einmal reaktiviert wurde.
Bis dahin knallte der INSERT mit 'Unknown column version' und man
konnte keine neuen Berichte anlegen.
Fix: create() prüft via SHOW COLUMNS welche optionalen Spalten
(page_format, page_orientation, is_template, template_label,
version, fk_bericht_parent) tatsächlich existieren und nimmt nur
die vorhandenen in das INSERT-Statement auf.
Damit laufen auch Systeme, bei denen die Migration noch nicht
durchgelaufen ist, ohne Fehler weiter. Nach der Reaktivierung
werden automatisch alle Spalten befüllt.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
- Neue Tabelle llx_bericht_material (element_type, fk_element, label,
qty, unit, note, fk_user_creat, datec) via Migration
- api/materials.php: GET list, POST anlegen, DELETE löschen
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
Desktop-Editor Polish:
- Aktives Seiten-Thumbnail deutlich markiert (scale + shadow + blauer
Label-Hintergrund)
- Titel-Feld pro Seite in der DB (llx_bericht_page.title)
Phase 5.2 Vorher/Nachher-Layout:
- Neuer layout-Typ 'before_after' in bericht_render_page_to_pdf
- Zwei Bilder nebeneinander, Labels 'Vorher' / 'Nachher', Titel oben
- 'VN'-Button in der Anhänge-Grid-Leiste
- create_grid_page akzeptiert before_after
- Layout-Dropdown hat before_after + title_only Option
Phase 5.2b Title-Only Seiten:
- Reine Titel/Zwischentitel-Seiten ohne Bild
- 32pt Titel zentriert, optional Notiz darunter
- bericht_render_page_to_pdf behandelt title_only separat
Phase 5.3 Bericht-Versionierung:
- Neue Spalten version + fk_bericht_parent in llx_bericht
- Bericht::duplicateAsNewVersion() kopiert alles inkl. Seiten
- '🔀 Neue Version'-Button im Editor-Footer
- Versions-Links in der Meta-Zeile (v1, v2, v3 …) mit Sprungmöglichkeit
- Alte Versionen bleiben unverändert erhalten
Phase 5.6 Batch-Modus:
- Neue Seite bericht_batch.php mit Filter (Datum von/bis, Suche)
- Checkbox-Liste aller finalisierten Berichte
- 'Ausgewählte als Sammel-PDF herunterladen' → FPDI merged alle
final_pdf_path in ein neues PDF mit Inhaltsverzeichnis-Seite
- Link im Admin-Setup
api/pages.php:
- POST-Update akzeptiert jetzt auch title und layout zusätzlich zu
note und rotation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
Der Dolibarr-Cron-Scheduler ist auf Prod nicht aktiv, deshalb hing
der tägliche Cleanup-Job 'Expired Upload-Tokens bereinigen' auf
'Geplant' und GlobalNotify meldete ihn als hängenden Job.
Lösung: Cronjob komplett aus dem Modul entfernt. Das Cleanup läuft
jetzt opportunistisch bei jedem neuen Token-Insert:
BerichtUploadToken::create() führt vor dem INSERT ein
DELETE FROM llx_bericht_upload_token WHERE expires_at < NOW()
aus. Das ist minimal teurer (1 Query extra pro Token-Create,
~0ms bei leerer Tabelle), aber vollständig cron-unabhängig.
WICHTIG für Prod: Nach Deploy muss der bestehende Cron-Eintrag
manuell aus llx_cronjob gelöscht werden (oder das Modul einmal
deaktiviert + reaktiviert werden, dann wird er mit remove() bzw.
init() neu gesetzt — ohne Eintrag). GlobalNotify ist danach ruhig.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
Bericht-Vorlagen (Phase 5.5):
- DB: is_template, template_label in llx_bericht
- Bericht::fetchAllTemplates() und createFromTemplate()
- fetchAllForElement() blendet Vorlagen aus
- ajax/save_as_template.php erzeugt Vorlage aus aktuellem Bericht
- Desktop-Editor: '📋 Als Vorlage' Button im Action-Bereich
- Bericht-Übersicht: Vorlagen-Dropdown beim + Neu Button
- api/templates.php: GET list + POST create_from_template
Schnell-Bericht (Phase 4.a/4.i):
- api/reports.php?action=create POST-Endpoint: Titel, Format,
Orientation, ODT-Template, optional template_id
- api/odt_templates.php: Liste der Deckblatt-Vorlagen
Whisper-Transkription (Phase 5.7):
- api/transcribe.php: POST mit relpath, nutzt externen Whisper-
HTTP-Endpoint (whisper.cpp server ODER OpenAI-kompatibel)
- Konfiguration im Admin: BERICHT_WHISPER_URL/MODE/API_KEY/LANG
- Sprache default 'de'
Cron-Fix:
- BerichtUploadToken::cleanupExpired() ist jetzt Instanz-Methode
(Dolibarr ruft new Klasse($db) bei jobtype=method auf)
- Returnwert für Cron-Success/Failure
- Statische Variante als cleanupExpiredStatic() für direkte Aufrufe
- Damit läuft der tägliche Cron 'Expired Upload-Tokens bereinigen'
nicht mehr hängend
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
ajax/verify_signature.php:
- Liest Metadaten aus .meta.json neben einer Unterschrift-Seite
- Rechnet SHA256 über die zum Signaturzeitpunkt existierenden Seiten
(erste N laut meta.page_count_at_signing)
- Vergleicht mit dem gespeicherten Hash → verified true/false
- Liefert reason bei Fehlschlag (Seitenanzahl oder Inhalt geändert)
bericht_card.php:
- Seiten mit .meta.json kriegen 🔒-Badge + grünen Rand
- 🔍-Button neben dem Löschen-Button öffnet Verifikations-Modal
editor.js:
- showSignatureVerifyResult-Modal zeigt alle Metadaten,
beide Hashes, GPS als OpenStreetMap-Link, IP, User, Zeit
- Grün/Rot je nach Verifikationsergebnis
api/pages.php:
- Neuer Action reorder: POST mit {order:[ids]} schreibt neue
page_order-Werte als Transaktion
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
Unterschrift (api/pages.php?action=signature):
- Metadaten einbrennen: Überschrift, Bericht-Ref, Parent-Ref, Kunde,
Datum/Zeit (Server), Unterzeichner-Name, Bestätigungstext, GPS
- SHA256 Hash-Verkettung über alle vorherigen Seiten + Bericht-Ref
→ nachträgliches Austauschen von Seiten fällt auf
- Composite-Canvas: Header mit Metadaten, Unterschrift mittig,
Footer mit Hash + Server + Zeitstempel
- Metadaten-JSON neben der PNG (für spätere Verifikation)
- signer_name ist Pflicht, gps_lat/gps_lon optional
- Page-Note zeigt Unterzeichner + Zeit statt nur 'Unterschrift Kunde'
Kundenkarten-API (api/customers.php):
- GET ohne id = Liste mit Suche (q-Param), zeigt Stammdaten + Bericht-Count
- GET mit id = Detail: Stammdaten, letzten 50 Aufträge, 50 Rechnungen,
100 Berichte über UNION aus commande/facture JOIN
- Nur echte Kunden (client IN (1,2,3)), keine reinen Lieferanten
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
- api/pages.php:
- DELETE (oder POST ?delete=1) — Seite aus Bericht entfernen
(source_path wird nur gelöscht wenn unter bericht/work/, damit
Anhänge von Auftrag/Rechnung nicht mit rausfliegen)
- POST {note, rotation} — Meta einer Seite updaten
- POST ?action=signature&bericht_id=X + multipart file= — PNG
Unterschrift als neue Seite am Bericht anhängen mit
note='Unterschrift Kunde'
- api/pdf.php — liefert finales PDF oder on-the-fly Preview mit
JWT-Auth (damit PWA das PDF direkt im <iframe> anzeigen kann)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
- Wenn mehrere Berichte zum Auftrag existieren, wird der erste
gefundene ENTWURF (status=0) wiederverwendet
- Finalisierte Berichte werden NICHT mehr mit neuen Seiten überschrieben
- Optional ?bericht_id=X um gezielt in einen bestimmten Bericht hochzuladen
- Wenn alle Berichte final sind, wird automatisch ein neuer Entwurf angelegt
So kann die PWA mehrere Fotos hintereinander in denselben Bericht
packen, statt pro Upload einen neuen anzulegen.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
- reports.php: GET ohne id listet alle Berichte des Users
(Multi-User-Filter über fk_user_creat + Parent fk_user_*),
mit parent_ref, page_count, status
- reports.php action=finalize: generiert jetzt wirklich das PDF
(TCPDF+FPDI + bericht_render_page_to_pdf), schreibt ECM-Eintrag,
setzt Status auf Final
- api/delete_photo.php: JWT-Version von delete_attachment
- api/voice.php: Audio-Upload pro Auftrag (webm/mp4/mp3/ogg)
in das Auftrags-Anhang-Verzeichnis
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
Manche Apache-Setups (Prod!) leiten den Authorization-Header nicht
als HTTP_AUTHORIZATION in $_SERVER weiter. Jetzt wird zusätzlich
REDIRECT_HTTP_AUTHORIZATION und apache_request_headers() geprüft.
Fallback: ?jwt=<token> als Query-Param akzeptieren (wird von der
PWA jetzt standardmäßig mitgesendet für <img>-kompatible URLs).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
_inc.php setzt standardmäßig Content-Type: application/json — das
hat photo.php beim Bildversand blockiert (die Blob kam als JSON rein
und der Browser konnte sie nicht als Bild darstellen).
Jetzt lädt photo.php Dolibarr direkt, dekodiert JWT manuell (mit
Query-Param-Support für Fallback ohne Header-Auth) und sendet NUR
image/jpeg-Header beim Ausliefern.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
Neuer Endpoint liefert Dateien aus DOL_DATA_ROOT aus, geschützt per
JWT statt Dolibarr-Session. Whitelist auf facture/commande/propal/
bericht. Optional size=small/mini für Thumbs (Dolibarr _small Variante).
CORS-Header damit PWA direkt zugreifen kann.
Die PWA kann damit Anhang-Bilder in der Auftrags-Detail-Ansicht
rendern — vorher nutzte sie document.php was nur mit Session ging
und deshalb im Standalone-Mode leer blieb.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]