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>
README: Anhaenge-Spalte, Arbeitstisch, Autosave, Tastenkuerzel, Bildtausch,
Sammelaktionen und Drag & Drop beschrieben; Architektur-Baum vervollstaendigt
(15 fehlende ajax-Endpoints, imageviewer, Root-Seiten, SQL-Dateien); GD als
Voraussetzung ergaenzt; llx_bericht_upload_token ist nicht mehr "geplant".
CLAUDE.md: Abschnitt zur Seitengeometrie als zentralem Begriff, LocalStorage-Keys,
Phase 1 abgeschlossen. Korrigiert die falsche Notiz, --inputbackgroundcolor gebe es
im awl-dark nicht — die Variable existiert (KB #512), deshalb waren die Eingabefelder
im Editor anders gestylt als Dolibarrs eigene.
Fix: dol_dir_list() zusaetzlich mit $nohook = 1 (KB #1060) — sonst laeuft der
getDirList-Hook fremder Module mit, der global $object auswertet; bericht_collect_attachments()
laeuft seit 1.5.0 auch im AJAX-Kontext (ajax/fragments.php), wo es das nicht gibt.
ROADMAP.md entfernt: alle Punkte abgearbeitet, Inhalt steht im ChangeLog.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Anhaenge-Spalte: Kachelraster mit Server-Thumbnails statt Dateinamen-Liste (Bilder per GD,
PDFs per PDF.js), Grossansicht per Klick, Auswahlreihenfolge sichtbar, Sortierung/Filter
(Aufnahmezeit per eigenem EXIF-Parser, da im Prod-Container ohne exif-Modul), ziehbare
Spaltenbreite, Kachelgroesse, Drag & Drop auf Seitenliste/Arbeitsflaeche/Raster-Plaetze.
Arbeitstisch zeigt jetzt Kopf-, Inhalts- und Fussbereich passend zur echten Seitengeometrie
(bericht_page_geometry()) - Editor und PDF-Erzeugung rechnen mit denselben Werten, das PDF
setzt die Arbeitsflaeche 1:1 statt sie ein zweites Mal einzupassen.
Neu: Bild einer Seite austauschen (auch einzelne Raster-Plaetze), Seiten mehrfach markieren
und sammeln loeschen/duplizieren, Autosave mit Verlassen-Warnung, Tastenkuerzel, kein
location.reload() mehr nach Hinzufuegen/Loeschen/Hochladen (ajax/fragments.php).
Nebenbei behoben: dol_dir_list() ohne mode=1 (Dateigroesse leer), PDF.js-Worker-Pfad ohne
/custom-Praefix, PDFs liefen als Download statt inline, Groessen-Select blieb leer,
Mehrfach-Upload nahm nur die erste Datei, Race-Condition bei schnellem Seitenwechsel.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
api/orders.php?action=create gibt den Auftrag mit validate=true jetzt ohne
jede Position frei. Bisher hing das Backend vorher eine Std-Lohn-Zeile mit
Menge 1 an (Kundenkarten-Default, sonst Fallback-Produkt).
Die Annahme, Commande::valid() scheitere an einem leeren Auftrag, ist falsch -
weder valid() noch confirm_validate pruefen auf Zeilen, gesperrt ist nur die
Button-Darstellung in commande/card.php (KB #355/#989, gegen Dolibarr 22.0.2
verifiziert). Die Alibi-Zeile verfaelschte total_ht, tauchte als offene Rest-
menge in der Lieferauflistung auf, stand im Kunden-PDF und liess sich nach der
Freigabe nicht mehr per deleteLine() entfernen. Die Leistungen kommen aus dem
Stundenzettel.
- Antwortfeld added_line entfaellt (die Baustelle-PWA hat es nie ausgewertet)
- Konstante BERICHT_ORDER_DEFAULT_SERVICE_REF wird nicht mehr verwendet
- Gleiche Umstellung in der Stunden-App (Stundenzettel 2.17.0), die mit der
neuen Aktion create_order jetzt ebenfalls Auftraege anlegen kann
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
customers.php holte die rohen englischen libelle aus c_payment_term/
c_paiement ('Credit Transfer', 'Due upon receipt'). Jetzt uebersetzt ueber
die Dolibarr-Sprachkeys PaymentConditionShort<code> / PaymentTypeShort<code>
(Sprachdatei bills), mit Fallback auf libelle wenn kein Key existiert.
Behebt 'Payment Condition' / 'Credit transfer' in der Uebernommen-Zeile.
- CLAUDE.md auf Stand 2026-04-17 gebracht
- Phase 3 (PWA MVP) und Phase 4 Block 1 als ✅ abgeschlossen dokumentiert
- Alle neuen REST-API-Endpoints aufgelistet (auth, orders, photo, pdf, pages)
- README.md: Neue PWA-Integration-Sektion mit API-Dokumentation
- Architektur erweitert um api/-Ordner mit JWT-Auth-Endpoints
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- 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]