Fix V2.8: selectedRows-Leerproblem und isRunning()-Inkonsistenz behoben

- markSelectedRowsAsResetPending() schützt selectedRows jetzt mit
  selectionSyncInProgress=true, sodass der TableView-SelectionModel-
  Listener die Selektion nicht löscht, wenn Zeilen ersetzt werden
- isRunning() und updateButtonStates() verwenden runningProperty.get()
  statt coordinator.isRunning() für konsistentes Verhalten zwischen
  Button-Zustand und Selektion
- Diagnose-LOG am Anfang von handleReprocessSelected() gibt isRunning()
  und selectedRows.size() aus (Laufend=false, Selektion>0 erwartet)
- Alle [TEMP-TRACE]-Logs entfernt aus GuiBatchRunCoordinator,
  SqliteUnitOfWorkAdapter, SqliteDocumentRecordRepositoryAdapter
  und DocumentProcessingCoordinator

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-04-23 15:35:57 +02:00
parent 55088354ab
commit 09605ee495
5 changed files with 16 additions and 25 deletions
@@ -298,13 +298,11 @@ public final class GuiBatchRunCoordinator {
// Reset the database status synchronously before starting the mini-run. // Reset the database status synchronously before starting the mini-run.
// This ensures that documents are not skipped due to FAILED_FINAL or other // This ensures that documents are not skipped due to FAILED_FINAL or other
// terminal states. // terminal states.
LOG.info("[TEMP-TRACE] startReprocessing: Konfiguration={}, Fingerprints={}", LOG.info("GUI-Erneut-Verarbeiten: Starte Status-Reset für {} Dokument(e), Konfiguration={}.",
configFilePath, fingerprintFilter.stream() fingerprintFilter.size(), configFilePath);
.map(f -> f.sha256Hex().substring(0, 8) + "")
.collect(java.util.stream.Collectors.joining(", ")));
ResetDocumentStatusResult resetResult = resetPort.reset(configFilePath, fingerprintFilter); ResetDocumentStatusResult resetResult = resetPort.reset(configFilePath, fingerprintFilter);
LOG.info("[TEMP-TRACE] startReprocessing: Reset-Ergebnis: {} erfolgreich, {} fehlgeschlagen; Fehler={}", LOG.info("GUI-Erneut-Verarbeiten: Status-Reset abgeschlossen {} erfolgreich, {} fehlgeschlagen.",
resetResult.successCount(), resetResult.failureCount(), resetResult.failures()); resetResult.successCount(), resetResult.failureCount());
if (resetResult.successCount() == 0) { if (resetResult.successCount() == 0) {
LOG.warn("GUI-Reprocessing: Reset für alle {} Dokumente fehlgeschlagen; " LOG.warn("GUI-Reprocessing: Reset für alle {} Dokumente fehlgeschlagen; "
+ "Mini-Lauf wird nicht gestartet.", fingerprintFilter.size()); + "Mini-Lauf wird nicht gestartet.", fingerprintFilter.size());
@@ -285,7 +285,7 @@ public final class GuiBatchRunTab {
* @return {@code true} while the coordinator is processing a run * @return {@code true} while the coordinator is processing a run
*/ */
public boolean isRunning() { public boolean isRunning() {
return coordinator.isRunning(); return runningProperty.get();
} }
/** /**
@@ -687,6 +687,8 @@ public final class GuiBatchRunTab {
} }
private void handleReprocessSelected() { private void handleReprocessSelected() {
LOG.info("GUI-Erneut-Verarbeiten: handleReprocessSelected aufgerufen. "
+ "Laufend={}, Selektion={}.", isRunning(), selectedRows.size());
if (isRunning() || selectedRows.isEmpty()) { if (isRunning() || selectedRows.isEmpty()) {
return; return;
} }
@@ -766,9 +768,14 @@ public final class GuiBatchRunTab {
*/ */
private void markSelectedRowsAsResetPending() { private void markSelectedRowsAsResetPending() {
List<GuiBatchRunResultRow> toMark = new ArrayList<>(selectedRows); List<GuiBatchRunResultRow> toMark = new ArrayList<>(selectedRows);
selectionSyncInProgress = true;
try {
for (GuiBatchRunResultRow row : toMark) { for (GuiBatchRunResultRow row : toMark) {
upsertResultRowByFingerprint(GuiBatchRunResultRow.resetMarker(row)); upsertResultRowByFingerprint(GuiBatchRunResultRow.resetMarker(row));
} }
} finally {
selectionSyncInProgress = false;
}
} }
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
@@ -822,7 +829,7 @@ public final class GuiBatchRunTab {
} }
private void updateButtonStates() { private void updateButtonStates() {
boolean running = coordinator.isRunning(); boolean running = runningProperty.get();
startButton.setDisable(running); startButton.setDisable(running);
if (!running) { if (!running) {
cancelButton.setDisable(true); cancelButton.setDisable(true);
@@ -108,13 +108,8 @@ public class SqliteDocumentRecordRepositoryAdapter implements DocumentRecordRepo
SKIPPED_ALREADY_PROCESSED, SKIPPED_FINAL_FAILURE -> SKIPPED_ALREADY_PROCESSED, SKIPPED_FINAL_FAILURE ->
new DocumentKnownProcessable(record); new DocumentKnownProcessable(record);
}; };
logger.info("[TEMP-TRACE] findByFingerprint: fingerprint={}, jdbcUrl={}, Ergebnis={}",
fingerprint.sha256Hex(), jdbcUrl, result.getClass().getSimpleName());
return result; return result;
} else { } else {
// Document not found
logger.info("[TEMP-TRACE] findByFingerprint: fingerprint={}, jdbcUrl={}, Ergebnis=DocumentUnknown",
fingerprint.sha256Hex(), jdbcUrl);
return new DocumentUnknown(); return new DocumentUnknown();
} }
} }
@@ -321,9 +316,7 @@ public class SqliteDocumentRecordRepositoryAdapter implements DocumentRecordRepo
PreparedStatement statement = connection.prepareStatement(sql)) { PreparedStatement statement = connection.prepareStatement(sql)) {
statement.setString(1, fingerprint.sha256Hex()); statement.setString(1, fingerprint.sha256Hex());
int rowsAffected = statement.executeUpdate(); statement.executeUpdate();
logger.info("[TEMP-TRACE] deleteByFingerprint: fingerprint={}, jdbcUrl={}, rowsAffected={}",
fingerprint.sha256Hex(), jdbcUrl, rowsAffected);
} catch (SQLException e) { } catch (SQLException e) {
String message = "Failed to delete document record for fingerprint '" String message = "Failed to delete document record for fingerprint '"
@@ -178,8 +178,6 @@ public class SqliteUnitOfWorkAdapter implements UnitOfWorkPort {
*/ */
@Override @Override
public void resetDocumentByFingerprint(DocumentFingerprint fingerprint) { public void resetDocumentByFingerprint(DocumentFingerprint fingerprint) {
logger.info("[TEMP-TRACE] resetDocumentByFingerprint: Fingerprint={}, jdbcUrl={}",
fingerprint.sha256Hex(), jdbcUrl);
// Delete attempts first (FK constraint: processing_attempt → document_record) // Delete attempts first (FK constraint: processing_attempt → document_record)
SqliteProcessingAttemptRepositoryAdapter attemptRepo = SqliteProcessingAttemptRepositoryAdapter attemptRepo =
new SqliteProcessingAttemptRepositoryAdapter(jdbcUrl) { new SqliteProcessingAttemptRepositoryAdapter(jdbcUrl) {
@@ -199,8 +197,6 @@ public class SqliteUnitOfWorkAdapter implements UnitOfWorkPort {
} }
}; };
recordRepo.deleteByFingerprint(fingerprint); recordRepo.deleteByFingerprint(fingerprint);
logger.info("[TEMP-TRACE] resetDocumentByFingerprint: Löschung abgeschlossen für Fingerprint={}",
fingerprint.sha256Hex());
} }
} }
} }
@@ -351,9 +351,6 @@ public class DocumentProcessingCoordinator {
// Step 1: Load the document master record // Step 1: Load the document master record
DocumentRecordLookupResult lookupResult = DocumentRecordLookupResult lookupResult =
documentRecordRepository.findByFingerprint(fingerprint); documentRecordRepository.findByFingerprint(fingerprint);
logger.info("[TEMP-TRACE] processDeferredOutcome: Fingerprint={}, Lookup-Ergebnis={}",
fingerprint.sha256Hex(), lookupResult.getClass().getSimpleName());
// Step 2: Handle persistence lookup failure // Step 2: Handle persistence lookup failure
if (lookupResult instanceof PersistenceLookupTechnicalFailure failure) { if (lookupResult instanceof PersistenceLookupTechnicalFailure failure) {
logger.error("Cannot process '{}': master record lookup failed: {}", logger.error("Cannot process '{}': master record lookup failed: {}",