Fix #41: Historischen KI-Dateinamen für übersprungene Dokumente in Ergebnistabelle anzeigen
Neue Komponenten: - ResolveHistoricalFileNameUseCase (port/in) und DefaultResolveHistoricalFileNameUseCase (usecase) - GuiHistoricalFileNamePort (GUI-interner Port, folgt dem Muster von GuiManualFileRenamePort) GuiBatchRunCoordinator ruft in toRow() für SKIPPED-Zeilen ohne finalName den historicalFileNamePort auf und trägt den Rückgabewert als neuen Dateinamen ein. Bootstrap verdrahtet resolveHistoricalFileNameForGui als GuiHistoricalFileNamePort und übergibt ihn über GuiStartupContext an den GUI-Adapter. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+37
@@ -0,0 +1,37 @@
|
||||
package de.gecheckt.pdf.umbenenner.application.port.in;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint;
|
||||
|
||||
/**
|
||||
* Inbound port for resolving the historical target filename for a previously processed document.
|
||||
* <p>
|
||||
* Used to populate the "Neuer Dateiname" column in the GUI result table for documents that
|
||||
* were skipped in the current run because they were already in a terminal state from a
|
||||
* previous run. For documents that previously reached
|
||||
* {@link de.gecheckt.pdf.umbenenner.domain.model.ProcessingStatus#SUCCESS}, the last
|
||||
* successfully written target filename is returned. For all other terminal states
|
||||
* (e.g. {@code FAILED_FINAL}) that were never copied to the target folder, an empty
|
||||
* {@link Optional} is returned.
|
||||
* <p>
|
||||
* <strong>Architecture boundary:</strong> Implementations of this port must not expose
|
||||
* JDBC, SQLite, filesystem or HTTP types through this interface. All infrastructure details
|
||||
* remain in the adapter layer.
|
||||
*/
|
||||
public interface ResolveHistoricalFileNameUseCase {
|
||||
|
||||
/**
|
||||
* Returns the last successfully written target filename for the document identified
|
||||
* by the given fingerprint, or an empty {@link Optional} if no such filename exists.
|
||||
* <p>
|
||||
* The method never throws: technical failures during the repository lookup are caught
|
||||
* and result in an empty return value.
|
||||
*
|
||||
* @param fingerprint content-based document identity; must not be {@code null}
|
||||
* @return the last target filename written for this document, or empty if the document
|
||||
* never reached a successful terminal state or the lookup failed
|
||||
* @throws NullPointerException if {@code fingerprint} is {@code null}
|
||||
*/
|
||||
Optional<String> resolveHistoricalFileName(DocumentFingerprint fingerprint);
|
||||
}
|
||||
+7
@@ -28,6 +28,13 @@
|
||||
* — Event and summary value types carried to the observer</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* <p>
|
||||
* Query use cases (for GUI adapters):
|
||||
* <ul>
|
||||
* <li>{@link de.gecheckt.pdf.umbenenner.application.port.in.ResolveHistoricalFileNameUseCase}
|
||||
* — Resolves the last known target filename for a document identified by its fingerprint</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* Architecture Rule: Inbound ports are independent of implementation and contain no business logic.
|
||||
* They define "what can be done to the application". All dependencies point inward;
|
||||
* adapters depend on ports, not vice versa.
|
||||
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
package de.gecheckt.pdf.umbenenner.application.usecase;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
import de.gecheckt.pdf.umbenenner.application.port.in.ResolveHistoricalFileNameUseCase;
|
||||
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentRecordLookupResult;
|
||||
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentRecordRepository;
|
||||
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentTerminalSuccess;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link ResolveHistoricalFileNameUseCase}.
|
||||
* <p>
|
||||
* Queries the {@link DocumentRecordRepository} for the master record of the given fingerprint.
|
||||
* If the record represents a document that previously reached a successful terminal state,
|
||||
* the last known target filename ({@code lastTargetFileName}) is returned.
|
||||
* <p>
|
||||
* For all other terminal states (e.g. documents that finally failed without ever producing
|
||||
* a target copy) or when no master record exists, an empty {@link Optional} is returned.
|
||||
* Technical failures during the repository lookup are caught silently and treated as
|
||||
* an absent result so that the calling GUI layer is never forced to handle exceptions
|
||||
* from this query path.
|
||||
*/
|
||||
public class DefaultResolveHistoricalFileNameUseCase implements ResolveHistoricalFileNameUseCase {
|
||||
|
||||
private final DocumentRecordRepository documentRecordRepository;
|
||||
|
||||
/**
|
||||
* Creates the use case with the required document master record repository.
|
||||
*
|
||||
* @param documentRecordRepository repository for reading document master records;
|
||||
* must not be {@code null}
|
||||
* @throws NullPointerException if {@code documentRecordRepository} is {@code null}
|
||||
*/
|
||||
public DefaultResolveHistoricalFileNameUseCase(
|
||||
DocumentRecordRepository documentRecordRepository) {
|
||||
this.documentRecordRepository = Objects.requireNonNull(
|
||||
documentRecordRepository, "documentRecordRepository must not be null");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last successfully written target filename for the given fingerprint,
|
||||
* or an empty {@link Optional} if no such filename exists.
|
||||
* <p>
|
||||
* The method queries the document master record. Only documents with a prior
|
||||
* successful terminal state carry a non-null {@code lastTargetFileName}. For all
|
||||
* other states (unknown, processable, finally failed) and on any technical lookup
|
||||
* failure, an empty {@link Optional} is returned.
|
||||
*
|
||||
* @param fingerprint content-based document identity; must not be {@code null}
|
||||
* @return the historical target filename, or empty if not available
|
||||
* @throws NullPointerException if {@code fingerprint} is {@code null}
|
||||
*/
|
||||
@Override
|
||||
public Optional<String> resolveHistoricalFileName(DocumentFingerprint fingerprint) {
|
||||
Objects.requireNonNull(fingerprint, "fingerprint must not be null");
|
||||
try {
|
||||
DocumentRecordLookupResult result =
|
||||
documentRecordRepository.findByFingerprint(fingerprint);
|
||||
if (result instanceof DocumentTerminalSuccess success) {
|
||||
return Optional.ofNullable(success.record().lastTargetFileName());
|
||||
}
|
||||
return Optional.empty();
|
||||
} catch (Exception e) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user