mandantAnlegen(); $this->stammdaten(); $this->auswahlVerschwindetAusDerListe(); $this->buchungUeberlebtDenGeloeschtenUmsatz(); $this->jederStatusLaesstSichLoeschen(); $this->leereUndUnbekannteAuswahlBleibtFolgenlos(); $this->jedeAnsichtBietetDieAuswahlAn(); } private function stammdaten(): void { $this->giro = Konto::anlegen([ 'name' => 'Sammelgiro', 'kontoart' => 'bank', 'anfangssaldo' => 0, 'aktiv' => 1, ]); $this->zugang = Bankzugang::anlegen([ 'name' => 'Sammelbank', 'bankname' => 'Sammelbank AG', 'blz' => '20041112', 'fints_url' => 'https://banking.example.org/fints', 'benutzerkennung' => 'testkunde', 'pin' => 'geheim1234', 'abrufzeitraum' => 'letzter_abruf', 'aktiv' => 1, ]); $this->bankkonto = (int)Bankzugang::findeOderLegeKontoAn( $this->zugang, 'DE53200411120847626901', '847626901', 'Girokonto' )['id']; Bankzugang::ordneKontoZu($this->bankkonto, $this->giro); } private function umsatzAnlegen(string $zweck, float $betrag = -19.99): int { // Jeder Umsatz bekommt einen eigenen Tag: Sonst fiele der Fingerabdruck // zweier gleich lautender Zeilen zusammen und der zweite käme nie an. $tag = str_pad((string)(++$this->laufendeNummer), 2, '0', STR_PAD_LEFT); $id = Bankumsatz::anlegen([ 'zugang_id' => $this->zugang, 'konto_id' => $this->giro, 'bank_konto_id' => $this->bankkonto, 'extern_id' => Bankumsatz::externId($this->bankkonto, '2026-07-' . $tag, $betrag, $zweck), 'buchungstag' => '2026-07-' . $tag, 'wertstellung' => '2026-07-' . $tag, 'betrag' => $betrag, 'empfaenger' => 'Sammelempfänger', 'verwendungszweck' => $zweck, 'buchungsart' => 'LASTSCHRIFT', ]); $this->assertNotEmpty($id, 'Umsatz angelegt: ' . $zweck); return (int)$id; } private function existiert(int $umsatzId): bool { return Database::fetchValue('SELECT 1 FROM bank_umsaetze WHERE id = ?', [$umsatzId]) !== null; } /** Die Mehrfachauswahl trifft genau die angekreuzten Zeilen. */ private function auswahlVerschwindetAusDerListe(): void { $eins = $this->umsatzAnlegen('Sammelstrom'); $zwei = $this->umsatzAnlegen('Sammelwasser'); $drei = $this->umsatzAnlegen('Sammelgas'); $anzahl = Bankumsatz::mehrereLoeschen([$eins, $drei]); $this->assertEquals(2, $anzahl, 'Zwei ausgewählte Umsätze gelöscht'); $this->assert(!$this->existiert($eins), 'Der erste ausgewählte Umsatz ist weg'); $this->assert(!$this->existiert($drei), 'Der zweite ausgewählte Umsatz ist weg'); $this->assert($this->existiert($zwei), 'Der nicht ausgewählte Umsatz steht noch da'); $offene = array_map('intval', array_column(Bankumsatz::liste(['status' => 'offen']), 'id')); $this->assert(!in_array($eins, $offene, true), 'Der gelöschte Umsatz steht nicht mehr in der Liste'); $this->assert(in_array($zwei, $offene, true), 'Der übrige Umsatz steht weiter zum Abgleich bereit'); } /** * Der Unterschied zum Kontoblatt: Hier wird die abgerufene Zeile gelöscht, * nicht die Buchung. Was einmal gebucht ist, bleibt gebucht. */ private function buchungUeberlebtDenGeloeschtenUmsatz(): void { $umsatz = $this->umsatzAnlegen('Sammelmiete', -750.00); $buchungId = Bankumsatz::uebernehmen($umsatz, []); $this->assertEquals(1, Bankumsatz::mehrereLoeschen([$umsatz]), 'Der übernommene Umsatz wurde gelöscht'); $this->assert(!$this->existiert($umsatz), 'Der übernommene Umsatz ist weg'); $buchung = Buchung::find((int)$buchungId); $this->assertNotEmpty($buchung, 'Die Buchung steht weiter im Kontoblatt'); $this->assertEquals(-750.00, (float)$buchung['betrag'], 'Und zwar unverändert'); } /** Der Knopf steht in jedem Reiter – offen, übernommen und verworfen. */ private function jederStatusLaesstSichLoeschen(): void { $offen = $this->umsatzAnlegen('Sammelversicherung', -44.00); $uebernommen = $this->umsatzAnlegen('Sammelrundfunk', -55.08); $verworfen = $this->umsatzAnlegen('Sammelfehlbuchung', -1.00); Bankumsatz::uebernehmen($uebernommen, []); Bankumsatz::verwerfen($verworfen); $anzahl = Bankumsatz::mehrereLoeschen([$offen, $uebernommen, $verworfen]); $this->assertEquals(3, $anzahl, 'Aus jedem Reiter lässt sich löschen'); $this->assert(!$this->existiert($offen), 'Offener Umsatz gelöscht'); $this->assert(!$this->existiert($uebernommen), 'Übernommener Umsatz gelöscht'); $this->assert(!$this->existiert($verworfen), 'Verworfener Umsatz gelöscht'); } /** * Ohne Auswahl darf nichts geschehen – der Controller weist den leeren Fall * ab, das Modell darf ihn trotzdem nicht als „alles löschen“ lesen. */ private function leereUndUnbekannteAuswahlBleibtFolgenlos(): void { $bleibt = $this->umsatzAnlegen('Sammelabo', -9.99); $this->assertEquals(0, Bankumsatz::mehrereLoeschen([]), 'Leere Auswahl löscht nichts'); $this->assertEquals(0, Bankumsatz::mehrereLoeschen([0, -3]), 'Unbrauchbare Nummern löschen nichts'); $this->assertEquals(0, Bankumsatz::mehrereLoeschen([$bleibt + 100000]), 'Unbekannte Nummer löscht nichts'); $this->assert($this->existiert($bleibt), 'Der vorhandene Umsatz ist unangetastet'); // Doppelt angekreuzt gibt es nicht, doppelt gesendet schon – die Anzahl // in der Meldung darf dadurch nicht steigen. $this->assertEquals(1, Bankumsatz::mehrereLoeschen([$bleibt, $bleibt]), 'Doppelte Nummer zählt einmal'); } /** * Die Oberfläche: In jedem Reiter stehen die Ankreuzfelder vor den Umsätzen, * das Feld „alle wählen“ in der Titelleiste und darüber der Knopf. */ private function jedeAnsichtBietetDieAuswahlAn(): void { $offen = $this->umsatzAnlegen('Ansichtsstrom', -31.00); $uebernommen = $this->umsatzAnlegen('Ansichtsmiete', -420.00); $verworfen = $this->umsatzAnlegen('Ansichtsdublette', -2.50); Bankumsatz::uebernehmen($uebernommen, []); Bankumsatz::verwerfen($verworfen); $erwartet = ['offen' => $offen, 'uebernommen' => $uebernommen, 'verworfen' => $verworfen]; foreach ($erwartet as $status => $umsatzId) { $html = $this->ansicht($status); $this->assert( str_contains($html, 'action="/banking/umsaetze/loeschen?status=' . $status . '"'), 'Reiter „' . $status . '“: Das Löschformular kehrt in denselben Reiter zurück' ); $this->assert( str_contains($html, '>Auswahl löschen'), 'Reiter „' . $status . '“: Der Knopf „Auswahl löschen“ steht über der Liste' ); $this->assert( str_contains($html, 'class="alle-waehlen" data-waehlt="umsatzauswahl"'), 'Reiter „' . $status . '“: Die Titelleiste hat ein Feld „alle wählen“' ); $this->assert( str_contains($html, 'name="umsaetze[]" value="' . $umsatzId . '" form="umsatzauswahl"'), 'Reiter „' . $status . '“: Vor dem Umsatz steht sein Ankreuzfeld' ); } // Ohne Umsätze steht auch kein Löschknopf da – sonst zeigte die leere // Liste eine Schaltfläche, die nichts treffen kann. Bankumsatz::mehrereLoeschen(array_values($erwartet)); $this->assert( !str_contains($this->ansicht('verworfen'), '>Auswahl löschen'), 'Ohne Umsätze gibt es nichts zu löschen' ); } /** Rendert die Abgleichliste eines Reiters so, wie der Controller es täte. */ private function ansicht(string $status): string { $umsaetze = []; foreach (Bankumsatz::liste(['status' => $status]) as $umsatz) { $umsatz['vorschlag'] = $status === 'offen' ? Bankumsatz::vorschlag($umsatz) : null; $umsaetze[] = $umsatz; } $titel = 'Umsätze abgleichen'; $flash = []; $benutzer = null; $csrfToken = 'x'; $kategorien = Kategorie::baumFlach(null, true); $klassen = Klasse::alle(); $konten = Konto::alle(true); $offen = Bankumsatz::anzahlOffen(); ob_start(); include dirname(__DIR__) . '/src/Views/banking/umsaetze.php'; return (string)ob_get_clean(); } }