mandantAnlegen(); $this->depot = Konto::anlegen([ 'name' => 'Bankdepot', 'kontoart' => 'depot', 'kontonummer' => '502022353', 'anfangssaldo' => 0, 'aktiv' => 1, ]); $this->bestandUndKurs(); $this->vergleichZeigtAbweichungen(); $this->einbuchen(); $this->einbuchenMitStichtag(); $this->zweiterAbrufErsetztDenErsten(); $this->depotZuordnung(); $this->nachweisMitPruefsumme(); } private function position(string $isin, string $name, float $stueck, float $kurs): Holding { return (new Holding()) ->setISIN($isin) ->setName($name) ->setAmount($stueck) ->setPrice($kurs) ->setValue($stueck * $kurs) ->setCurrency('EUR') ->setDate(new \DateTime('2026-09-30')); } private function bestandUndKurs(): void { $bekannt = Wertpapier::findenOderAnlegen([ 'name' => 'Bekannte AG', 'isin' => 'DE0001111111', 'art' => 'aktie', ]); $ergebnis = Depotabgleich::uebernehmen($this->depot, [ $this->position('DE0001111111', 'Bekannte AG', 10.0, 12.5), $this->position('DE0002222222', 'Unbekannte AG', 4.0, 100.0), ]); $this->assertEquals(2, $ergebnis['positionen'], 'Beide Positionen stehen im Bankbestand'); $this->assertEquals(1, $ergebnis['kurse'], 'Nur das bekannte Papier bekommt einen Kurs'); $this->assertEquals( 12.5, (float)Wertpapier::letzterKurs($bekannt)['kurs'], 'Der Bewertungskurs der Bank ist der Kurs des Papiers' ); $this->assertNull( Wertpapier::finde('DE0002222222'), 'Das unbekannte Papier wird nicht angelegt' ); $this->assertNotEmpty(Depotabgleich::abgerufenAm($this->depot), 'Der Abrufzeitpunkt ist vermerkt'); } private function einbuchen(): void { $e = Depotabgleich::einbuchen($this->depot); $this->assertEquals(2, $e['eingeliefert'], 'Zwei Positionen mit Mehrbestand eingeliefert'); $this->assertEquals(1, $e['ausgeliefert'], 'Das nur im Finanzmanager geführte Papier ausgeliefert'); $this->assertEquals(1, $e['angelegt'], 'Das unbekannte Papier wurde angelegt'); $this->assertNotEmpty(Wertpapier::finde('DE0002222222'), 'Neues Wertpapier existiert'); foreach (Depotabgleich::vergleich($this->depot) as $zeile) { $this->assert(abs((float)$zeile['abweichung']) < 0.000001, 'Nach dem Einbuchen stimmt ' . $zeile['name']); } $this->assertEquals( ['eingeliefert' => 0, 'ausgeliefert' => 0, 'angelegt' => 0], Depotabgleich::einbuchen($this->depot), 'Ein zweites Einbuchen ändert nichts' ); } private function einbuchenMitStichtag(): void { $depot = Konto::anlegen([ 'name' => 'Stichtagsdepot', 'kontoart' => 'depot', 'kontonummer' => '502022999', 'anfangssaldo' => 0, 'aktiv' => 1, ]); Depotabgleich::uebernehmen($depot, [$this->position('DE0004444444', 'Stichtag AG', 5.0, 20.0)]); $e = Depotabgleich::einbuchen($depot, '2025-12-31'); $this->assertEquals(1, $e['eingeliefert'], 'Der Mehrbestand wird eingeliefert'); $buchungen = Depotbuchung::liste(['konto_id' => $depot]); $this->assertEquals(1, count($buchungen), 'Eine Depotbuchung entstanden'); $this->assertEquals('2025-12-31', substr((string)$buchungen[0]['datum'], 0, 10), 'Die Einlieferung trägt den Stichtag'); $this->assert(str_contains((string)$buchungen[0]['notiz'], '30.09.2026'), 'Die Notiz nennt den Bankstand'); } private function vergleichZeigtAbweichungen(): void { $bekannt = Wertpapier::finde('DE0001111111'); Depotbuchung::anlegen([ 'konto_id' => $this->depot, 'wertpapier_id' => (int)$bekannt['id'], 'vorgang' => 'kauf', 'datum' => '2026-01-05', 'stueck' => 8, 'kurs' => 10, 'betrag' => 80, ]); $verkauftes = Wertpapier::findenOderAnlegen([ 'name' => 'Alte AG', 'isin' => 'DE0003333333', 'art' => 'aktie', ]); Depotbuchung::anlegen([ 'konto_id' => $this->depot, 'wertpapier_id' => $verkauftes, 'vorgang' => 'kauf', 'datum' => '2026-01-05', 'stueck' => 3, 'kurs' => 10, 'betrag' => 30, ]); $je = []; foreach (Depotabgleich::vergleich($this->depot) as $zeile) { $je[$zeile['isin']] = $zeile; } $this->assertEquals(2.0, $je['DE0001111111']['abweichung'], 'Die Bank hat 2 Stück mehr als gebucht'); $this->assertEquals(4.0, $je['DE0002222222']['abweichung'], 'Nur bei der Bank: ganze Position abweichend'); $this->assertNull($je['DE0002222222']['eigen'], 'Für das unbekannte Papier gibt es keinen eigenen Bestand'); $this->assertEquals(-3.0, $je['DE0003333333']['abweichung'], 'Nur im Finanzmanager: Bank führt es nicht'); $this->assertNull($je['DE0003333333']['bank'], 'Bei der Bank fehlt das Papier'); } private function zweiterAbrufErsetztDenErsten(): void { Depotabgleich::uebernehmen($this->depot, [ $this->position('DE0001111111', 'Bekannte AG', 8.0, 13.0), ]); $bank = array_filter( Depotabgleich::vergleich($this->depot), static fn(array $zeile): bool => $zeile['bank'] !== null ); $this->assertEquals(1, count($bank), 'Der neue Abruf ersetzt den alten Bankbestand'); } private function depotZuordnung(): void { $zugang = Bankzugang::anlegen([ 'name' => 'Depotbank', 'blz' => '12030000', 'fints_url' => 'https://example.org/fints', 'benutzerkennung' => 'kennung', 'pin' => '1234', ]); $giro = Bankzugang::findeOderLegeKontoAn($zugang, 'DE02120300000000202051', '202051', 'Giro'); $bankdepot = Bankzugang::findeOderLegeDepotAn($zugang, '0502022353', 'Depot'); $this->assertEquals('depot', $bankdepot['art'], 'Das Depot der Bank ist als Depot vermerkt'); $this->assertEquals( $this->depot, (int)$bankdepot['konto_id'], 'Passt die Kontonummer (auch mit führender Null), wird gleich zugeordnet' ); $this->assertEquals( (int)$bankdepot['id'], (int)Bankzugang::findeOderLegeDepotAn($zugang, '0502022353')['id'], 'Ein zweites Erkennen legt nichts doppelt an' ); $this->assertEquals( [(int)$giro['id']], array_map(static fn(array $z): int => (int)$z['id'], Bankzugang::konten($zugang)), 'Der Umsatzabruf sieht das Depot nicht' ); $this->assertEquals(1, count(Bankzugang::depots($zugang)), 'Das Depot steht in der Depotliste'); $unbekannt = Bankzugang::findeOderLegeDepotAn($zugang, '999'); $this->assertNull($unbekannt['konto_id'], 'Ohne passende Kontonummer bleibt das Depot unzugeordnet'); Bankzugang::ordneKontoZu((int)$unbekannt['id'], $this->depot, true); $this->assertEquals( $this->depot, (int)Bankzugang::depots($zugang)[1]['konto_id'], 'Die Zuordnung lässt sich von Hand treffen' ); } private function nachweisMitPruefsumme(): void { $verrechnung = Konto::anlegen([ 'name' => 'Nachweisgiro', 'kontoart' => 'bank', 'anfangssaldo' => 100, 'aktiv' => 1, ]); $auftragId = Zahlungsauftrag::anlegen([ 'konto_id' => $verrechnung, 'empfaenger' => 'Beweis GmbH', 'iban' => 'DE02120300000000202051', 'betrag' => 12.34, 'verwendungszweck' => 'Rechnung 42', ]); $dialog = "> HNHBK:1:3+000000000100'\n< HIRMG:2:2+0010::Nachricht entgegengenommen'"; $id = Bankprotokoll::speichern(null, 'ueberweisung', 'erfolg', 'übergeben', $dialog, $auftragId); $this->assertNotEmpty($id, 'Das Protokoll wird gespeichert'); $auftrag = Zahlungsauftrag::find($auftragId); $nachweis = Bankprotokoll::nachweis($auftrag); $this->assert(str_contains($nachweis, 'Beweis GmbH'), 'Der Nachweis nennt den Empfänger'); $this->assert(str_contains($nachweis, $dialog), 'Der Nachweis enthält den vollständigen Dialog'); $this->assert(str_contains($nachweis, hash('sha256', $dialog)), 'Der Nachweis nennt die Prüfsumme'); $this->assert(str_contains($nachweis, 'unverändert seit dem Speichern'), 'Unveränderter Text wird bestätigt'); \Finanzmanager\Database::execute( 'UPDATE bank_protokolle SET protokoll = ? WHERE id = ?', [$dialog . "\nnachträglich", $id] ); $this->assert( str_contains(Bankprotokoll::nachweis($auftrag), 'ABWEICHUNG'), 'Ein nachträglich geänderter Text fällt im Nachweis auf' ); } }