# V3.0 – Infrastruktur, Transparenz und Bedienkomfort **Status:** Freigegeben zur Implementierung **Erstellt:** 2026-04-29 **Überarbeitet:** 2026-04-30 (nach ChatGPT-Review Runden 1, 2 und 3) **Autor:** Marcus (mit Claude als Mentor) --- ## Ziel V3.0 ist kein Wechsel der Kernfunktion, sondern ein gezielter Qualitätssprung in drei Dimensionen: 1. **Infrastruktur** – saubere Versionierung, Flyway-DB-Migration, Jenkins-Optimierung und MSI-Stabilisierung schaffen ein solides technisches Fundament für alle weiteren Ausbaustufen. 2. **Transparenz** – der Historien-Tab macht die SQLite-Datenbank sichtbar; Fehlerstatus werden benutzerfreundlich erklärt; ein Summary-Banner liefert nach jedem Lauf sofort den Überblick. 3. **Bedienkomfort** – Tooltips, Statuszeile, Prompt-Editor und die Klärung der Fehlerstatus-Semantik reduzieren den täglichen Reibungsverlust spürbar. --- ## Einordnung V2.9 ist der abgeschlossene Ausgangspunkt. Die fachliche Kernverarbeitung (PDF lesen → KI benennen → Zieldatei kopieren) bleibt in V3.0 **vollständig unverändert**. Hexagonale Architektur, Modulstruktur, headless-Betrieb und `.properties`-Konfigurationswahrheit bleiben unangetastet. V3.0 fügt **kein neues Maven-Modul** hinzu. --- ## Scope ### In V3.0 enthalten | # | Thema | Kategorie | |---|---|---| | #49 | Flyway für SQLite-DB-Migration | Infrastruktur | | #65 | MSI-Installer Review & Optimierung | Infrastruktur | | #67 | Konsistente Versionierung (POM / App / Jenkins) | Infrastruktur | | #68 | Jenkins-Build-Optimierung | Infrastruktur | | #51 | FAILED_RETRYABLE vs. FAILED_FINAL klären | Fachlich/UX | | #7 | Historien-Tab (SQLite-DB-Ansicht) | GUI | | #50 | Statuszeile | GUI | | #66 | Tooltips | UX | | #71 | Prompt-Editor in der GUI | GUI | | #73 | Lauf-Statistik / Summary-Banner | GUI | ### Explizit nicht in V3.0 - Automatischer Scheduler / Quellordner-Überwachung (#22) → V3.x - Token- und Kosten-Tracking (#74) → V3.x - Excel-Export (#75) → V3.x - Automatische Update-Prüfung (#76) → V3.x - Dark Mode (#70) → V3.x - Log-Viewer (#72) → V3.x - PDF-Viewer Render-DPI (#23) → V3.x - Zoom per Mausrad (#32) → V3.x - Hilfe-Datei F1 (#69) → V3.x - Änderung der fachlichen Kernverarbeitung - Neue Maven-Module, neue KI-Provider, Architekturbrüche --- ## Unverrückbare Leitplanken (unverändert gegenüber V2.x) - Java 21, Maven Multi-Module, hexagonale Architektur - Shade-JAR als primäres Distributionsartefakt - GUI ist Standardstart, `--headless` bleibt vollständig erhalten - `.properties` bleibt die einzige Konfigurationswahrheit - Kein Webserver, kein Applikationsserver - GUI offiziell nur unter Windows; headless für Windows Server / Task Scheduler - JavaFX-Threading: I/O auf Worker-Thread, UI-Updates via `Platform.runLater()` - Kein JavaFX in Domain oder Application - JavaDoc-Standard für alle neuen öffentlichen Ports, Use-Cases, DTOs und Adapter-Methoden - Notwendige Code-Kommentare auf Deutsch; Logging auf Deutsch --- ## Verbindliche Status-Mapping-Tabelle Diese Tabelle ist die einzige autoritative Quelle für Status-Darstellung in der GUI. **Die Statusnamen wurden gegen den Domain-Enum `ProcessingStatus` und die SQLite-Persistierung geprüft und entsprechen 1:1 dem Code.** Persistierung via `.name()` – gespeicherter String = Enum-Konstantenname. | Domain-Status (`ProcessingStatus`) | GUI-Icon | Farbe | GUI-Text (Tooltip) | Summary-Kategorie | |---|---|---|---|---| | `SUCCESS` | `✓` | Grün | „Erfolgreich verarbeitet und umbenannt." | erfolgreich | | `FAILED_RETRYABLE` | `↻` | Orange | „Temporärer Fehler – wird beim nächsten Lauf automatisch erneut versucht." | wird wiederholt | | `FAILED_FINAL` | `×` | Rot | „Dauerhaft nicht verarbeitbar – z. B. kein Textinhalt (Foto-PDF), Passwortschutz oder beschädigte Datei. Kein weiterer automatischer Versuch." | fehlgeschlagen | | `SKIPPED_ALREADY_PROCESSED` | `≡` | Grau | „Übersprungen – wurde bereits in einem früheren Lauf erfolgreich verarbeitet." | übersprungen | | `SKIPPED_FINAL_FAILURE` | `⊘` | Dunkelgrau | „Endgültig übersprungen nach wiederholten Fehlern." | endgültig übersprungen | | `READY_FOR_AI` | `⟳` | Blau | „Wartet auf Verarbeitung." | – | | `PROPOSAL_READY` | `◇` | Hellblau | „KI-Vorschlag liegt vor, wartet auf Bestätigung." | – | | `PROCESSING` | `▶` | Hellgrau | „Wird gerade verarbeitet." | – | **Wichtig:** Farbe ist niemals das einzige Unterscheidungsmerkmal. Icon und Tooltip-Text müssen den Status allein eindeutig beschreiben. **`PROCESSING` wird nicht als unvollständiger `processing_attempt` persistiert.** `processing_attempt` wird erst nach vollständigem Abschluss eines Versuchs geschrieben – `ended_at NOT NULL` bleibt daher immer erfüllbar. `PROCESSING` tritt ausschließlich in `document_record.overall_status` während eines laufenden Verarbeitungslaufs auf. --- ## Infrastruktur-Features ### #67 – Konsistente Versionierung #### Problem - Alle Maven-POMs: dauerhaft `0.0.1-SNAPSHOT` - Anwendungsversion (`2.5.0`): manuell hardcoded als `app.version` im Packaging-POM - Jenkins: MAJOR/MINOR als manuelle Parameter, werden nicht an Maven übergeben #### Lösung: Maven CI-friendly Versioning Im **Parent-POM** wird `${revision}` als zentrale Versions-Property eingeführt: ```xml 0.0.1-SNAPSHOT ${revision} ``` In allen **Kind-Modulen** (nur die ``-Referenz): ```xml ${revision} ``` **Wichtig:** Interne Modul-Abhängigkeiten zwischen Kind-Modulen verwenden `${project.version}`, **nicht** `${revision}`: ```xml pdf-umbenenner-domain ${project.version} ``` `flatten-maven-plugin` wird im **Parent-POM unter ``** aktiviert (nicht nur unter `` – sonst wird es nicht ausgeführt): ```xml org.codehaus.mojo flatten-maven-plugin 1.6.0 true resolveCiFriendliesOnly flatten process-resources flatten flatten.clean clean clean ``` #### Versionsnummer-Schema ``` MAJOR.MINOR.BUILD_NUMBER (Beispiel: 3.0.42) ``` - MAJOR/MINOR: manuell per Jenkins-Parameter (wie bisher) - BUILD_NUMBER: automatisch durch Jenkins - Jenkins übergibt: `-Drevision=${EFFECTIVE_MAJOR}.${EFFECTIVE_MINOR}.${BUILD_NUMBER}` #### Verifikation Nach `mvn clean install -Drevision=3.0.1` muss geprüft werden: - Kein installiertes POM in `~/.m2` enthält noch `${revision}` unaufgelöst - `java -jar pdf-ki-renamer-3.0.1.jar` zeigt in der Statuszeile exakt `V3.0.1` #### MANIFEST.MF Das Shade-Plugin im Bootstrap-Modul befüllt `Implementation-Version`. Es muss sichergestellt werden, dass der Manifest-Transformer die Einträge korrekt in das finale Fat-JAR überträgt: ```xml ${revision} PDF KI Renamer ``` Laufzeit-Auslesen mit Fallback: ```java String version = getClass().getPackage().getImplementationVersion(); if (version == null) { version = "dev"; // Fallback für IDE-Start und ungepackten Betrieb } ``` #### `pdf-umbenenner-packaging` ```xml ${revision} ``` --- ### #68 – Jenkins-Build-Optimierung #### Aktueller Stand - `mvn clean verify` ohne `-Drevision` → POM bleibt `0.0.1-SNAPSHOT` - JAR-Archivierung fragil - MSI-Build nicht im CI (Linux-Container kann kein Windows-MSI bauen) #### Verbesserungen **1. Versionsübergabe an Maven:** ```groovy stage('Maven Build') { steps { sh "mvn clean verify -Drevision=${env.EFFECTIVE_MAJOR}.${env.EFFECTIVE_MINOR}.${env.BUILD_NUMBER}" } } ``` **2. Robuste Shade-JAR-Archivierung (exakt ein JAR erzwingen, Bash explizit):** ```groovy stage('Archive JAR') { steps { sh '''#!/usr/bin/env bash set -euo pipefail mapfile -t JARS < <(find pdf-umbenenner-bootstrap/target \ -maxdepth 1 -name "pdf-umbenenner-bootstrap-*.jar" \ ! -name "*-sources.jar" ! -name "*-javadoc.jar") test "${#JARS[@]}" -eq 1 \ || { echo "FEHLER: Erwartet genau 1 Shade-JAR, gefunden: ${#JARS[@]}"; exit 1; } JAR_NAME="pdf-ki-renamer-${EFFECTIVE_MAJOR}.${EFFECTIVE_MINOR}.${BUILD_NUMBER}.jar" cp "${JARS[0]}" "$JAR_NAME" ''' archiveArtifacts artifacts: 'pdf-ki-renamer-*.jar', fingerprint: true } } ``` **3. MSI als manuell dokumentieren:** Im Jenkinsfile-Kommentar und in `betrieb.md`: > MSI-Build ist Windows-only (`jpackage + WiX Toolset 3.x`). Jenkins läuft im > Linux-Container auf Synology NAS und kann kein MSI erzeugen. Der MSI-Build > wird bewusst manuell auf der Windows-Entwicklungsmaschine ausgeführt: > `.\mvnw.cmd clean package -P release -pl pdf-umbenenner-packaging --also-make -DskipTests` #### Unverändert MAJOR/MINOR-Persistenz in State-Datei, PIT/JaCoCo/JUnit-Berichte, Artefakt-Ablage nach `/builds/`. --- ### #49 – Flyway für SQLite-DB-Migration #### Motivation Die manuelle `evolveTableColumns()`-Logik via `DatabaseMetaData.getColumns()` ist schwer nachvollziehbar und fehleranfällig. Flyway ersetzt sie durch versionierte, reproduzierbare Migrationsskripte. #### Abhängigkeiten ```xml org.flywaydb flyway-core ``` Der SQLite-JDBC-Treiber (`org.xerial:sqlite-jdbc`) ist bereits im Projekt vorhanden und bleibt unverändert. Flyway nutzt ihn über die DataSource. URL-Format: `jdbc:sqlite:`. #### Migrationsstrategie: Ein einziges Basisskript `V1__initial_schema.sql` enthält das **vollständige Zielschema** aller Spalten und Constraints. Es gibt keine V2 oder V3. Für neue DBs läuft V1 vollständig. Für bestehende V2.9-DBs wird nach vollständiger Schema-Prüfung auf V1 baselined. Ablage: `pdf-umbenenner-adapter-out/src/main/resources/db/migration/` **`V1__initial_schema.sql`:** ```sql CREATE TABLE document_record ( id INTEGER PRIMARY KEY AUTOINCREMENT, fingerprint TEXT NOT NULL, last_known_source_locator TEXT NOT NULL, last_known_source_file_name TEXT NOT NULL, overall_status TEXT NOT NULL, content_error_count INTEGER NOT NULL DEFAULT 0, transient_error_count INTEGER NOT NULL DEFAULT 0, last_failure_instant TEXT, last_success_instant TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, last_target_path TEXT, last_target_file_name TEXT, CONSTRAINT uq_document_record_fingerprint UNIQUE (fingerprint) ); CREATE TABLE processing_attempt ( id INTEGER PRIMARY KEY AUTOINCREMENT, fingerprint TEXT NOT NULL, run_id TEXT NOT NULL, attempt_number INTEGER NOT NULL, started_at TEXT NOT NULL, ended_at TEXT NOT NULL, status TEXT NOT NULL, failure_class TEXT, failure_message TEXT, retryable INTEGER NOT NULL DEFAULT 0, model_name TEXT, prompt_identifier TEXT, processed_page_count INTEGER, sent_character_count INTEGER, ai_raw_response TEXT, ai_reasoning TEXT, resolved_date TEXT, date_source TEXT, validated_title TEXT, final_target_file_name TEXT, ai_provider TEXT, CONSTRAINT fk_processing_attempt_fingerprint FOREIGN KEY (fingerprint) REFERENCES document_record (fingerprint), CONSTRAINT uq_processing_attempt_fingerprint_number UNIQUE (fingerprint, attempt_number) ); CREATE INDEX idx_processing_attempt_fingerprint ON processing_attempt (fingerprint); CREATE INDEX idx_processing_attempt_run_id ON processing_attempt (run_id); CREATE INDEX idx_document_record_overall_status ON document_record (overall_status); ``` #### Differenzierte Startstrategie Die Flyway-Initialisierung in `SqliteSchemaInitializationAdapter` unterscheidet drei Fälle. **`baselineOnMigrate(true)` wird ausschließlich in Fall 2 gesetzt.** ``` Fall 1: DB-Datei existiert nicht, oder existiert aber enthält keine fachlichen Tabellen und keine Flyway-History-Tabelle („leer" = keine Tabellen, nicht nur Dateigröße 0 Byte) → Flyway mit baselineOnMigrate=false → V1__initial_schema.sql wird ausgeführt → vollständige neue DB Fall 2: DB existiert, enthält fachliche Tabellen, aber keine Flyway-History-Tabelle (bestehende V2.9-DB) → Vollständige Schema-Prüfung gegen V1-Zielschema (siehe Checkliste unten) → Bei vollständig konformem Schema: Backup erstellen (datierte Kopie der .sqlite-Datei) Flyway mit baselineOnMigrate=true, baselineVersion="1" migrate() → nur zukünftige Migrationen ab V2 werden ausgeführt → Bei nicht konformem Schema: Start abbrechen mit klarer Fehlermeldung, kein stilles Weiterlaufen Fall 3: DB existiert, Flyway-History-Tabelle vorhanden (regulärer Folgestart) → Flyway mit baselineOnMigrate=false → migrate() (idempotent) ``` #### Vollständige Schema-Prüfcheckliste für Fall 2 Da nach Baseline V1 **nicht mehr ausgeführt wird**, muss die bestehende DB **vollständig** dem V1-Zielschema entsprechen. Geprüft wird via `DatabaseMetaData`: **Tabellen:** `document_record` und `processing_attempt` müssen existieren. **Alle Spalten `document_record`:** `id`, `fingerprint`, `last_known_source_locator`, `last_known_source_file_name`, `overall_status`, `content_error_count`, `transient_error_count`, `last_failure_instant`, `last_success_instant`, `created_at`, `updated_at`, `last_target_path`, `last_target_file_name` **Alle Spalten `processing_attempt`:** `id`, `fingerprint`, `run_id`, `attempt_number`, `started_at`, `ended_at`, `status`, `failure_class`, `failure_message`, `retryable`, `model_name`, `prompt_identifier`, `processed_page_count`, `sent_character_count`, `ai_raw_response`, `ai_reasoning`, `resolved_date`, `date_source`, `validated_title`, `final_target_file_name`, `ai_provider` **Constraints (soweit per Metadata prüfbar):** - `UNIQUE` auf `document_record.fingerprint` - `UNIQUE` auf `(processing_attempt.fingerprint, processing_attempt.attempt_number)` - Foreign Key von `processing_attempt.fingerprint` auf `document_record.fingerprint` **Indizes:** `idx_processing_attempt_fingerprint`, `idx_processing_attempt_run_id`, `idx_document_record_overall_status` Fehlt ein Element: Start mit Fehlermeldung abbrechen. #### PRAGMA foreign_keys – Connection-sicheres Setzen SQLite setzt `PRAGMA foreign_keys` pro Connection, nicht global. Konfiguration via DataSource-Initializer: ```java SQLiteConfig config = new SQLiteConfig(); config.enforceForeignKeys(true); SQLiteDataSource ds = new SQLiteDataSource(config); ``` Ein einmaliges Statement vor dem Flyway-Aufruf ist **nicht ausreichend**. #### Gleichzeitige Starts / Concurrency Flyway unterstützt bei SQLite keine gleichzeitigen Migrationen. Parallele Starts werden über einen einfachen Lock-Mechanismus erkannt (z. B. Lock-Datei neben der SQLite-Datei oder OS-Dateilock). Bei aktivem Lock bricht der zweite Start mit einer verständlichen Meldung ab („Anwendung läuft bereits"). Korruption der DB ist in keinem Fall akzeptabel. #### Architektur - Integration in `SqliteSchemaInitializationAdapter.initializeSchema()` - `PersistenceSchemaInitializationPort`-Vertrag bleibt unverändert - `evolveTableColumns()` wird vollständig entfernt --- ### #65 – MSI-Installer Review & Optimierung #### Drei kritische Baustellen (vor Release zwingend) **1. Icon-Platzhalter ersetzen** `src/main/packaging/icon.ico` ist ein 1×1-Pixel-Platzhalter. Ersetzen durch echtes Anwendungsicon (`.ico`, mindestens 256×256 px). **2. `addModules`-Liste aktualisieren** Die Java-Modulliste ist veraltet. Neue Bibliotheken (PDFViewFX, jai-imageio-jpeg2000, PDFBox) müssen erfasst werden: ``` jdeps --print-module-deps --ignore-missing-deps \ pdf-umbenenner-bootstrap-.jar ``` Das Ergebnis muss **manuell plausibilisiert** werden: Nach dem MSI-Build wird die installierte Anwendung **ohne Entwicklungs-JDK** gestartet und folgende Szenarien durchlaufen: - GUI-Start - PDF laden und rendern - Verarbeitungslauf mit PDFBox-Textextraktion - Historien-Tab öffnen Nur eine erfolgreiche Laufzeit-Prüfung ohne JDK bestätigt die Modulliste. **3. `app.version` ersetzen** Nach #67: `${revision}` #### Weitere Punkte - `winUpgradeUuid` mit stabilem GUID einmalig generieren und dauerhaft pflegen - Desktop-Shortcut verifizieren (`winShortcut=true` + `winShortcutPrompt=false`) - MSI-Build bleibt bewusst manuell (Linux-CI kann kein MSI bauen) #### Installationsstruktur nach MSI-Installation ``` \ PDF-KI-Renamer.bat (headless-Start via Task Scheduler) PDF-KI-Renamer-GUI.bat (GUI-Start) PDF KI Renamer\ PDF-KI-Renamer.exe application.example.properties (alle weiteren App-Dateien) ``` Beide BAT-Dateien sind Bestandteil des MSI-Installers und referenzieren `%~dp0PDF KI Renamer\PDF-KI-Renamer.exe`. Sie müssen auch bei **Installationspfaden mit Leerzeichen** korrekt funktionieren. #### Hinweis zu Prompt- und Konfigurationspfaden unter MSI In `betrieb.md` ist folgender Hinweis aufzunehmen: > Für den MSI-Betrieb (Startmenü, Task Scheduler) wird **empfohlen**, > `prompt.template.file` und `sqlite.path` als **absolute Pfade** zu konfigurieren > oder auf `%APPDATA%`/`%ProgramData%`-Verzeichnisse zu zeigen. > Relative Pfade beziehen sich auf das Arbeitsverzeichnis, das je nach Startart > variiert (siehe Tabelle in #71). #### Testmatrix - [ ] Neuinstallation auf sauberer Windows-Umgebung ohne vorinstalliertes Java - [ ] Installation in Pfad **mit Leerzeichen** - [ ] Upgrade von installiertem V2.x-MSI auf V3.0-MSI (kein manuelles Deinstallieren) - [ ] GUI-Start über Startmenü-Eintrag - [ ] Headless-Start über `PDF-KI-Renamer.bat` im Task Scheduler - [ ] Desktop-Shortcut vorhanden oder Einschränkung in `betrieb.md` dokumentiert - [ ] App-Version `3.0.x` im Windows-Installer sichtbar („Programme und Features") - [ ] Deinstallation sauber – ProgramData-Konfiguration bleibt erhalten - [ ] SmartScreen-Warnung erwartet und in `betrieb.md` dokumentiert - [ ] BAT-Dateien funktionieren bei Installationspfad mit Leerzeichen - [ ] Anwendungsstart ohne Entwicklungs-JDK erfolgreich (Modullisten-Verifikation) --- ## Fachliche Features ### #51 – FAILED_RETRYABLE vs. FAILED_FINAL Semantik und GUI-Darstellung gemäß verbindlicher Status-Mapping-Tabelle. Die interne Status-Unterscheidung bleibt vollständig erhalten. Kein Umbau an Domain oder Persistenz. #### Detailbereich bei FAILED_FINAL > „Diese Datei kann nicht verarbeitet werden. Mögliche Ursachen: > kein lesbarer Text (z. B. gescanntes Foto ohne OCR), Passwortschutz > oder beschädigte Datei. > Sie können den Status manuell zurücksetzen, wenn Sie die Ursache > behoben haben." „Erneut verarbeiten" und „Status zurücksetzen" bleiben für beide Fehlerstatus verfügbar. --- ## GUI-Features ### #7 – Historien-Tab #### Ziel Neuer Tab „Verlauf" zeigt alle jemals verarbeiteten Dokumente mit Status und Verarbeitungsdetails aus der SQLite-Datenbank. #### Layout ``` [ Konfiguration | Verarbeitungslauf | Verlauf | Prompt ] [ Suchfeld / Filter ] [ Status-Filter ▾ ] [ Aktualisieren ] [ Dokumentenliste (~55%) | Detailbereich (~45%) ] [ | Dokument-Info ] [ | Versuche-Tabelle ] [ | KI-Begründung ] [ Statuszeile ] ``` #### Dokumentenliste (linke Tabelle) | Spalte | Quelle | |---|---| | Status-Icon | `document_record.overall_status` | | Quelldateiname | `document_record.last_known_source_file_name` | | Zieldateiname | `document_record.last_target_file_name` | | Quellpfad | `document_record.last_known_source_locator` | | Letzter Versuch | `document_record.updated_at` | | Anzahl Versuche | `COUNT(processing_attempt)` | **Sortierung:** Standard absteigend nach `updated_at`, Tie-Breaker: `fingerprint ASC` (stabil und reproduzierbar). **Initiales Limit (kein echtes Paging in V3.0):** - `LIMIT 501` in der Query – 501 um „mehr vorhanden" sauber zu erkennen - Bei mehr als 500 Treffern: „Weitere Einträge vorhanden – Filter verwenden" - Bei leerer DB: „Noch keine Verarbeitungen vorhanden." - Lange Dateinamen/Pfade: Ellipsis in der Tabelle, Tooltip zeigt vollen Text **Filter (DB-seitig, kein GUI-seitiger Vollscan):** - Freitextsuche über Quelldateiname und Zieldateiname - Case-insensitiv (`COLLATE NOCASE` oder `LOWER()`) - Sonderzeichen `%` und `_` in der Sucheingabe werden escaped - Status-ComboBox: alle / einzelner Status **`READY_FOR_AI`, `PROPOSAL_READY` und `PROCESSING` werden im Summary-Banner nicht gezählt** – das Banner erscheint nach Laufabschluss und zeigt ausschließlich abgeschlossene Ergebnisse. #### Detailbereich (rechte Seite) **Dokument-Info:** Fingerprint (12 Zeichen), Quelldateiname, Quellpfad, Status (Icon + Text), Erstellt/Aktualisiert. **Versuche-Tabelle:** | Spalte | Quelle | |---|---| | # | `processing_attempt.attempt_number` | | Datum | `processing_attempt.ended_at` | | Status | `processing_attempt.status` | | Provider | `processing_attempt.ai_provider` | | Modell | `processing_attempt.model_name` | | Vorgeschlagener Name | `processing_attempt.final_target_file_name` | **KI-Begründung:** `processing_attempt.ai_reasoning` als nicht editierbare TextArea. #### Aktionen | Aktion | Beschreibung | |---|---| | **Status zurücksetzen** | Setzt definierte Felder zurück (siehe unten). **Versuche bleiben vollständig erhalten.** Bestätigungsdialog. | | **Eintrag löschen** | Löscht Stammsatz und alle Versuche vollständig (destruktiv, nicht rückgängig). Bestätigungsdialog mit explizitem Hinweis. | **`DefaultResetDocumentStatusUseCase` – feldgenaue Definition:** Folgende Felder werden aktualisiert: | Feld | Neuer Wert | |---|---| | `overall_status` | `READY_FOR_AI` | | `content_error_count` | `0` | | `transient_error_count` | `0` | | `last_failure_instant` | `null` | Folgende Felder werden **nicht** geändert: | Feld | Begründung | |---|---| | `created_at` | Historisches Datum, immer unverändert | | `last_success_instant` | Historische Information, bleibt erhalten | | `last_target_path` / `last_target_file_name` | Historische Information, bleibt erhalten | | `processing_attempt`-Einträge | Verlaufshistorie, bleibt vollständig erhalten | Nach dem Reset muss das Dokument beim nächsten Lauf tatsächlich als verarbeitbar gelten. `READY_FOR_AI` ist der einzige Trigger für die Verarbeitungslogik. Alte Versuche dürfen die Wiederverarbeitung nicht blockieren. **`DefaultDeleteDocumentHistoryUseCase` – Löschreihenfolge:** Da der Foreign Key von `processing_attempt.fingerprint` auf `document_record.fingerprint` **ohne `ON DELETE CASCADE`** definiert ist und `PRAGMA foreign_keys` aktiv ist, muss innerhalb einer Transaktion in folgender Reihenfolge gelöscht werden: 1. Alle `processing_attempt`-Einträge zum Fingerprint 2. Den `document_record`-Stammsatz zum Fingerprint **Sperren während aktivem Verarbeitungslauf:** Beide Aktionen sind während eines laufenden Verarbeitungslaufs **deaktiviert**. Hinweis: „Aktion während Verarbeitungslauf nicht möglich." #### Architektur Das bestehende Muster des Projekts: ``` GUI-Adapter → Gui...Port (Bridge-Interface in adapter-in-gui) ↑ implementiert von Bootstrap Default...UseCase (in application) ↓ Outbound-Ports → Adapter ``` Neue Komponenten: | Komponente | Typ | Modul | |---|---|---| | `GuiHistoryOverviewPort` | Bridge-Interface | `adapter-in-gui` | | `GuiHistoryDetailsPort` | Bridge-Interface | `adapter-in-gui` | | `GuiResetDocumentStatusPort` | Bridge-Interface | `adapter-in-gui` | | `GuiDeleteDocumentHistoryPort` | Bridge-Interface | `adapter-in-gui` | | `DefaultHistoryOverviewUseCase` | Use-Case-Impl. | `application` | | `DefaultHistoryDetailsUseCase` | Use-Case-Impl. | `application` | | `DefaultResetDocumentStatusUseCase` | Use-Case-Impl. | `application` | | `DefaultDeleteDocumentHistoryUseCase` | Use-Case-Impl. | `application` | Der GUI-Adapter kennt ausschließlich `Gui...Port`-Interfaces – niemals direkt `DocumentRecordRepository` oder `ProcessingAttemptRepository`. **Threading:** Lesende DB-Zugriffe auf Worker-Thread, UI via `Platform.runLater()`. Kein Echtzeit-Tailing – manueller „Aktualisieren"-Button. --- ### #50 – Statuszeile Permanente Statuszeile am unteren Rand der Anwendung (unterhalb aller Tabs): ``` [ V3.0.42 ] [ Provider: Claude · claude-opus-4-7 (Beispiel) ] [ config/application.properties ] ``` | Segment | Inhalt | Quelle | |---|---|---| | Links | `V{version}` | `MANIFEST.MF` via `getImplementationVersion()`, Fallback: `dev` | | Mitte | Aktiver Provider und Modell | Geladene Konfiguration | | Rechts | Konfigurationsdateipfad | Aktueller Ladezustand | - Permanent sichtbar auf allen Tabs - Aktualisierung bei Laden/Speichern neuer Konfiguration - Kein Profil geladen: „Kein Profil geladen" - Version immer sichtbar, auch ohne geladene Konfiguration - Voraussetzung: #67 muss umgesetzt sein --- ### #66 – Tooltips JavaFX `Tooltip` auf allen Buttons, Feldern und Status-Icons. Farbe ist niemals das einzige Unterscheidungsmerkmal. Bei der Umsetzung wird eine UI-Checkliste pro Tab geführt. **Konfigurationstab:** | Element | Tooltip | |---|---| | Quellordner | „Ordner mit den zu verarbeitenden PDF-Dateien. Inhalt wird nicht verändert." | | Zielordner | „Ordner für die umbenannten Kopien." | | SQLite-Datei | „Datenbank für Verarbeitungsergebnisse und Datei-Historie." | | Prompt-Datei | „Externe Textdatei mit den KI-Anweisungen." | | Provider-ComboBox | „Der KI-Dienst, der die Dateinamen generiert." | | Modell-Feld | „Das konkrete Sprachmodell des gewählten Providers." | | max.text.characters | „Maximale Zeichenzahl aus dem PDF-Text. Höhere Werte = mehr Kontext, höhere Kosten." | | max.pages | „Maximale Seitenzahl, die aus einem PDF gelesen wird." | | max.title.length | „Maximale Länge des Dateinamens in Zeichen (ohne Datum und Erweiterung). Gültig: 10–120." | **Tab „Verarbeitungslauf":** Alle Status-Icons gemäß Status-Mapping-Tabelle. | Element | Tooltip | |---|---| | „Dateiname übernehmen" | „Benennt die Zieldatei um und aktualisiert die Datenbank. Nicht rückgängig zu machen." | | „Zurücksetzen auf KI-Vorschlag" | „Stellt den KI-generierten Namen wieder her, ohne zu speichern." | **Toolbar:** | Button | Tooltip | |---|---| | Neu | „Neue Konfiguration erstellen." | | Öffnen | „Bestehende Konfigurationsdatei (.properties) öffnen." | | Speichern | „Aktuelle Konfiguration speichern." | | Speichern unter | „Konfiguration unter neuem Dateipfad speichern." | | Validieren | „Aktuelle Eingaben auf Vollständigkeit und Korrektheit prüfen." | | Technische Tests | „Dateipfade, Datenbankverbindung und KI-Erreichbarkeit prüfen." | Technisch: `element.setTooltip(new Tooltip("Text"))` – rein im Modul `pdf-umbenenner-adapter-in-gui`. --- ### #71 – Prompt-Editor in der GUI #### Ziel Eigener Tab „Prompt" zum Lesen, Bearbeiten und Speichern der KI-Prompt-Datei – kein externer Editor notwendig. #### Bestehende Infrastruktur | Komponente | Stand | |---|---| | `PromptPort.loadPrompt()` | ✅ existiert | | `ResourceCreationPort.createPromptFile()` | ✅ existiert (nur Default-Template) | | Port zum Speichern beliebigen Inhalts | ❌ fehlt – neu erstellen | #### Neuer Port `PromptPort` wird um eine Schreibmethode erweitert: ```java PromptSaveResult savePrompt(String content); ``` `savePrompt(String content)` speichert immer in die aktuell über die geladene Konfiguration aufgelöste Prompt-Datei. Der Pfad wird im Use-Case aus der Konfiguration bestimmt und dem Adapter intern bereitgestellt – er ist **nicht** Teil der Port-Signatur. Implementierung: `FilesystemPromptPortAdapter` im Modul `pdf-umbenenner-adapter-out`. #### Architektur | Komponente | Typ | Modul | |---|---|---| | `GuiPromptEditorPort` | Bridge-Interface | `adapter-in-gui` | | `DefaultPromptEditorUseCase` | Use-Case-Impl. | `application` | Der GUI-Adapter greift **nicht** direkt auf `PromptPort` oder das Dateisystem zu. #### Verhalten - **Tab-Öffnen:** Inhalt der konfigurierten Prompt-Datei laden (Worker-Thread) - **Keine Datei konfiguriert/vorhanden:** Hinweistext + „Standard-Prompt erstellen"-Button (ruft `createPromptFile()` auf) - **Bearbeitung** in `TextArea` - **Dirty State:** Tab-Titel mit Asterisk (`Prompt *`) - **„Speichern":** schreibt atomar (temp-Datei **im selben Verzeichnis** wie Zieldatei → `ATOMIC_MOVE`), Encoding: **UTF-8**, Zeilenenden: unverändert übernommen (keine Normalisierung) - **„Auf Standard zurücksetzen":** befüllt TextArea mit Default, **ohne** zu speichern - **Tab-Wechsel/Schließen mit Dirty State:** Bestätigungsdialog „Änderungen verwerfen?" #### Fehlerfälle | Fehlerfall | Verhalten | |---|---| | Konfigurierter Ordner nicht vorhanden | Fehlermeldung im Tab, kein Absturz | | Datei schreibgeschützt | Fehlermeldung nach Speicherversuch | | Zielordner während Bearbeitung gelöscht | Fehlermeldung beim Speichern | | `ATOMIC_MOVE` schlägt fehl | Speichervorgang abbrechen, Fehlermeldung – kein stiller Fallback auf Teilüberschreibung | | Leerer Prompt beim Speichern | Bestätigungsdialog: „Wirklich leere Prompt-Datei speichern?" | | Extern geänderte Datei | Last-write-wins (kein automatisches Reload) | #### Pfadauflösung (Verhalten beibehalten, dokumentieren) Der Prompt-Pfad wird relativ zum **Arbeitsverzeichnis** aufgelöst – wie bisher. `betrieb.md` muss folgende Tabelle enthalten: | Startart | Arbeitsverzeichnis | |---|---| | IDE | Projektwurzel | | Fat-JAR per Konsole | Aktuelles Terminal-Verzeichnis | | MSI / Startmenü | Installationsverzeichnis | | Task Scheduler headless | Explizit gesetztes „Start in"-Verzeichnis empfohlen | **Empfehlung für MSI-Betrieb** (in `betrieb.md`): Für MSI-Betrieb über Startmenü oder Task Scheduler werden **absolute Pfade** für `prompt.template.file` und `sqlite.path` empfohlen, idealerweise auf `%APPDATA%`- oder `%ProgramData%`-Verzeichnisse. Langfristig (V3.x): Pfadauflösung relativ zur geladenen `.properties`-Datei. #### Threading Laden und Speichern auf Worker-Thread, UI via `Platform.runLater()`. --- ### #73 – Lauf-Statistik / Summary-Banner Banner erscheint nach Laufabschluss, unterhalb des Fortschrittsbalkens, oberhalb der Ergebnistabelle: ``` ✓ 14 erfolgreich · ↻ 1 wird wiederholt · × 2 fehlgeschlagen · ≡ 3 übersprungen · ⊘ 1 endgültig übersprungen ``` - Nur Kategorien mit Anzahl > 0 (inkl. `⊘ endgültig übersprungen` wenn > 0) - `READY_FOR_AI`, `PROPOSAL_READY` und `PROCESSING` werden **nicht gezählt** - Bei vollständig sauberem Lauf: `✓ 17 erfolgreich` - Verschwindet beim Start des nächsten Laufs - Kein Banner bei Anwendungsstart oder Tab-Wechsel ohne Lauf - Icon + Text tragen die Information, nicht nur Farbe - Reine GUI-Logik, kein neuer Port; Aggregation aus bestehender Ergebnisliste --- ## Architektur-Zusammenfassung ### Neue Ports / Use-Cases | Komponente | Modul | Zweck | |---|---|---| | `PromptPort.savePrompt(String)` | `application` | Prompt-Inhalt speichern (#71) | | `DefaultPromptEditorUseCase` | `application` | Prompt-Editor-Logik (#71) | | `DefaultHistoryOverviewUseCase` | `application` | Dokumentenliste Historien-Tab (#7) | | `DefaultHistoryDetailsUseCase` | `application` | Detailansicht Historien-Tab (#7) | | `DefaultResetDocumentStatusUseCase` | `application` | Feldgenauer Status-Reset, Versuche bleiben (#7) | | `DefaultDeleteDocumentHistoryUseCase` | `application` | Transaktionales Löschen in korrekter Reihenfolge (#7) | ### Neue Bridge-Interfaces (adapter-in-gui) | Interface | Zweck | |---|---| | `GuiPromptEditorPort` | Brücke zum Prompt-Editor-Use-Case (#71) | | `GuiHistoryOverviewPort` | Brücke zur Dokumentenliste (#7) | | `GuiHistoryDetailsPort` | Brücke zur Detailansicht (#7) | | `GuiResetDocumentStatusPort` | Brücke zum Status-Reset (#7) | | `GuiDeleteDocumentHistoryPort` | Brücke zum vollständigen Löschen (#7) | ### Geänderte Adapter | Adapter | Modul | Änderung | |---|---|---| | `FilesystemPromptPortAdapter` | `adapter-out` | Neue Methode `savePrompt()` (#71) | | `SqliteSchemaInitializationAdapter` | `adapter-out` | Ersetzt durch Flyway-Integration (#49) | ### Nicht geändert - `pdf-umbenenner-domain` – keine Änderungen - `pdf-umbenenner-adapter-in-cli` – keine Änderungen - `pdf-umbenenner-bootstrap` – nur MANIFEST.MF-Ergänzung (#67) + neue Bridge-Verdrahtung - Headless-Betrieb – vollständig unberührt --- ## Definition of Done (V3.0 gesamt) - [ ] Alle 10 Issues implementiert und einzeln getestet - [ ] `mvn clean verify` grün (alle Module, kein `-DskipTests`) - [ ] `mvn clean install -Drevision=3.0.1` – kein unaufgelöstes `${revision}` in installierten POMs - [ ] `java -jar pdf-ki-renamer-3.0.1.jar` zeigt in Statuszeile exakt `V3.0.1` - [ ] Bestehende V2.9-DB via Flyway-Baseline korrekt übernommen – kein Datenverlust - [ ] Historien-Tab zeigt korrekte Daten aus einer V2.9-DB - [ ] Versionsnummer konsistent: MANIFEST.MF, Statuszeile, MSI und JAR-Dateiname - [ ] Jenkins-Build läuft mit `-Drevision`-Übergabe durch; Bash-Skript schlägt bei 0 oder >1 JAR ab - [ ] MSI-Testmatrix vollständig abgearbeitet inkl. Anwendungsstart ohne JDK - [ ] Tooltips vollständig auf allen spezifizierten Elementen (UI-Checkliste pro Tab) - [ ] Prompt-Editor: alle Szenarien inkl. Fehlerfälle getestet - [ ] `FAILED_RETRYABLE` und `FAILED_FINAL` eindeutig durch Icon, Farbe und Text unterschieden - [ ] „Status zurücksetzen" setzt feldgenau zurück, löscht keine Versuche, Datei wird wieder verarbeitet - [ ] „Eintrag löschen" löscht in korrekter Reihenfolge (Attempts vor Record) - [ ] Destruktive Aktionen während Lauf gesperrt - [ ] Notwendige Code-Kommentare auf Deutsch; Logging auf Deutsch - [ ] JavaDoc auf allen neuen öffentlichen Ports, Use-Cases, DTOs und Adapter-Methoden - [ ] `betrieb.md` enthält Prompt-Pfadauflösungs-Tabelle je Startart + MSI-Absolute-Pfad-Empfehlung - [ ] `betrieb.md` und `gui-bedienanleitung.md` auf V3.0-Stand gebracht - [ ] Freigabedokument `freigabe-v3_0.md` erstellt - [ ] Manueller GUI-Produkttest durchgeführt (Green build ≠ fertige Software) --- ## Abnahmekriterien je Feature ### #49 Flyway - [ ] Neue SQLite-DB vollständig per Flyway aus V1 aufgebaut - [ ] „Leer" korrekt erkannt: keine Tabellen vorhanden (nicht nur Dateigröße 0 Byte) - [ ] Bestehende V2.9-DB: vollständige V1-Schema-Prüfcheckliste durchlaufen - [ ] `baselineOnMigrate=true` ausschließlich in Fall 2 gesetzt - [ ] Start mit nicht konformem Schema bricht mit Fehlermeldung ab - [ ] PRAGMA foreign_keys via DataSource-Initializer (nicht einmaliges Statement) - [ ] `evolveTableColumns()` vollständig entfernt - [ ] Lock-Mechanismus verhindert korrupte DB bei parallelem Start ### #65 MSI - [ ] Echtes Icon (kein 1×1-Platzhalter) - [ ] `addModules`-Liste per `jdeps` aktualisiert und per Laufzeit-Test ohne JDK verifiziert - [ ] Version entspricht V3.0-Stand (`${revision}`) - [ ] `winUpgradeUuid` gesetzt - [ ] Installationsstruktur korrekt: BAT-Dateien neben App-Verzeichnis - [ ] BAT-Dateien funktionieren bei Pfad mit Leerzeichen - [ ] Vollständige Testmatrix abgearbeitet - [ ] `betrieb.md` mit MSI-Hinweisen zu ProgramData, absoluten Pfaden und SmartScreen ### #67 Versionierung - [ ] `${revision}` im Parent-POM eingeführt - [ ] `flatten-maven-plugin` in `` des Parent-POM (nicht nur pluginManagement) - [ ] `flatten-maven-plugin` mit `resolveCiFriendliesOnly` und `flatten:clean` - [ ] Interne Modul-Abhängigkeiten verwenden `${project.version}` - [ ] MANIFEST.MF enthält `Implementation-Version` korrekt im Fat-JAR - [ ] Fallback `"dev"` bei null implementiert - [ ] `app.version` im Packaging-Modul nutzt `${revision}` ### #68 Jenkins - [ ] Maven-Build übergibt `-Drevision=MAJOR.MINOR.BUILD_NUMBER` - [ ] Bash explizit erzwungen (`#!/usr/bin/env bash`, `set -euo pipefail`) - [ ] `mapfile`-Prüfung: Build bricht ab bei 0 oder >1 JAR - [ ] MSI-Build als manuell dokumentiert ### #51 FAILED_RETRYABLE vs. FAILED_FINAL - [ ] Icons (↻ vs. ×), Farben (Orange vs. Rot) und Tooltips eindeutig unterschiedlich - [ ] Detailbereich zeigt erweiterte Erklärung bei `FAILED_FINAL` - [ ] Farbe ist nicht das einzige Unterscheidungsmerkmal ### #7 Historien-Tab - [ ] Tab „Verlauf" sichtbar und navigierbar - [ ] `LIMIT 501`-Technik, Hinweis bei mehr als 500 Treffern - [ ] Hinweistext bei leerer DB - [ ] Sortierung mit stabilem Tie-Breaker - [ ] Suche case-insensitiv, Sonderzeichen escaped - [ ] Detailbereich zeigt Versuche und KI-Begründung - [ ] „Status zurücksetzen" setzt feldgenau zurück (Status, Zähler, last_failure_instant) - [ ] „Status zurücksetzen" löscht keine Versuche; Datei wird beim nächsten Lauf verarbeitet - [ ] „Eintrag löschen": Attempts vor Record, innerhalb Transaktion, mit Bestätigungsdialog - [ ] Beide Aktionen während Lauf deaktiviert ### #50 Statuszeile - [ ] Version aus MANIFEST.MF, Fallback `dev` - [ ] Provider und Modell aus geladener Konfiguration - [ ] Konfigurationspfad korrekt - [ ] Aktualisierung bei Laden/Speichern neuer Konfiguration - [ ] Kein Absturz wenn keine Konfiguration geladen ### #66 Tooltips - [ ] UI-Checkliste pro Tab geführt und abgearbeitet - [ ] Alle spezifizierten Elemente haben Tooltips - [ ] Farbe ist nicht einziges Unterscheidungsmerkmal ### #71 Prompt-Editor - [ ] Laden beim Tab-Öffnen (Worker-Thread) - [ ] Dirty State korrekt angezeigt - [ ] Speichern atomar (temp im selben Verzeichnis + `ATOMIC_MOVE`), Encoding UTF-8 - [ ] Kein stiller Fallback bei fehlgeschlagenem `ATOMIC_MOVE` - [ ] „Auf Standard zurücksetzen" ohne automatisches Speichern - [ ] Bestätigungsdialog bei Tab-Wechsel mit Dirty State - [ ] Alle Fehlerfälle laut Spezifikation getestet - [ ] `betrieb.md` enthält Pfadauflösungs-Tabelle + MSI-Empfehlung für absolute Pfade ### #73 Summary-Banner - [ ] Korrekte Zählwerte nach Lauf inkl. `⊘ endgültig übersprungen` - [ ] `READY_FOR_AI`, `PROPOSAL_READY`, `PROCESSING` werden nicht gezählt - [ ] Nur Kategorien > 0 angezeigt - [ ] Verschwindet beim nächsten Laufstart - [ ] Icon + Text tragen Information, nicht nur Farbe