From ec72fd213a6e70ce61a7cd966a06dd6b8203645b Mon Sep 17 00:00:00 2001 From: fds2610-bot Date: Wed, 30 Sep 2026 17:30:59 +0200 Subject: [PATCH] =?UTF-8?q?Fix=20#102:=20Offener=20Vorgang=20f=C3=BCr=20fe?= =?UTF-8?q?hlgeschlagene=20Bargeld-Tool-Pr=C3=BCfungen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Die Kachel "Offene Vorgänge" im Cockpit zeigt jetzt auch Buchungen, deren letzter Abgleich mit dem Bargeld-Tool an einem Abrufproblem gescheitert ist (bargeld_status = 'fehler'). Ein Klick führt auf die neue, kontoübergreifende Liste unter Verwaltung -> Bargeld-Tool -> fehlgeschlagene Prüfungen. Co-Authored-By: Claude Sonnet 5 --- Changes.md | 9 ++++ docs/buchungen.md | 6 +++ docs/cockpit.md | 12 ++++- public/index.php | 7 +-- src/Controllers/VerwaltungController.php | 16 ++++++ src/Models/Buchung.php | 22 +++++++++ src/Services/Cockpit.php | 17 +++++++ src/Views/verwaltung/bargeld_fehler.php | 62 ++++++++++++++++++++++++ tests/15_CockpitTest.php | 61 +++++++++++++++++++++++ tests/34_BargeldAbgleichTest.php | 41 ++++++++++++++++ 10 files changed, 248 insertions(+), 5 deletions(-) create mode 100644 src/Views/verwaltung/bargeld_fehler.php diff --git a/Changes.md b/Changes.md index 287490b..1522420 100644 --- a/Changes.md +++ b/Changes.md @@ -1,5 +1,14 @@ # Änderungen +## Offener Vorgang für fehlgeschlagene Bargeld-Tool-Prüfungen (30.09.2026) + +- Die Kachel „Offene Vorgänge“ im Cockpit zählt jetzt auch Buchungen, deren letzter Abgleich + mit dem Bargeld-Tool an einem Abrufproblem gescheitert ist (`bargeld_status = 'fehler'`) – + nicht zu verwechseln mit „im Bargeld-Tool nicht gefunden“. Ein Klick führt auf die neue + Liste unter Verwaltung → Bargeld-Tool → fehlgeschlagene Prüfungen + (`/verwaltung/bargeld/fehler`), die diese Buchungen kontoübergreifend mit Konto, Betrag und + Fehlermeldung auflistet (`Buchung::mitBargeldFehler()`). Fix #102. + ## phpFinTS auf v4.1.6 (30.09.2026) - Fork neu auf Upstream vom 22.09.2026 aufgebaut (psr/log und Depotaufstellung v6, #598, bleiben diff --git a/docs/buchungen.md b/docs/buchungen.md index 76fb69e..0ce5484 100644 --- a/docs/buchungen.md +++ b/docs/buchungen.md @@ -271,6 +271,12 @@ treffen kann. Der Tooltip nennt den Treffer bzw. die Meldung der Gegenstelle. Is Adresse hinterlegt, entfällt die Spalte ganz; sie zeigte sonst in jeder Zeile denselben grauen Kreis. +Buchungen mit dem Status `fehler` zählen im Cockpit als offener Vorgang „Bargeld-Tool-Prüfung +fehlgeschlagen“ (siehe `docs/cockpit.md`) – ein Klick führt auf +`/verwaltung/bargeld/fehler`, die kontoübergreifende Liste dieser Buchungen +(`Buchung::mitBargeldFehler()`). Der Status `fehlt` erscheint dort bewusst nicht: Er ist ein +Ergebnis, kein offenes Problem – eine erneute Zuordnung fragt einfach wieder ohne Erfolg. + **Ein Fehlschlag ist ein Ergebnis, kein Abbruch.** Weder ein abgeschaltetes Bargeld-Tool noch ein Netzfehler darf verhindern, dass die Kategorie gespeichert wird; der Abruf bricht nach acht Sekunden ab. Null Treffer sind ausdrücklich kein Fehler – ein 404 dagegen schon, diff --git a/docs/cockpit.md b/docs/cockpit.md index 9b5631f..b781c03 100644 --- a/docs/cockpit.md +++ b/docs/cockpit.md @@ -177,6 +177,11 @@ Was auf Bearbeitung wartet, mit Zahl und Weg dorthin: * **ohne Kategorie** – Buchungen ohne Kategorie, die auch keine Umbuchung und nicht gesplittet sind. Umbuchungen brauchen keine Kategorie und zählen deshalb nicht mit. * **nicht abgeglichen** – Buchungen aktiver Konten bis heute ohne Abgleich-Kennzeichen. +* **Bargeld-Tool-Prüfung fehlgeschlagen** – Buchungen mit `bargeld_status = 'fehler'`, also + einem gescheiterten Abruf beim Bargeld-Tool (siehe `docs/buchungen.md`), nicht zu + verwechseln mit „im Bargeld-Tool nicht gefunden“. Führt auf + `/verwaltung/bargeld/fehler`, die Liste dieser Buchungen kontoübergreifend + (`Buchung::mitBargeldFehler()`), denn die Prüfung hängt an der Kategorie, nicht am Konto. * **Monatsabschluss steht aus** – der Vorschlag aus `Monatsabschluss::naechsterOffener()`, aber nur, wenn dieser Monat wirklich noch offen ist und Buchungen trägt. @@ -188,8 +193,11 @@ regelmäßigen Buchungen. * `tests/15_CockpitTest.php` – Kachelauswahl (Reihenfolge, Unbekanntes, leere Auswahl), Monatsreihe mit Trennung von Einnahmen und Ausgaben, Umbuchungen bleiben draußen, - Verteilung auf die Wurzelkategorie, Kürzung auf die Segmentgrenze, offene Vorgänge, - Legendeneinträge führen zu Buchungslisten, deren Summe den Legendenwert trifft + Verteilung auf die Wurzelkategorie, Kürzung auf die Segmentgrenze, offene Vorgänge + (auch fehlgeschlagene Bargeld-Tool-Prüfungen), Legendeneinträge führen zu + Buchungslisten, deren Summe den Legendenwert trifft +* `tests/34_BargeldAbgleichTest.php` – die Liste der fehlgeschlagenen Prüfungen + (`Buchung::mitBargeldFehler()`) und ihre Anzeige unter Verwaltung → Bargeld-Tool * `tests/13_VermoegensentwicklungTest.php` – Zeitraum, Fortschreibung der Stände, Umbuchungen, Σ-Kennzeichen, Schalter „nur aktive“, Zusammenfassen jenseits der Palette, Ebenen (jedes Konto genau einmal, letzte Ebene ohne Sammelstück, zu tiefe Ebene fällt diff --git a/public/index.php b/public/index.php index e4257a9..fa9cda4 100644 --- a/public/index.php +++ b/public/index.php @@ -136,9 +136,10 @@ $router->post('/verwaltung/kontengruppen/{id}/loeschen', fn(array $p) => (new VerwaltungController())->kontengruppeLoeschen($p)); // Die Konfiguration des Bargeld-Tools samt Auswahl der abzugleichenden Kategorien. -$router->get('/verwaltung/bargeld', fn() => (new VerwaltungController())->bargeld()); -$router->post('/verwaltung/bargeld', fn() => (new VerwaltungController())->bargeldSpeichern()); -$router->post('/verwaltung/bargeld/test', fn() => (new VerwaltungController())->bargeldTest()); +$router->get('/verwaltung/bargeld', fn() => (new VerwaltungController())->bargeld()); +$router->post('/verwaltung/bargeld', fn() => (new VerwaltungController())->bargeldSpeichern()); +$router->post('/verwaltung/bargeld/test', fn() => (new VerwaltungController())->bargeldTest()); +$router->get('/verwaltung/bargeld/fehler', fn() => (new VerwaltungController())->bargeldFehler()); // Das Bankleitzahlen-Verzeichnis: nachschlagen und die Institutsliste einlesen. $router->get('/verwaltung/bankinstitute', fn() => (new BankinstituteController())->index()); diff --git a/src/Controllers/VerwaltungController.php b/src/Controllers/VerwaltungController.php index 6b8272c..ba71ee6 100644 --- a/src/Controllers/VerwaltungController.php +++ b/src/Controllers/VerwaltungController.php @@ -6,6 +6,7 @@ namespace Finanzmanager\Controllers; use Finanzmanager\Models\Adresse; use Finanzmanager\Models\Bankinstitut; +use Finanzmanager\Models\Buchung; use Finanzmanager\Models\Einstellung; use Finanzmanager\Models\Erinnerung; use Finanzmanager\Models\Kategorie; @@ -275,4 +276,19 @@ class VerwaltungController extends BaseController $ergebnis = $zugang->test(); View::redirect('/verwaltung/bargeld', $ergebnis['meldung'], $ergebnis['erfolg'] ? 'erfolg' : 'fehler'); } + + /** + * Alle Buchungen, an denen die Bargeld-Tool-Prüfung zuletzt einen Fehler + * festgestellt hat – der Weg, den die Kachel „Offene Vorgänge“ des + * Cockpit anbietet, sobald es solche Buchungen gibt. + */ + public function bargeldFehler(): void + { + $this->verlangeAnmeldung(); + + $this->render('verwaltung/bargeld_fehler.php', [ + 'titel' => 'Bargeld-Tool: fehlgeschlagene Prüfungen', + 'buchungen' => Buchung::mitBargeldFehler(), + ]); + } } diff --git a/src/Models/Buchung.php b/src/Models/Buchung.php index a312571..e898f0d 100644 --- a/src/Models/Buchung.php +++ b/src/Models/Buchung.php @@ -472,6 +472,28 @@ class Buchung return $zeilen; } + /** + * Alle Buchungen, an denen die Bargeld-Tool-Prüfung zuletzt einen Fehler + * festgestellt hat – kontoübergreifend, denn die Prüfung hängt an der + * Kategorie der Buchung, nicht an ihrem Konto. Der Status 'fehler' steht + * für einen gescheiterten Abruf (siehe Services\BargeldAbgleich::FEHLER), + * nicht für eine im Bargeld-Tool fehlende Buchung. + * + * @return list> + */ + public static function mitBargeldFehler(): array + { + return Database::fetchAll( + 'SELECT b.id, b.datum, b.betrag, b.empfaenger, b.buchungstext, + b.bargeld_hinweis, b.bargeld_geprueft_am, b.konto_id, k.name AS konto_name + FROM buchungen b + JOIN konten k ON k.id = b.konto_id + WHERE b.benutzer_id = ? AND b.bargeld_status = \'fehler\' + ORDER BY b.datum DESC, b.id DESC', + [Mandant::id()] + ); + } + /** * Salden aller Konten in einem Rutsch: Anfangssaldo plus Buchungen, plus die * Gegenseiten fremder Umbuchungen. Ohne Stichtag zählt alles (erwarteter diff --git a/src/Services/Cockpit.php b/src/Services/Cockpit.php index 494bbfd..8af767e 100644 --- a/src/Services/Cockpit.php +++ b/src/Services/Cockpit.php @@ -271,6 +271,23 @@ class Cockpit ]; } + // Buchungen, deren letzter Abgleich mit dem Bargeld-Tool gescheitert + // ist – ein Abrufproblem, keine Aussage über die Buchung selbst (siehe + // BargeldAbgleich::FEHLER), aber solange offen, bis er erneut glückt. + $bargeldFehler = (int)Database::fetchValue( + 'SELECT COUNT(*) FROM buchungen WHERE benutzer_id = ? AND bargeld_status = ?', + [$benutzer, BargeldAbgleich::FEHLER], + 0 + ); + if ($bargeldFehler > 0) { + $vorgaenge[] = [ + 'schluessel' => 'bargeld_fehler', + 'text' => 'Bargeld-Tool-Prüfung fehlgeschlagen', + 'anzahl' => $bargeldFehler, + 'pfad' => '/verwaltung/bargeld/fehler', + ]; + } + // Fällige regelmäßige Buchungen – die automatischen zählen mit, denn // gebucht sind sie erst nach dem Fälligkeitslauf. $termine = Faelligkeitslauf::vorschau($heute); diff --git a/src/Views/verwaltung/bargeld_fehler.php b/src/Views/verwaltung/bargeld_fehler.php new file mode 100644 index 0000000..2af3a7b --- /dev/null +++ b/src/Views/verwaltung/bargeld_fehler.php @@ -0,0 +1,62 @@ +> $buchungen + */ + +include __DIR__ . '/../layout/kopf.php'; +?> + + + +

+ Bei diesen Buchungen ist der letzte Abruf beim Bargeld-Tool gescheitert – ein + Abrufproblem, keine Aussage darüber, ob die Buchung dort steht. Eine erneute + Zuordnung der Kategorie in der Buchungsmaske oder im Kontoblatt stößt die + Prüfung erneut an. +

+ + +

Keine offenen Fehler.

+ + + + + + + + + + + + + + + + + + + + + + +
DatumKontoEmpfänger / BuchungstextBetragMeldung
+ + + + + + + +
+ + + diff --git a/tests/15_CockpitTest.php b/tests/15_CockpitTest.php index ec4eadf..f0d9336 100644 --- a/tests/15_CockpitTest.php +++ b/tests/15_CockpitTest.php @@ -2,6 +2,7 @@ declare(strict_types=1); +use Finanzmanager\Database; use Finanzmanager\Models\Buchung; use Finanzmanager\Models\Kategorie; use Finanzmanager\Models\Konto; @@ -35,6 +36,7 @@ class CockpitTest extends TestCase $this->verteilungFasstAufDerWurzelZusammen(); $this->verteilungKuerztAufDieGrenze(); $this->offeneVorgaengeZaehlenWasWartet(); + $this->bargeldFehlerErscheintAlsOffenerVorgang(); $this->legendenFuehrenZuDenBuchungen(); } @@ -251,6 +253,65 @@ class CockpitTest extends TestCase } } + /** + * Eine Buchung, deren letzter Abgleich mit dem Bargeld-Tool gescheitert + * ist, erscheint als eigener offener Vorgang – unabhängig vom Konto, denn + * die Prüfung hängt an der Kategorie. + */ + private function bargeldFehlerErscheintAlsOffenerVorgang(): void + { + $schluessel = static fn(array $vorgaenge): array => array_column($vorgaenge, 'anzahl', 'schluessel'); + + $vorher = $schluessel(Cockpit::offeneVorgaenge(self::HEUTE)); + $this->assert( + !isset($vorher['bargeld_fehler']), + 'Ohne betroffene Buchung fehlt der Vorgang ganz' + ); + + $id = Buchung::anlegen([ + 'konto_id' => $this->giro, 'datum' => '2026-08-09', 'betrag' => -15, + 'empfaenger' => 'Bargeldabhebung', 'kategorie_id' => $this->freizeit, 'abgeglichen' => 1, + ]); + Database::execute( + "UPDATE buchungen SET bargeld_status = 'fehler', bargeld_hinweis = 'Kasse nicht erreichbar.' + WHERE id = ?", + [$id] + ); + + $nachher = Cockpit::offeneVorgaenge(self::HEUTE); + $eintrag = null; + foreach ($nachher as $vorgang) { + if ($vorgang['schluessel'] === 'bargeld_fehler') { + $eintrag = $vorgang; + } + } + + $this->assertNotEmpty($eintrag, 'Der offene Vorgang erscheint jetzt'); + $this->assertEquals(1, $eintrag['anzahl'], 'Genau die eine betroffene Buchung wird gezählt'); + $this->assertEquals( + '/verwaltung/bargeld/fehler', + $eintrag['pfad'], + 'Der Weg führt zur Liste der fehlgeschlagenen Prüfungen' + ); + + $liste = Buchung::mitBargeldFehler(); + $this->assertEquals(1, count($liste), 'Die Liste enthält genau diese Buchung'); + $this->assertEquals($id, $liste[0]['id'], 'Es ist dieselbe Buchung'); + $this->assertEquals('Cockpitgiro', $liste[0]['konto_name'], 'Der Kontoname kommt mit'); + $this->assertEquals( + 'Kasse nicht erreichbar.', + $liste[0]['bargeld_hinweis'], + 'Die Meldung der Gegenstelle steht dabei' + ); + + // Ein Status ungleich 'fehler' (z. B. 'gefunden') zählt nicht mit. + Database::execute("UPDATE buchungen SET bargeld_status = 'gefunden' WHERE id = ?", [$id]); + $ohne = $schluessel(Cockpit::offeneVorgaenge(self::HEUTE)); + $this->assert(!isset($ohne['bargeld_fehler']), 'Ein gefundener Treffer ist kein offener Vorgang mehr'); + + Database::execute("UPDATE buchungen SET bargeld_status = 'fehler' WHERE id = ?", [$id]); + } + /** * Jeder Legendeneintrag führt zu einer Buchungsliste, deren Summe genau * seinen Wert trifft – auch „Nicht kategorisiert“ und das Sammelsegment. diff --git a/tests/34_BargeldAbgleichTest.php b/tests/34_BargeldAbgleichTest.php index 20e4638..e67a589 100644 --- a/tests/34_BargeldAbgleichTest.php +++ b/tests/34_BargeldAbgleichTest.php @@ -2,6 +2,7 @@ declare(strict_types=1); +use Finanzmanager\Database; use Finanzmanager\Models\Buchung; use Finanzmanager\Models\Einstellung; use Finanzmanager\Models\Kategorie; @@ -51,6 +52,7 @@ class BargeldAbgleichTest extends TestCase $this->kontoblattFuehrtDenStatusMit(); $this->kontoblattZeigtDieViervierSymbole(); $this->konfigurationsseiteZeigtDieAuswahl(); + $this->fehlerlisteZeigtDieBuchungen(); } protected function tearDown(): void @@ -509,6 +511,45 @@ class BargeldAbgleichTest extends TestCase ); } + /** + * Die Liste unter „Verwaltung → Bargeld-Tool → fehlgeschlagene Prüfungen“, + * das Ziel der Kachel „Offene Vorgänge“ im Cockpit. Buchung::mitBargeldFehler() + * ist kontoübergreifend – geprüft am Datenmodell, nicht am Kontoblatt. + */ + private function fehlerlisteZeigtDieBuchungen(): void + { + $id = $this->buchung(-27.30, '2026-06-01'); + Buchung::zuordnen($id, $this->bargeldKategorie, null); + Database::execute( + "UPDATE buchungen SET bargeld_status = 'fehler', bargeld_hinweis = 'Kasse steht still.' WHERE id = ?", + [$id] + ); + + $liste = Buchung::mitBargeldFehler(); + $treffer = null; + foreach ($liste as $zeile) { + if ((int)$zeile['id'] === $id) { + $treffer = $zeile; + } + } + $this->assertNotEmpty($treffer, 'Die Buchung mit Status „fehler“ steht in der Liste'); + $this->assertEquals('Bargeld Giro', $treffer['konto_name'], 'Der Kontoname kommt mit – die Liste ist kontoübergreifend'); + $this->assertEquals('Kasse steht still.', $treffer['bargeld_hinweis'], 'Die Meldung steht dabei'); + + $html = $this->rendere('verwaltung/bargeld_fehler.php', ['buchungen' => $liste]); + + $this->assert(str_contains($html, 'Bargeld Giro'), 'Der Kontoname steht in der Liste'); + $this->assert(str_contains($html, 'Bargeldabhebung'), 'Der Empfänger steht in der Liste'); + $this->assert(str_contains($html, 'Kasse steht still.'), 'Die Meldung der Gegenstelle steht dabei'); + $this->assert( + str_contains($html, '/konten/' . $this->konto), + 'Der Kontoname führt ins Kontoblatt' + ); + + $leer = $this->rendere('verwaltung/bargeld_fehler.php', ['buchungen' => []]); + $this->assert(str_contains($leer, 'Keine offenen Fehler.'), 'Eine leere Liste sagt das ausdrücklich'); + } + /** @param list> $zeilen */ private function kontoblattHtml(array $zeilen, bool $bargeld): string {