mandantAnlegen(); $this->konto = Konto::anlegen([ 'name' => 'Haushaltsgiro', 'kontoart' => 'bank', 'anfangssaldo' => 500, 'aktiv' => 1, ]); $this->kategorie = Kategorie::anlegen(['name' => 'Haushalt bar', 'art' => 'ausgabe']); Kategorie::bargeldAuswahlSetzen([$this->kategorie]); // Eine Adresse, unter der niemand antwortet: Jeder echte Abruf endet // als Fehler – ein Abruf, der gar nicht erst stattfindet, nicht. Einstellung::set('bargeld_url', 'http://127.0.0.1:9', 'bargeld'); Einstellung::cacheLeeren(); $this->schalterWirdGespeichert(); $this->keinAbgleichUndKeinOffenerVorgang(); $this->nachtraeglichSetzenRaeumtDenStatusWeg(); $this->schnellzuordnungBehaeltDenSchalter(); $this->anzeigeImKontoblattUndBericht(); $this->maskeZeigtDenSchalter(); } /** @param array $zusatz */ private function buchung(string $datum, array $zusatz = []): int { return Buchung::anlegen($zusatz + [ 'konto_id' => $this->konto, 'datum' => $datum, 'betrag' => -45.10, 'empfaenger' => 'Sammelbuchung Bargeld', 'kategorie_id' => $this->kategorie, ]); } /** @return array */ private function vorgaenge(): array { return array_column(Cockpit::offeneVorgaenge('2026-09-30'), 'anzahl', 'schluessel'); } private function schalterWirdGespeichert(): void { $id = $this->buchung('2026-09-01', ['ohne_bargeld_abgleich' => true]); $this->assertEquals(1, (int)Buchung::find($id)['ohne_bargeld_abgleich'], 'Der Schalter wird gespeichert'); $normal = $this->buchung('2026-09-02'); $this->assertEquals(0, (int)Buchung::find($normal)['ohne_bargeld_abgleich'], 'Ohne Schalter bleibt er aus'); } private function keinAbgleichUndKeinOffenerVorgang(): void { $vorher = $this->vorgaenge()['bargeld_fehler'] ?? 0; $ohne = $this->buchung('2026-09-05', ['ohne_bargeld_abgleich' => true]); $this->assertNull(BargeldAbgleich::pruefe($ohne), 'Eine Buchung ohne Bargeld-Abgleich wird nicht geprüft'); $this->assertNull(Buchung::find($ohne)['bargeld_status'], 'Sie trägt keinen Abgleichstatus'); $this->assertEquals($vorher, $this->vorgaenge()['bargeld_fehler'] ?? 0, 'Sie erscheint nicht unter den offenen Vorgängen'); // Gegenprobe: Dieselbe Buchung ohne Schalter wird geprüft und angemahnt. $mit = $this->buchung('2026-09-06'); $this->assertEquals(BargeldAbgleich::FEHLER, BargeldAbgleich::pruefe($mit), 'Ohne Schalter wird abgeglichen'); $this->assertEquals($vorher + 1, $this->vorgaenge()['bargeld_fehler'] ?? 0, 'Und als offener Vorgang angemahnt'); } private function nachtraeglichSetzenRaeumtDenStatusWeg(): void { $id = $this->buchung('2026-09-10'); Database::execute("UPDATE buchungen SET bargeld_status = 'fehlt', bargeld_hinweis = 'Kein Treffer.' WHERE id = ?", [$id]); $vorher = $this->vorgaenge()['bargeld_fehlt'] ?? 0; $this->assertGreaterThan(0, $vorher, 'Die nicht gefundene Buchung wird angemahnt'); $buchung = Buchung::find($id); Buchung::speichern($id, ['ohne_bargeld_abgleich' => 1] + $buchung); BargeldAbgleich::pruefe($id); $frisch = Buchung::find($id); $this->assertEquals(1, (int)$frisch['ohne_bargeld_abgleich'], 'Der Schalter lässt sich nachträglich setzen'); $this->assertNull($frisch['bargeld_status'], 'Der alte Abgleichstatus verschwindet'); $this->assertEquals($vorher - 1, $this->vorgaenge()['bargeld_fehlt'] ?? 0, 'Die Buchung wird nicht mehr angemahnt'); Buchung::speichern($id, ['ohne_bargeld_abgleich' => 0] + $frisch); $this->assertEquals(0, (int)Buchung::find($id)['ohne_bargeld_abgleich'], 'Abgeschaltet gilt der Abgleich wieder'); } private function schnellzuordnungBehaeltDenSchalter(): void { $id = $this->buchung('2026-09-12', ['ohne_bargeld_abgleich' => true, 'kategorie_id' => null]); Buchung::zuordnen($id, $this->kategorie, null); $this->assertNull(BargeldAbgleich::pruefe($id), 'Auch nach der Schnellzuordnung wird nicht abgeglichen'); $this->assertEquals( 1, (int)Buchung::find($id)['ohne_bargeld_abgleich'], 'Die Schnellzuordnung lässt den Schalter stehen' ); } private function anzeigeImKontoblattUndBericht(): void { $marke = View::bargeldMarke(null, null, true); $this->assert(str_contains($marke, 'Bewusst ohne Abgleich mit dem Bargeld-Tool'), 'Die Marke nennt den Grund'); $this->assert(str_contains($marke, '○'), 'Die Marke ist ein leerer Kreis'); $this->assert(str_contains(View::bargeldMarke(null, null), '●'), 'Ohne Schalter bleibt der volle Kreis'); $zeilen = Buchung::kontoblatt($this->konto, ['von' => '2026-09-01', 'bis' => '2026-09-01']); $this->assertEquals(1, (int)($zeilen[0]['ohne_bargeld_abgleich'] ?? 0), 'Das Kontoblatt liefert den Schalter mit'); $bericht = Auswertung::bericht(Auswertung::normalisiere([ 'typ' => 'buchungsbericht', 'zeitraum' => 'alles', ]), '2026-09-30'); $buchungen = array_merge(...array_column($bericht['gruppen'], 'buchungen')); $treffer = array_values(array_filter( $buchungen, static fn(array $b): bool => $b['datum'] === '2026-09-01' && $b['ohne_bargeld_abgleich'] )); $this->assertEquals(1, count($treffer), 'Der Buchungsbericht liefert den Schalter mit'); } private function maskeZeigtDenSchalter(): void { $mit = $this->maske(true, ['ohne_bargeld_abgleich' => 1]); $this->assert( (bool)preg_match('#name="ohne_bargeld_abgleich" value="1"\s*checked#', $mit), 'Mit Bargeld-Tool steht der Schalter gesetzt in der Maske' ); $this->assert(str_contains($mit, 'Ohne Bargeld-Abgleich'), 'Er ist beschriftet'); $ohne = $this->maske(false, ['ohne_bargeld_abgleich' => 1]); $this->assert(!str_contains($ohne, 'Ohne Bargeld-Abgleich'), 'Ohne Bargeld-Tool ist der Schalter unsichtbar'); $this->assert( str_contains($ohne, ''), 'Ein gesetzter Wert geht trotzdem nicht verloren' ); $leer = $this->maske(false, []); $this->assert(!str_contains($leer, 'ohne_bargeld_abgleich'), 'Ohne Bargeld-Tool und ohne Wert steht nichts in der Maske'); } /** @param array $buchung */ private function maske(bool $bargeld, array $buchung): string { $daten = [ 'titel' => 'Buchung bearbeiten', 'konto' => Konto::find($this->konto), 'buchung' => $buchung + [ 'id' => 1, 'konto_id' => $this->konto, 'datum' => '2026-09-01', 'betrag' => -10.0, ], 'splits' => [], 'eingaben' => null, 'kategorien' => Kategorie::baumFlach(), 'klassen' => [], 'konten' => Konto::alle(), 'bargeld' => $bargeld, 'flash' => [], 'benutzer' => null, 'csrfToken' => 'x', 'aktuellerPfad' => '/konten', ]; extract($daten, EXTR_SKIP); ob_start(); include dirname(__DIR__) . '/src/Views/buchungen/formular.php'; return (string)ob_get_clean(); } }