Feature: Scheduler-Tick-Zaehlung korrigieren und Sitzungstotale einfuehren

Der Scheduler-Tab meldete nach erfolgreicher Verarbeitung faelschlich
"keine neuen Dokumente". Ursache war ein hartkodiertes RunSummary.noOp()
im BatchRunTrigger der Bootstrap; der echte Lauf-Summary wurde nie
gelesen.

- Bootstrap: BatchRunProgressObserver erfasst RunSummary aus onRunEnded
  und uebersetzt ihn in den ausgehenden RunSummary fuer das Tick-Ergebnis
- Neuer Wert-Typ SchedulerSessionTotals (success/failed) plus
  Optional-Feld in SchedulerStatus
- DefaultSchedulerControlUseCase setzt die Totale beim start() auf null
  zurueck, summiert pro Started-Tick auf, friert sie beim stop() ein
- GuiSchedulerTab zeigt pro Tick "X verarbeitet, Y Fehler" oder
  "keine neuen Dokumente" sowie ein zusaetzliches Label
  "Seit Scheduler-Start: X verarbeitet, Y Fehler"

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-07 14:51:36 +02:00
parent ac5b74917f
commit 368cb81b56
6 changed files with 233 additions and 10 deletions
@@ -13,6 +13,7 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import de.gecheckt.pdf.umbenenner.application.port.in.SchedulerControlUseCase;
import de.gecheckt.pdf.umbenenner.application.port.in.SchedulerSessionTotals;
import de.gecheckt.pdf.umbenenner.application.port.in.SchedulerStartException;
import de.gecheckt.pdf.umbenenner.application.port.in.SchedulerState;
import de.gecheckt.pdf.umbenenner.application.port.in.SchedulerStatus;
@@ -81,6 +82,7 @@ public final class GuiSchedulerTab {
private final Button stopButton = new Button("Scheduler stoppen");
private final Label nextTickLabel = new Label();
private final Label lastRunLabel = new Label("Noch kein Lauf in dieser Sitzung.");
private final Label sessionTotalsLabel = new Label();
private final Label lastErrorLabel = new Label();
private final TextField intervalField = new TextField();
private final Label intervalValidationLabel = new Label();
@@ -174,6 +176,7 @@ public final class GuiSchedulerTab {
updateButtons(status);
updateNextTickLabel(status);
updateLastRunLabel(status);
updateSessionTotalsLabel(status);
updateLastErrorLabel(status);
updateIntervalFieldEditability(status);
}
@@ -199,6 +202,11 @@ public final class GuiSchedulerTab {
lastRunLabel.setWrapText(true);
sessionTotalsLabel.setWrapText(true);
sessionTotalsLabel.setStyle("-fx-text-fill: #7f8c8d;");
sessionTotalsLabel.setVisible(false);
sessionTotalsLabel.setManaged(false);
lastErrorLabel.setStyle("-fx-text-fill: #c0392b;");
lastErrorLabel.setWrapText(true);
lastErrorLabel.setVisible(false);
@@ -219,6 +227,7 @@ public final class GuiSchedulerTab {
buttonBox,
nextTickLabel,
lastRunLabel,
sessionTotalsLabel,
lastErrorLabel,
new Separator(),
intervalBox,
@@ -337,19 +346,34 @@ public final class GuiSchedulerTab {
RunSummary summary = status.lastRunSummary().get();
String timeStr = TIME_FORMATTER.format(endedAt);
boolean noDocuments = summary.successCount() == 0
&& summary.failedCount() == 0
&& summary.skippedCount() == 0;
&& summary.failedCount() == 0;
if (noDocuments) {
lastRunLabel.setText("Letzter Lauf: " + timeStr + " keine neuen Dokumente");
} else {
lastRunLabel.setText("Letzter Lauf: " + timeStr + " "
+ summary.successCount() + " Dokumente verarbeitet");
+ summary.successCount() + " verarbeitet, "
+ summary.failedCount() + " Fehler");
}
} else {
lastRunLabel.setText("Noch kein Lauf in dieser Sitzung.");
}
}
private void updateSessionTotalsLabel(SchedulerStatus status) {
Optional<SchedulerSessionTotals> totals = status.sessionTotals();
if (totals.isPresent()) {
SchedulerSessionTotals t = totals.get();
sessionTotalsLabel.setText("Seit Scheduler-Start: "
+ t.successCount() + " verarbeitet, "
+ t.failedCount() + " Fehler");
sessionTotalsLabel.setVisible(true);
sessionTotalsLabel.setManaged(true);
} else {
sessionTotalsLabel.setVisible(false);
sessionTotalsLabel.setManaged(false);
}
}
private void updateLastErrorLabel(SchedulerStatus status) {
Optional<String> lastError = status.lastError();
if (lastError.isPresent() && !lastError.get().isBlank()) {