Doku auf Stand 1.5.0: README + CLAUDE.md, dol_dir_list mit nohook=1
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>
This commit is contained in:
parent
4102ebfe5b
commit
eb37a4b632
4 changed files with 129 additions and 194 deletions
81
CLAUDE.md
81
CLAUDE.md
|
|
@ -1,6 +1,6 @@
|
|||
# Bericht-Modul — Projekt-Status & Architektur
|
||||
|
||||
## Stand 2026-05-27
|
||||
## Stand 2026-08-21 (Version 1.5.0)
|
||||
|
||||
Dolibarr-Custom-Modul für Arbeitsberichte mit Browser-PDF-Editor + PWA-API-Layer + Lieferschein-Unterschrift.
|
||||
|
||||
|
|
@ -34,19 +34,42 @@ Berichte gehören 1:1 zu einem Parent (`element_type` + `fk_element`). Auf einer
|
|||
**500033** — kollidiert NICHT mit BankImport (500021). Permission-IDs sind 500033 + n.
|
||||
|
||||
### CSS-Variablen (Dolibarr awl-dark Theme)
|
||||
Verwendete: `--colorbacktitle1`, `--colortext`, `--colorbackbody`, `--colorboxbordertitle1`, `--colortextlink`, `--colorbackhmenu1`, `--colortextbackhmenu`, `--colorbackvmenu1`
|
||||
Verwendete: `--colorbacktitle1`, `--colortext`, `--colorbackbody`, `--colorboxbordertitle1`,
|
||||
`--colortextlink`, `--colorbackhmenu1`, `--colortextbackhmenu`, `--colorbackvmenu1`,
|
||||
`--colorbacklinepair1`, `--colorbacklinebreak`, `--colorbacklinepairchecked`,
|
||||
`--colortexttitlenotab`, `--colortexttitlenotab2`, `--inputbackgroundcolor`, `--inputbordercolor`,
|
||||
`--btncolorborderhover`
|
||||
|
||||
NICHT vorhanden im awl-dark: `--inputbackgroundcolor`, `--inputtextcolor` — stattdessen Fallbacks nutzen.
|
||||
**Korrektur 2026-08-21:** Hier stand, `--inputbackgroundcolor` und `--inputtextcolor` gebe es im
|
||||
awl-dark nicht. Das stimmt für `--inputbackgroundcolor` **nicht** — die Variable ist gesetzt
|
||||
(`rgb(20,22,24)`), ebenso `--inputbordercolor` (`rgb(54,57,62)`). Wegen der falschen Notiz waren
|
||||
die Eingabefelder im Editor mit `--colorbackbody` gestylt und sahen anders aus als Dolibarrs
|
||||
eigene Felder. Vollständige, per DevTools geprüfte Liste: **KB #512**.
|
||||
|
||||
`--colorbackhmenu1` (`rgb(39,44,49)`) taugt **nicht** für Aktiv-Zustände — praktisch derselbe Ton
|
||||
wie der Toolbar-Hintergrund. Dafür `--colorbacklinepairchecked` (`rgb(30,87,116)`) nehmen.
|
||||
|
||||
### JS-Libraries (lokal in js/lib/)
|
||||
- pdf.min.js (PDF.js 3.11)
|
||||
- pdf.worker.min.js
|
||||
- pdf.worker.min.js — **Pfad NIE hartkodieren**, sonst fehlt `/custom` und PDF.js fällt auf den
|
||||
„fake worker" zurück (rendert dann im Hauptthread). Kommt über
|
||||
`cfg.urls.pdf_worker` aus `dol_buildpath()`.
|
||||
- fabric.min.js (Fabric.js 5.3)
|
||||
- Sortable.min.js (SortableJS 1.15)
|
||||
- Sortable.min.js (SortableJS 1.15) — mit `group: {put: false}`, sonst kollidiert das Sortieren
|
||||
mit dem eigenen Ablegen von Anhang-Kacheln
|
||||
- qrcode.min.js (QR-Code für den Mobile-Upload)
|
||||
|
||||
### LocalStorage Settings
|
||||
Key: `bericht.editor.settings.v1`
|
||||
Speichert: color, stroke, fontFamily, fontSize, bold, italic, zoom
|
||||
| Key | Inhalt |
|
||||
|---|---|
|
||||
| `bericht.editor.settings.v1` | color, stroke, fontFamily, fontSize, bold, italic, zoom |
|
||||
| `bericht.attachments.view.v1` | Kachel- oder Listenansicht der Anhänge |
|
||||
| `bericht.attachments.sort.v1` | Sortierung (taken / taken_desc / name / size_desc) |
|
||||
| `bericht.attachments.filter.v1` | Filter (all / image / pdf / unused) |
|
||||
| `bericht.attachments.tilesize.v1` | Kachelgröße (s / m / l) |
|
||||
| `bericht.attachments.width.v1` | Breite der Anhänge-Spalte in px |
|
||||
| `bericht.attachments.collapsed.v1` | Spalte eingeklappt (1/0) |
|
||||
| `bericht.editor.margins.v1` | Seitenränder im Arbeitstisch anzeigen (1/0) |
|
||||
|
||||
### Forgejo
|
||||
- Repo: `data/bericht` (NICHT `data-it/bericht` — Token hat keine org-write rechte)
|
||||
|
|
@ -56,7 +79,7 @@ Speichert: color, stroke, fontFamily, fontSize, bold, italic, zoom
|
|||
|
||||
---
|
||||
|
||||
## Phase 1 — Bericht-Modul-Erweiterungen (in Arbeit)
|
||||
## Phase 1 — Bericht-Modul-Erweiterungen ✅ (abgeschlossen mit 1.5.0)
|
||||
|
||||
### ✅ Erledigt vor Phase 1
|
||||
- Modul-Scaffold (modBericht, SQL, Lang, Rechte korrekt nach Stundenzettel-Format)
|
||||
|
|
@ -75,16 +98,40 @@ Speichert: color, stroke, fontFamily, fontSize, bold, italic, zoom
|
|||
- [x] **1.1 Live-PDF-Vorschau** ✅
|
||||
- [x] **1.2 Anhänge löschen** ✅
|
||||
- [x] **1.3 Seitengröße A4/A3/Letter + Hoch/Quer** ✅ — global pro Bericht (nicht pro Seite override)
|
||||
- [/] **1.4 Mehrere Bilder pro Seite** — DB + Klassen-Skelett da, Grid-Rendering im PDF + Editor noch offen (siehe TODO unten)
|
||||
- [/] **1.5 Bildgröße pro Seite** — image_scale/image_align Spalten da, UI noch offen
|
||||
- [ ] **1.7 Kunden-Tab** — Tab "Berichte" auf thirdparty
|
||||
- [x] **1.4 Mehrere Bilder pro Seite** ✅ 1.5.0 — Raster im PDF und im Editor; Plätze direkt auf
|
||||
der Seite anklickbar, einzeln tauschbar
|
||||
- [x] **1.5 Bildgröße pro Seite** ✅ — image_scale/image_align mit Auswahl in der Werkzeugleiste
|
||||
- [x] **1.7 Kunden-Tab** ✅ — `bericht_thirdparty.php`, Konstante `BERICHT_TAB_ON_THIRDPARTY`
|
||||
|
||||
### TODO Phase 1.4 + 1.5 (Folge-Commit)
|
||||
- BerichtPage::getImages() liest llx_bericht_page_image
|
||||
- Editor: Layout-Dropdown pro Seite (1/2/4/6 Slots)
|
||||
- Drag&Drop von Anhängen in Slots oder "Als Grid hinzufügen"-Button
|
||||
- generate_pdf/preview_pdf: Grid-Rendering (calculate slot rects)
|
||||
- Bei `single`-Layout: image_scale (1.0/0.7/0.5) + image_align (fit/center/topleft/topright)
|
||||
### Seitengeometrie — der wichtigste Begriff im Modul (seit 1.5.0)
|
||||
|
||||
`bericht_page_geometry($format, $orientation, $has_note)` in `lib/bericht.lib.php` ist die
|
||||
**einzige** Quelle für Seitenmaße und Ränder. Editor **und** PDF-Erzeugung rechnen damit:
|
||||
|
||||
- Ränder 10 mm, Kopfbereich **30 mm** (dort druckt TCPDF Logo/Titel, Trennlinie bei y=27),
|
||||
Fußbereich 16 mm, Notizzone 25 % der Seitenhöhe (nur wenn die Seite eine Notiz hat)
|
||||
- Der Editor-Canvas ist der **Inhaltsbereich**, nicht das Blatt. Das PDF setzt das gespeicherte
|
||||
Composite-PNG 1:1 dorthin.
|
||||
- **Nicht** erneut einpassen — genau das war vor 1.5.0 der Fehler (Inhalt wurde zweimal
|
||||
verkleinert, breiter Rand, Vorschau stimmte nicht).
|
||||
- Composites von vor 1.5.0 haben Blatt-Proportion; sie werden an der Proportion erkannt und
|
||||
proportional eingepasst statt verzerrt. Diese Weiche nicht entfernen, solange Altberichte
|
||||
neu erzeugt werden könnten.
|
||||
- Raster-Plätze über `BerichtPage::slotRectsInBox()` (PHP) bzw. `slotRectsPercent()` (JS) —
|
||||
beide teilen denselben Inhaltsbereich auf und müssen zusammenpassen.
|
||||
|
||||
### Weitere Fallen (2026-08-21 verifiziert)
|
||||
|
||||
- `dol_dir_list()` braucht `$mode = 1` für `size`/`date` **und** `$nohook = 1` — sonst läuft der
|
||||
`getDirList`-Hook fremder Module mit, der `global $object` auswertet (im AJAX-Kontext nicht da).
|
||||
Siehe KB #1060.
|
||||
- `document.php` braucht `&attachment=0`, sonst wird das PDF heruntergeladen statt angezeigt.
|
||||
- **Prod-Container hat kein `exif` und kein Imagick** — EXIF liest das Modul selbst
|
||||
(`bericht_jpeg_taken_at()`), PDF-Vorschauen laufen über PDF.js im Browser.
|
||||
- Seitenwechsel sind serialisiert (Kette + Token in `loadPage()`). Beim schnellen Durchklicken
|
||||
überlappten sich sonst zwei Ladevorgänge und die Notiz landete auf der falschen Seite.
|
||||
- Fabric-Ereignisse (`object:added` …) feuern auch beim **Aufbauen** einer Seite — deshalb
|
||||
`buildingPage`-Flag, sonst meldet das Autosave sofort „nicht gespeichert".
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
87
README.md
87
README.md
|
|
@ -6,10 +6,16 @@ Bilder und PDFs lassen sich im Browser annotieren (Pfeile, Kreise, Rechtecke, Te
|
|||
## Funktionen
|
||||
|
||||
- **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)
|
||||
- **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
|
||||
- **Seiten-Verwaltung** per Drag&Drop (SortableJS): umordnen, löschen, drehen, neue Seiten hochladen
|
||||
- **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)
|
||||
|
|
@ -27,8 +33,13 @@ Bilder und PDFs lassen sich im Browser annotieren (Pfeile, Kreise, Rechtecke, Te
|
|||
- 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
|
||||
|
|
@ -42,9 +53,9 @@ Bilder und PDFs lassen sich im Browser annotieren (Pfeile, Kreise, Rechtecke, Te
|
|||
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 ankreuzen → **Auswahl in Bericht übernehmen**
|
||||
5. Im mittleren Editor mit den Werkzeugen Pfeile, Texte etc. zeichnen
|
||||
6. Seiten rechts per Drag&Drop sortieren, einzelne Seiten löschen, neue Dateien hochladen
|
||||
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
|
||||
|
|
@ -152,8 +163,8 @@ Per `admin/setup.php` oder `llx_const`:
|
|||
|---|---|
|
||||
| `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_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
|
||||
|
|
@ -162,26 +173,48 @@ Per `admin/setup.php` oder `llx_const`:
|
|||
bericht/
|
||||
├── core/modules/modBericht.class.php Modul-Descriptor, Tabs, Extrafields-Init, Konstanten
|
||||
├── class/
|
||||
│ ├── bericht.class.php Bericht + BerichtPage CRUD
|
||||
│ ├── 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, Auftragsnr., Templates, Shipment-PDF,
|
||||
│ Signature-Box, FPDI-Stempel-Fallback)
|
||||
├── 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
|
||||
├── ajax/ Legacy Endpoints (Token-geschützt)
|
||||
│ ├── _inc.php Gemeinsamer Header
|
||||
├── 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
|
||||
│ ├── upload_extra.php Direkter Upload
|
||||
│ ├── save_annotations.php Fabric-JSON speichern
|
||||
│ ├── save_signature_box.php UPSERT der Signatur-Box pro Template
|
||||
│ ├── 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
|
||||
│ ├── page_image.php Seitenbild/PDF ausliefern
|
||||
│ ├── delete_page.php
|
||||
│ ├── reorder_pages.php
|
||||
│ └── generate_pdf.php Finalisierung: TCPDF + FPDI + ODT-Deckblatt
|
||||
│ ├── 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 (JWT-Auth)
|
||||
│ ├── _inc.php JWT-Authentifizierung + Dolibarr-Init
|
||||
│ ├── _jwt.php JWT encoding/decoding (mit ?jwt= Query-Fallback)
|
||||
|
|
@ -197,12 +230,18 @@ bericht/
|
|||
│ ├── voice.php Sprachnotizen (Upload + Transkription)
|
||||
│ └── transcribe.php Whisper-Transkription
|
||||
├── js/
|
||||
│ ├── editor.js PDF.js + Fabric.js Integration
|
||||
│ └── lib/ PDF.js, Fabric.js, SortableJS (lokal)
|
||||
├── css/bericht.css
|
||||
│ ├── 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
|
||||
```
|
||||
|
|
|
|||
151
ROADMAP.md
151
ROADMAP.md
|
|
@ -1,151 +0,0 @@
|
|||
# ROADMAP — Modul Bericht
|
||||
|
||||
Stand: 2026-08-21 · Version im Repo: 1.5.0
|
||||
|
||||
---
|
||||
|
||||
## 1. Anhänge-Spalte (links im Editor) — Hauptbaustelle
|
||||
|
||||
Ausgangslage (Eddy, 21.08.2026): *„die Dateinamen können in dem schmalen Fenster nicht ganz
|
||||
angezeigt werden, ich habe Schwierigkeiten die richtigen Bilder auszusuchen."*
|
||||
Die Spalte ist 260 px breit und zeigt pro Datei nur eine Zeile `🖼 langer_dateiname… 2,3 MB 🗑️`.
|
||||
Bei 20–40 Baustellenfotos mit Namen wie `IMG_20260812_141233_HDR.jpg` ist das unbrauchbar.
|
||||
|
||||
### 1.1 Miniatur-Vorschau statt Dateiname ✅ 1.5.0
|
||||
- [x] Kachel-Raster (2 Spalten) statt Liste
|
||||
- [x] Server-Thumbnails für Bilder (GD, gecacht) — Original-Handyfotos werden **nicht** mehr voll geladen
|
||||
- [x] PDF-Anhänge: Seite 1 als Miniatur (PDF.js, clientseitig, lazy)
|
||||
- [x] Klick auf Miniatur → Lightbox (Zoom/Pan/Swipe), `✕` bzw. Esc schließt — PDFs im Vollbild-Viewer
|
||||
- [x] Dateiname zweizeilig unter der Kachel + voller Name als Tooltip
|
||||
|
||||
### 1.2 Auswahl bedienbar machen ✅ 1.5.0
|
||||
- [x] Checkbox als Overlay auf der Kachel (großes Klickziel), Klick auf die Namenszeile wählt ebenfalls aus
|
||||
- [x] Auswahl-Reihenfolge sichtbar (① ② ③) — bestimmt die Slot-Reihenfolge im Grid-Layout
|
||||
(vorher: DOM-Reihenfolge, also nicht steuerbar)
|
||||
- [x] „Alle / Keine" pro Herkunfts-Gruppe + Zähler „3 ausgewählt"
|
||||
- [x] Bereits im Bericht verwendete Anhänge werden markiert (✓-Badge, ausgegraut)
|
||||
|
||||
### 1.3 Sortierung und Filter ✅ 1.5.0
|
||||
- [x] Sortierung nach **Aufnahmezeit** (auf- und absteigend), Name, Größe — jeweils innerhalb der
|
||||
Herkunfts-Gruppe, damit die Zuordnung zum Beleg erhalten bleibt
|
||||
- [x] Filter: alle · nur Bilder · nur PDFs · noch nicht verwendet
|
||||
- [x] Aufnahmezeit steht unter der Kachel (`12.08. 11:48 · 56 Kb`)
|
||||
- [x] EXIF-Parser in PHP (`bericht_jpeg_taken_at`), weil das `exif`-Modul im Container
|
||||
`90-Dolibarr-Prod-Custom` **nicht geladen** ist. Gegen das PHP-Modul geprüft: identische Werte.
|
||||
Fallback auf das Dateidatum, wenn kein EXIF vorhanden ist.
|
||||
- [x] Auswahl von Sortierung/Filter/Ansicht wird gemerkt (localStorage)
|
||||
|
||||
### 1.4 Spaltenbreite, Kachelgröße, Ziehen und Ablegen ✅ 1.5.0
|
||||
- [x] **Spaltenbreite ziehbar** — Trennlinie zwischen Anhängen und Arbeitsfläche (150–620 px),
|
||||
Doppelklick setzt auf 260 px zurück; Breite wird gemerkt
|
||||
- [x] **Ein-/ausklappbar** über „«" in der Kopfzeile — eingeklappt bleiben 26 px stehen
|
||||
- [x] **Kachelgröße** klein / mittel / groß über „⤢" (in der kleinsten Stufe entfällt die Größenangabe)
|
||||
- [x] **Kachel auf die Seitenliste ziehen** → neue Seite(n). Sind mehrere Kacheln angekreuzt und man
|
||||
zieht eine davon, kommen alle mit.
|
||||
- [x] **Kachel auf die Arbeitsfläche ziehen** → ersetzt das Bild der offenen Seite
|
||||
- [x] **Kachel auf einen Raster-Platz ziehen** → setzt genau diesen Platz
|
||||
- [x] **Dateien vom Rechner auf die Spalte ziehen** → Upload (nicht unterstützte Typen werden
|
||||
benannt und übersprungen)
|
||||
- [x] SortableJS bekam `group: {put: false}` — sonst streiten sich Sortierung und Ablegen um die
|
||||
gleichen Ereignisse
|
||||
|
||||
---
|
||||
|
||||
## 2. Seitenansicht ("Arbeitstisch") ✅ 1.5.0
|
||||
|
||||
Vorher zeigte der Editor ein A4-Blatt, auf dem das Foto formatfüllend lag. Beim PDF wurde
|
||||
dieses ganze Blatt **nochmal** in den Satzspiegel eingepasst — der Inhalt schrumpfte also ein
|
||||
zweites Mal, und die Vorschau entsprach nicht dem Ergebnis. Seiten, die nie im Editor
|
||||
gespeichert wurden, liefen zudem über den Kopfbereich (Logo/Titel) hinweg.
|
||||
|
||||
- [x] Eine gemeinsame Geometrie-Quelle: `bericht_page_geometry($format, $orientation, $has_note)`
|
||||
in `lib/bericht.lib.php` — liefert Seitenmaß, Ränder (10 mm), Kopfbereich (30 mm),
|
||||
Fußbereich (16 mm), Notizzone und den Inhaltsbereich. Editor **und** PDF rechnen damit.
|
||||
- [x] Arbeitsfläche = Inhaltsbereich. Drumherum das Blatt mit schraffierter Kopf-/Fußzone
|
||||
(das, was TCPDF selbst druckt) und einer Notizzone, sobald die Seite eine Notiz hat.
|
||||
- [x] PDF setzt das Composite 1:1 in den Inhaltsbereich statt es erneut einzupassen
|
||||
- [x] Alle Layout-Zweige der PDF-Erzeugung (Einzelbild, Raster, Vorher/Nachher, Notiz)
|
||||
auf dieselbe Geometrie umgestellt — vorher rechneten sie mit `margin = 10` ab
|
||||
Blattkante und schoben Bilder unter den Kopfbereich
|
||||
- [x] Raster-Vorschau (`ajax/page_image.php`) rendert im Format des Inhaltsbereichs
|
||||
- [x] Umschalter „📐 Rand" in der Werkzeugleiste (Zustand wird gemerkt)
|
||||
- [x] Rückwärtskompatibel: Composites aus der Zeit davor haben Blatt-Proportion und werden
|
||||
an der Proportion erkannt und proportional eingepasst statt verzerrt
|
||||
|
||||
---
|
||||
|
||||
## 3. Bild einer Seite austauschen ✅ 1.5.0
|
||||
|
||||
- [x] `ajax/replace_page_source.php` — tauscht die Quelle einer Seite; Reihenfolge, Notiz und
|
||||
Anmerkungen bleiben erhalten (vorher: Seite löschen und hinten neu anhängen)
|
||||
- [x] Raster-Seiten: einzelner Platz austauschbar (nutzt das vorhandene `set_slot_image.php`,
|
||||
für das es bis dahin keine Bedienung gab)
|
||||
- [x] Bedienung: „🔄" auf der Seitenminiatur → Statusleiste am unteren Rand → links auf das
|
||||
Ersatzbild klicken. Esc bricht ab. Bei Rastern vorher den Platz (1…n) wählen.
|
||||
- [x] Finalisierte Berichte werden serverseitig abgewiesen
|
||||
|
||||
---
|
||||
|
||||
## 4. Editor / Arbeitsablauf
|
||||
|
||||
- [x] **Kein `location.reload()` mehr** ✅ 1.5.0 — Hinzufügen, Löschen, Hochladen, Bildtausch und
|
||||
der QR-Upload aktualisieren nur noch die betroffenen Listen. Neuer Endpoint
|
||||
`ajax/fragments.php` liefert Seiten- und Anhängeliste als HTML; gerendert wird über
|
||||
`bericht_print_page_list()` / `bericht_print_attachment_list()`, dieselben Funktionen wie beim
|
||||
Seitenaufbau. Der Editor bleibt auf der bearbeiteten Seite, Zoom und Ansicht bleiben erhalten.
|
||||
Einzige Ausnahme: nach dem Finalisieren wird bewusst neu geladen, weil der Bericht den Status
|
||||
wechselt und die ganze Karte anders aussieht.
|
||||
- [x] **Autosave** ✅ 1.5.0 — 4 Sekunden nach der letzten Änderung wird gespeichert, ebenso beim
|
||||
Seitenwechsel und beim Wegschalten des Tabs. Zustand steht in der Fußleiste
|
||||
(„● nicht gespeichert" / „✓ gespeichert"), beim Verlassen der Seite warnt der Browser.
|
||||
Wichtig: das Aufbauen einer Seite (und Zoomen) löst Fabric-Events aus, zählt aber nicht als
|
||||
Änderung — sonst stünde direkt nach dem Öffnen „nicht gespeichert" da.
|
||||
- [x] **Tastenkürzel** ✅ 1.5.0 — V/P/R/K/A/T für die Werkzeuge, Entf löscht die Auswahl (das
|
||||
Hintergrundbild ist ausgenommen), Strg+Z/Y, Strg+S, ↑/↓ blättert durch die Seiten.
|
||||
In Eingabefeldern und bei offenem Dialog oder offener Großansicht greifen sie nicht.
|
||||
Die Kürzel stehen in den Tooltips der Schaltflächen.
|
||||
- [x] **Seitenliste: Mehrfachauswahl** ✅ 1.5.0 — Klick auf die Seitennummer markiert, Umschalt
|
||||
erweitert bis dorthin. Sammelleiste mit Verdoppeln (⧉), Löschen (🗑️) und Aufheben (Esc).
|
||||
Neuer Endpoint `ajax/page_bulk.php`; Kopien landen direkt hinter ihrem Original
|
||||
(`bericht_reorder_with_copies()`), nach dem Löschen wird lückenlos neu nummeriert.
|
||||
`composite_path` wird bewusst **nicht** mitkopiert — sonst zeigten beide Seiten auf dieselbe
|
||||
Datei und das Bearbeiten der einen überschriebe die andere.
|
||||
- [x] **Raster-Plätze direkt anklicken** ✅ 1.5.0 — über der Arbeitsfläche liegt ein Gitter mit
|
||||
einer Schaltfläche je Platz (bei Vorher/Nachher entsprechend beschriftet). Klick setzt das
|
||||
links ausgewählte Bild, Ablegen per Drag setzt das gezogene. Die Aufteilung entspricht
|
||||
`BerichtPage::slotRectsInBox()`. Die Plätze reagieren nur im Auswahlmodus — beim Zeichnen
|
||||
haben die Werkzeuge Vorrang, sonst käme man auf Raster-Seiten nicht mehr zum Zeichnen.
|
||||
- [x] **Seitenwechsel serialisiert** ✅ 1.5.0 — beim schnellen Durchklicken überlappten sich zwei
|
||||
Durchläufe von `loadPage()`: der zweite speicherte die noch nicht umgeschaltete Seite ein
|
||||
zweites Mal, und beide rendern in denselben Canvas — so konnte die Notiz der einen Seite bei
|
||||
der anderen landen. Jeder Aufruf bekommt jetzt eine Nummer und bricht ab, sobald ein neuerer
|
||||
gestartet ist.
|
||||
|
||||
## 5. Kleinkram / Fixes
|
||||
|
||||
- [x] **Mehrfach-Upload**: `bindExtraUpload` verarbeitete nur `inp.files[0]` — Mehrfachauswahl im
|
||||
Dateidialog war möglich, es kam aber nur eine Datei an ✅ 1.5.0
|
||||
- [x] Upload-Fortschritt ✅ 1.5.0 — der Knopf zeigt beim Mehrfach-Upload „⏳ 3 / 8"
|
||||
- [x] `att-delete` löscht die Datei wirklich aus den Dokumenten des Belegs ✅ 1.5.0 — der Dialog
|
||||
sagt das jetzt deutlich und warnt zusätzlich, wenn die Datei im Bericht in Gebrauch ist.
|
||||
|
||||
---
|
||||
|
||||
## Erledigt
|
||||
|
||||
### 1.5.0 — 2026-08-21
|
||||
Vollständig abgearbeitet. Anhänge-Kachelraster mit Miniaturen (1.1–1.4), Server-Thumbnail-Endpoint,
|
||||
PDF-Lightbox, Arbeitstisch mit echter Seitengeometrie (2), Bildaustausch auf bestehenden Seiten (3),
|
||||
Arbeiten ohne Neuladen, Autosave, Tastenkürzel, Sammelaktionen auf Seiten und Raster-Plätze (4).
|
||||
|
||||
Nebenbei gefundene und behobene Fehler:
|
||||
- `dol_dir_list()` wurde ohne `$mode = 1` aufgerufen — dadurch waren `size` und `date` leer
|
||||
und in der Anhängeliste stand „ Bytes" ohne Zahl
|
||||
- PDF.js-Worker-Pfad war hartkodiert ohne `/custom`-Präfix → PDF.js lief im „fake worker"-Modus
|
||||
auf dem Hauptthread (spürbar langsamer beim Rendern)
|
||||
- `document.php` wurde ohne `attachment=0` verlinkt → PDFs wurden heruntergeladen statt angezeigt
|
||||
- Größen-Auswahl der Seite blieb leer, weil der DB-Wert `1` die Option `1.0` nicht traf
|
||||
- Mehrfach-Upload nahm nur die erste Datei
|
||||
- `--inputbackgroundcolor` wurde im Modul als „im AwlDark nicht vorhanden" behandelt; die
|
||||
Variable existiert (KB #512, per DevTools bestätigt) — Editor-Eingabefelder nutzen jetzt
|
||||
dieselben Farben wie Dolibarr selbst
|
||||
|
|
@ -482,7 +482,7 @@ function bericht_collect_attachments(DoliDB $db, CommonObject $parent, $element_
|
|||
if ($dir_key && !empty($conf->{$dir_key}->multidir_output[$parent->entity])) {
|
||||
$upload_dir = $conf->{$dir_key}->multidir_output[$parent->entity].'/'.dol_sanitizeFileName($parent->ref);
|
||||
if (is_dir($upload_dir)) {
|
||||
$files = dol_dir_list($upload_dir, 'files', 1, '', '(\.meta|_preview.*\.png|thumbs)$', 'name', SORT_ASC, 1);
|
||||
$files = dol_dir_list($upload_dir, 'files', 1, '', '(\.meta|_preview.*\.png|thumbs)$', 'name', SORT_ASC, 1, 1);
|
||||
foreach ($files as $f) {
|
||||
$result[] = array(
|
||||
'source' => $element_type,
|
||||
|
|
@ -509,7 +509,7 @@ function bericht_collect_attachments(DoliDB $db, CommonObject $parent, $element_
|
|||
if (empty($conf->{$linked_dolibarr_type}->multidir_output[$lobj->entity])) continue;
|
||||
$linked_dir = $conf->{$linked_dolibarr_type}->multidir_output[$lobj->entity].'/'.dol_sanitizeFileName($lobj->ref);
|
||||
if (!is_dir($linked_dir)) continue;
|
||||
$files = dol_dir_list($linked_dir, 'files', 1, '', '(\.meta|_preview.*\.png|thumbs)$', 'name', SORT_ASC, 1);
|
||||
$files = dol_dir_list($linked_dir, 'files', 1, '', '(\.meta|_preview.*\.png|thumbs)$', 'name', SORT_ASC, 1, 1);
|
||||
foreach ($files as $f) {
|
||||
$result[] = array(
|
||||
'source' => $linked_module_type,
|
||||
|
|
|
|||
Loading…
Reference in a new issue