V2.8: Selektive Wiederverarbeitung und Statusreset in der GUI

- Mehrfachauswahl mit CheckBox-Spalte und Master-Tri-State-Checkbox
- Gezielter Mini-Lauf über ausgewählte Einträge (unabhängig vom Status)
- Statusreset für ausgewählte Einträge (Stammsatz + Versuchshistorie)
- Fehlende Quelldatei im Mini-Lauf wird als FAILED_PERMANENT synthetisiert
- Identische Zieldatei wird als SUCCESS ohne erneute KI-Verarbeitung erkannt
- Weiche Stop-Semantik erhält zurückgesetzte Einträge unverändert
- Nicht-ausgewählte Einträge bleiben in allen Pfaden unberührt
- Buttons reagieren jetzt korrekt auf Auswahländerungen

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-04-23 12:04:22 +02:00
parent f4a1bce9ae
commit 9fd5bd5a52
40 changed files with 3478 additions and 223 deletions
@@ -1,7 +1,10 @@
package de.gecheckt.pdf.umbenenner.domain.model;
import java.time.Instant;
import java.util.Collections;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
/**
* Technical context representing a single batch processing run.
@@ -14,31 +17,99 @@ import java.util.Objects;
* <ul>
* <li>Track the unique identity of the batch run</li>
* <li>Record when the run started (and eventually when it ends)</li>
* <li>Provide run context to persistence, logging, and result tracking (future milestones)</li>
* <li>Carry an optional fingerprint filter for targeted (mini) runs that restrict
* processing to a specific set of documents</li>
* <li>Provide run context to persistence, logging, and result tracking</li>
* </ul>
* <p>
* This context is independent of individual document processing and contains
* no business logic. It is purely a technical container for run identity and timing.
* no business logic. It is purely a technical container for run identity, timing,
* and optional candidate restriction.
* <p>
* When no fingerprint filter is set ({@link #fingerprintFilter()} returns empty),
* the run processes all candidates found in the source folder (regular batch run).
* When a non-empty filter is present, only candidates whose fingerprint is contained
* in that set are processed.
*/
public final class BatchRunContext {
private final RunId runId;
private final Instant startInstant;
private Instant endInstant;
private final Set<DocumentFingerprint> fingerprintFilter;
/**
* Creates a new BatchRunContext with the given run ID and start time.
* Creates a new BatchRunContext for a regular (unfiltered) batch run.
* <p>
* No fingerprint filter is applied; all candidates from the source folder are
* eligible for processing.
* <p>
* The end instant is initially null and may be set later via {@link #setEndInstant(Instant)}.
*
* @param runId the unique identifier for this run, must not be null
* @param startInstant the moment when the run started, must not be null
* @throws NullPointerException if runId or startInstant is null
* @param runId the unique identifier for this run; must not be null
* @param startInstant the moment when the run started; must not be null
* @throws NullPointerException if {@code runId} or {@code startInstant} is null
*/
public BatchRunContext(RunId runId, Instant startInstant) {
this(runId, startInstant, null);
}
/**
* Creates a new BatchRunContext, optionally restricting the run to a specific
* set of document fingerprints.
* <p>
* When {@code fingerprintFilter} is non-null and non-empty, the batch run
* processes only candidates whose computed fingerprint is contained in the
* supplied set. An empty set results in a run that processes nothing.
* A {@code null} value is treated identically to a regular unfiltered run.
* <p>
* The supplied set is defensively copied; modifications to the original set
* after construction have no effect on this context.
* <p>
* The end instant is initially null and may be set later via {@link #setEndInstant(Instant)}.
*
* @param runId the unique identifier for this run; must not be null
* @param startInstant the moment when the run started; must not be null
* @param fingerprintFilter the set of fingerprints to restrict processing to,
* or {@code null} for an unfiltered run
* @throws NullPointerException if {@code runId} or {@code startInstant} is null
*/
public BatchRunContext(RunId runId, Instant startInstant, Set<DocumentFingerprint> fingerprintFilter) {
this.runId = Objects.requireNonNull(runId, "RunId must not be null");
this.startInstant = Objects.requireNonNull(startInstant, "Start instant must not be null");
this.endInstant = null;
this.fingerprintFilter = fingerprintFilter != null
? Collections.unmodifiableSet(Set.copyOf(fingerprintFilter))
: null;
}
/**
* Returns a new {@link BatchRunContext} with the same run ID and start instant,
* but restricted to the given fingerprint filter.
* <p>
* The returned context is independent; changes to the supplied set after this
* call have no effect on the new context.
*
* @param filter the set of fingerprints to restrict processing to;
* {@code null} is treated as no filter (regular run)
* @return a new context with the supplied filter applied; never null
*/
public BatchRunContext withFingerprintFilter(Set<DocumentFingerprint> filter) {
return new BatchRunContext(runId, startInstant, filter);
}
/**
* Returns the optional fingerprint filter for this run.
* <p>
* When the returned optional is empty, no restriction applies and all source-folder
* candidates are eligible (regular batch run). When present, only candidates whose
* computed fingerprint is contained in the returned set are processed.
*
* @return an {@link Optional} containing the immutable fingerprint filter set,
* or an empty optional for a regular unfiltered run
*/
public Optional<Set<DocumentFingerprint>> fingerprintFilter() {
return Optional.ofNullable(fingerprintFilter);
}
/**
@@ -103,6 +174,8 @@ public final class BatchRunContext {
"runId=" + runId +
", startInstant=" + startInstant +
", endInstant=" + endInstant +
", fingerprintFilter=" + (fingerprintFilter == null
? "none" : fingerprintFilter.size() + " fingerprints") +
'}';
}
}