1031 lines
39 KiB
Markdown
1031 lines
39 KiB
Markdown
# 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
|
||
<properties>
|
||
<revision>0.0.1-SNAPSHOT</revision>
|
||
</properties>
|
||
<version>${revision}</version>
|
||
```
|
||
|
||
In allen **Kind-Modulen** (nur die `<parent>`-Referenz):
|
||
|
||
```xml
|
||
<parent>
|
||
<version>${revision}</version>
|
||
</parent>
|
||
```
|
||
|
||
**Wichtig:** Interne Modul-Abhängigkeiten zwischen Kind-Modulen verwenden
|
||
`${project.version}`, **nicht** `${revision}`:
|
||
|
||
```xml
|
||
<!-- korrekt für interne Modul-Abhängigkeiten: -->
|
||
<dependency>
|
||
<artifactId>pdf-umbenenner-domain</artifactId>
|
||
<version>${project.version}</version>
|
||
</dependency>
|
||
```
|
||
|
||
`flatten-maven-plugin` wird im **Parent-POM unter `<build><plugins>`** aktiviert
|
||
(nicht nur unter `<pluginManagement>` – sonst wird es nicht ausgeführt):
|
||
|
||
```xml
|
||
<build>
|
||
<plugins>
|
||
<plugin>
|
||
<groupId>org.codehaus.mojo</groupId>
|
||
<artifactId>flatten-maven-plugin</artifactId>
|
||
<version>1.6.0</version>
|
||
<configuration>
|
||
<updatePomFile>true</updatePomFile>
|
||
<flattenMode>resolveCiFriendliesOnly</flattenMode>
|
||
</configuration>
|
||
<executions>
|
||
<execution>
|
||
<id>flatten</id>
|
||
<phase>process-resources</phase>
|
||
<goals><goal>flatten</goal></goals>
|
||
</execution>
|
||
<execution>
|
||
<id>flatten.clean</id>
|
||
<phase>clean</phase>
|
||
<goals><goal>clean</goal></goals>
|
||
</execution>
|
||
</executions>
|
||
</plugin>
|
||
</plugins>
|
||
</build>
|
||
```
|
||
|
||
#### 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
|
||
<manifestEntries>
|
||
<Implementation-Version>${revision}</Implementation-Version>
|
||
<Implementation-Title>PDF KI Renamer</Implementation-Title>
|
||
</manifestEntries>
|
||
```
|
||
|
||
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
|
||
<appVersion>${revision}</appVersion>
|
||
```
|
||
|
||
---
|
||
|
||
### #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
|
||
<dependency>
|
||
<groupId>org.flywaydb</groupId>
|
||
<artifactId>flyway-core</artifactId>
|
||
<!-- Version zentral im Parent-POM pinnen -->
|
||
</dependency>
|
||
```
|
||
|
||
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:<pfad-zur-datei>`.
|
||
|
||
#### 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-<version>.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: `<appVersion>${revision}</appVersion>`
|
||
|
||
#### 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
|
||
|
||
```
|
||
<InstallDir>\
|
||
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 `<build><plugins>` 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
|