Fix n.n.: Kontoblatt zeigt IBAN und Bank der Gegenseite im Verwendungszweck-Tooltip

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
fds2610-bot 2026-09-27 15:46:51 +02:00
parent e02bac22c0
commit 2223ab4e06
9 changed files with 285 additions and 11 deletions

View file

@ -1,5 +1,24 @@
# Änderungen # Änderungen
## IBAN und Bank der Gegenseite im Kontoblatt (27.09.2026)
Bei einer Buchung aus dem Bankabruf ließ sich oft nicht mehr erkennen, woher sie
stammt – der Empfängername allein reicht nicht immer (z. B. bei Überweisungen
zwischen eigenen Konten verschiedener Banken).
- `Services\FinTsAbruf` löst IBAN/Kontonummer und Bankleitzahl bzw. BIC der Gegenseite
schon beim Abruf gegen das Bankleitzahlen-Verzeichnis auf (neu:
`Bankinstitut::findeNachBic()` / `findeNachBicOderBlz()`) und schreibt beides nach
`bank_umsaetze.gegen_iban` / `gegen_bankname`.
- `Bankumsatz::uebernehmen()` trägt beides auf die neue Buchung ein (`buchungen.iban`,
`buchungen.bankname`), Migration `020_buchung_herkunft.sql`.
- Im Kontoblatt zeigt der Verwendungszweck beim Mouseover IBAN und Bank, sofern
bekannt (`kontoblatt.js`, `data-herkunft`).
- Gilt nur für künftige Bankabrufe – bereits vorhandene Buchungen aus früheren
Abrufen (auch die den Anstoß gebende Buchung 31178) kennen die Gegenseite nicht
nachträglich, weil sie beim damaligen Abruf nicht mitgeschrieben wurde.
- Test in `tests/45_BuchungHerkunftTest.php`.
## Umbuchung wird auch von der Gegenkontoseite erkannt (27.09.2026) ## Umbuchung wird auch von der Gegenkontoseite erkannt (27.09.2026)
Eine Umbuchung gehört ihrem Ursprungskonto; der automatische Abgleich suchte eine Eine Umbuchung gehört ihrem Ursprungskonto; der automatische Abgleich suchte eine

View file

@ -0,0 +1,20 @@
-- IBAN und Bank der Gegenseite, soweit der Bankabruf sie liefert.
--
-- MT940 und CAMT nennen zur Gegenseite einer Buchung deren Kontonummer (bei
-- CAMT die IBAN) und deren Bankleitzahl bzw. BIC – bisher landete das nirgends,
-- Nutzer sahen im Kontoblatt nur den Namen. Bei Buchung 31178 („Felix David
-- Schepmann“) reichte der Name nicht, um die Herkunft zu erkennen.
--
-- Aufgelöst wird der Bankname schon beim Abruf (Services\FinTsAbruf) über das
-- Bankleitzahlenverzeichnis (Models\Bankinstitut) – nicht erst bei jeder
-- Anzeige im Kontoblatt.
ALTER TABLE bank_umsaetze ADD COLUMN IF NOT EXISTS
gegen_iban VARCHAR(34) NOT NULL DEFAULT '' AFTER verwendungszweck;
ALTER TABLE bank_umsaetze ADD COLUMN IF NOT EXISTS
gegen_bankname VARCHAR(128) NOT NULL DEFAULT '' AFTER gegen_iban;
ALTER TABLE buchungen ADD COLUMN IF NOT EXISTS
iban VARCHAR(34) NOT NULL DEFAULT '' AFTER empfaenger;
ALTER TABLE buchungen ADD COLUMN IF NOT EXISTS
bankname VARCHAR(128) NOT NULL DEFAULT '' AFTER iban;

View file

@ -28,15 +28,29 @@
* *
* Kategorie und Klasse tragen ihren Tooltip vom Server; er nennt bei * Kategorie und Klasse tragen ihren Tooltip vom Server; er nennt bei
* Splitbuchungen mehr, als in der Zelle steht. * Splitbuchungen mehr, als in der Zelle steht.
*
* Der Buchungstext trägt zusätzlich `data-herkunft`: IBAN und Bank der
* Gegenseite, sofern der Bankabruf sie kennt. Die stehen unabhängig davon
* im Tooltip, ob der Text selbst abgeschnitten ist – sie stecken sonst
* nirgends in der Zelle.
*/ */
function tooltipsSetzen() { function tooltipsSetzen() {
tabelle.querySelectorAll('.zellentext').forEach(function (kasten) { tabelle.querySelectorAll('.zellentext').forEach(function (kasten) {
const abgeschnitten = kasten.scrollHeight > kasten.clientHeight + 1 const abgeschnitten = kasten.scrollHeight > kasten.clientHeight + 1
|| kasten.scrollWidth > kasten.clientWidth + 1; || kasten.scrollWidth > kasten.clientWidth + 1;
const text = kasten.textContent.trim(); const text = kasten.textContent.trim();
const herkunft = kasten.dataset.herkunft || '';
const zeilen = [];
if (abgeschnitten && text !== '') { if (abgeschnitten && text !== '') {
kasten.title = text; zeilen.push(text);
}
if (herkunft !== '') {
zeilen.push(herkunft);
}
if (zeilen.length > 0) {
kasten.title = zeilen.join('\n');
} else { } else {
kasten.removeAttribute('title'); kasten.removeAttribute('title');
} }

View file

@ -71,6 +71,48 @@ class Bankinstitut
return $zeile === false ? null : $zeile; return $zeile === false ? null : $zeile;
} }
/**
* Der Eintrag zu einer BIC – für die Gegenseite eines FinTS-Umsatzes:
* CAMT nennt dort meist die BIC statt der Bankleitzahl.
*
* @return array<string,mixed>|null
*/
public static function findeNachBic(string $bic): ?array
{
$bic = strtoupper(trim($bic));
if ($bic === '') {
return null;
}
$zeile = Database::fetchOne(
"SELECT * FROM bankinstitute
WHERE bic = ?
ORDER BY (pintan_url <> '') DESC, nr
LIMIT 1",
[$bic]
);
return $zeile === false ? null : $zeile;
}
/**
* Der Eintrag zur Gegenseite eines Umsatzes, deren Kennung mal eine
* Bankleitzahl ist (MT940), mal eine BIC (CAMT) – nur Ziffern
* unterscheidet zuverlässig zwischen beiden, eine BIC beginnt immer mit
* vier Buchstaben.
*
* @return array<string,mixed>|null
*/
public static function findeNachBicOderBlz(string $code): ?array
{
$code = trim($code);
if ($code === '') {
return null;
}
return ctype_digit($code) ? self::findeNachBlz($code) : self::findeNachBic($code);
}
/** /**
* Suche über Bankleitzahl, BIC, Name und Ort. * Suche über Bankleitzahl, BIC, Name und Ort.
* *

View file

@ -91,8 +91,8 @@ class Bankumsatz
Database::execute( Database::execute(
'INSERT INTO bank_umsaetze 'INSERT INTO bank_umsaetze
(benutzer_id, zugang_id, konto_id, bank_konto_id, extern_id, buchungstag, wertstellung, (benutzer_id, zugang_id, konto_id, bank_konto_id, extern_id, buchungstag, wertstellung,
betrag, waehrung, empfaenger, verwendungszweck, buchungsart, roh) betrag, waehrung, empfaenger, verwendungszweck, gegen_iban, gegen_bankname, buchungsart, roh)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
[ [
Mandant::id(), Mandant::id(),
(int)$daten['zugang_id'], (int)$daten['zugang_id'],
@ -107,6 +107,8 @@ class Bankumsatz
mb_substr((string)($daten['waehrung'] ?? 'EUR'), 0, 3), mb_substr((string)($daten['waehrung'] ?? 'EUR'), 0, 3),
mb_substr(trim((string)($daten['empfaenger'] ?? '')), 0, 255), mb_substr(trim((string)($daten['empfaenger'] ?? '')), 0, 255),
(string)($daten['verwendungszweck'] ?? ''), (string)($daten['verwendungszweck'] ?? ''),
mb_substr(strtoupper(trim((string)($daten['gegen_iban'] ?? ''))), 0, 34),
mb_substr(trim((string)($daten['gegen_bankname'] ?? '')), 0, 128),
mb_substr(trim((string)($daten['buchungsart'] ?? '')), 0, 100), mb_substr(trim((string)($daten['buchungsart'] ?? '')), 0, 100),
($daten['roh'] ?? null) ?: null, ($daten['roh'] ?? null) ?: null,
] ]
@ -286,6 +288,8 @@ class Bankumsatz
'betrag' => (float)$umsatz['betrag'], 'betrag' => (float)$umsatz['betrag'],
'empfaenger' => (string)$umsatz['empfaenger'], 'empfaenger' => (string)$umsatz['empfaenger'],
'buchungstext' => (string)$umsatz['verwendungszweck'], 'buchungstext' => (string)$umsatz['verwendungszweck'],
'iban' => (string)($umsatz['gegen_iban'] ?? ''),
'bankname' => (string)($umsatz['gegen_bankname'] ?? ''),
'kategorie_id' => $zuordnung['kategorie_id'] ?? null, 'kategorie_id' => $zuordnung['kategorie_id'] ?? null,
'klasse_id' => $zuordnung['klasse_id'] ?? null, 'klasse_id' => $zuordnung['klasse_id'] ?? null,
'gegenkonto_id' => $zuordnung['gegenkonto_id'] ?? null, 'gegenkonto_id' => $zuordnung['gegenkonto_id'] ?? null,

View file

@ -101,12 +101,13 @@ class Buchung
return (int)Database::transaktion(static function () use ($felder, $splits, $adressen): int { return (int)Database::transaktion(static function () use ($felder, $splits, $adressen): int {
Database::execute( Database::execute(
'INSERT INTO buchungen 'INSERT INTO buchungen
(benutzer_id, konto_id, datum, valuta, betrag, empfaenger, buchungstext, nummer, (benutzer_id, konto_id, datum, valuta, betrag, empfaenger, iban, bankname, buchungstext,
abgeglichen, kategorie_id, klasse_id, gegenkonto_id, gesplittet, quelle, import_hash) nummer, abgeglichen, kategorie_id, klasse_id, gegenkonto_id, gesplittet, quelle, import_hash)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
[ [
Mandant::id(), $felder['konto_id'], $felder['datum'], $felder['valuta'], Mandant::id(), $felder['konto_id'], $felder['datum'], $felder['valuta'],
$felder['betrag'], $felder['empfaenger'], $felder['buchungstext'], $felder['nummer'], $felder['betrag'], $felder['empfaenger'], $felder['iban'], $felder['bankname'],
$felder['buchungstext'], $felder['nummer'],
$felder['abgeglichen'], $felder['kategorie_id'], $felder['klasse_id'], $felder['abgeglichen'], $felder['kategorie_id'], $felder['klasse_id'],
$felder['gegenkonto_id'], $felder['gesplittet'], $felder['quelle'], $felder['import_hash'], $felder['gegenkonto_id'], $felder['gesplittet'], $felder['quelle'], $felder['import_hash'],
] ]
@ -341,21 +342,24 @@ class Buchung
SELECT b.id, b.datum, b.valuta, b.betrag, b.empfaenger, b.buchungstext, b.nummer, SELECT b.id, b.datum, b.valuta, b.betrag, b.empfaenger, b.buchungstext, b.nummer,
b.abgeglichen, b.kategorie_id, b.klasse_id, b.gesplittet, b.quelle, b.abgeglichen, b.kategorie_id, b.klasse_id, b.gesplittet, b.quelle,
b.bargeld_status, b.bargeld_hinweis, b.bargeld_status, b.bargeld_hinweis,
b.gegenkonto_id, b.konto_id, 0 AS gegensicht, NULL AS split_id b.gegenkonto_id, b.konto_id, 0 AS gegensicht, NULL AS split_id,
b.iban, b.bankname
FROM buchungen b FROM buchungen b
WHERE b.konto_id = ? WHERE b.konto_id = ?
UNION ALL UNION ALL
SELECT b.id, b.datum, b.valuta, -b.betrag, b.empfaenger, b.buchungstext, b.nummer, SELECT b.id, b.datum, b.valuta, -b.betrag, b.empfaenger, b.buchungstext, b.nummer,
b.abgeglichen, b.kategorie_id, b.klasse_id, b.gesplittet, b.quelle, b.abgeglichen, b.kategorie_id, b.klasse_id, b.gesplittet, b.quelle,
NULL AS bargeld_status, NULL AS bargeld_hinweis, NULL AS bargeld_status, NULL AS bargeld_hinweis,
b.konto_id AS gegenkonto_id, b.konto_id, 1 AS gegensicht, NULL AS split_id b.konto_id AS gegenkonto_id, b.konto_id, 1 AS gegensicht, NULL AS split_id,
\'\' AS iban, \'\' AS bankname
FROM buchungen b FROM buchungen b
WHERE b.gegenkonto_id = ? AND b.gesplittet = 0 WHERE b.gegenkonto_id = ? AND b.gesplittet = 0
UNION ALL UNION ALL
SELECT b.id, b.datum, b.valuta, -s.betrag, b.empfaenger, s.notiz, b.nummer, SELECT b.id, b.datum, b.valuta, -s.betrag, b.empfaenger, s.notiz, b.nummer,
b.abgeglichen, s.kategorie_id, s.klasse_id, 0 AS gesplittet, b.quelle, b.abgeglichen, s.kategorie_id, s.klasse_id, 0 AS gesplittet, b.quelle,
NULL AS bargeld_status, NULL AS bargeld_hinweis, NULL AS bargeld_status, NULL AS bargeld_hinweis,
b.konto_id AS gegenkonto_id, b.konto_id, 1 AS gegensicht, s.id AS split_id b.konto_id AS gegenkonto_id, b.konto_id, 1 AS gegensicht, s.id AS split_id,
\'\' AS iban, \'\' AS bankname
FROM buchung_splits s FROM buchung_splits s
JOIN buchungen b ON b.id = s.buchung_id JOIN buchungen b ON b.id = s.buchung_id
WHERE s.gegenkonto_id = ? WHERE s.gegenkonto_id = ?
@ -684,6 +688,8 @@ class Buchung
'valuta' => self::istDatum($valuta) ? $valuta : null, 'valuta' => self::istDatum($valuta) ? $valuta : null,
'betrag' => $betrag, 'betrag' => $betrag,
'empfaenger' => mb_substr(trim((string)($daten['empfaenger'] ?? '')), 0, 255), 'empfaenger' => mb_substr(trim((string)($daten['empfaenger'] ?? '')), 0, 255),
'iban' => mb_substr(strtoupper(trim((string)($daten['iban'] ?? ''))), 0, 34),
'bankname' => mb_substr(trim((string)($daten['bankname'] ?? '')), 0, 128),
'buchungstext' => trim((string)($daten['buchungstext'] ?? '')), 'buchungstext' => trim((string)($daten['buchungstext'] ?? '')),
'nummer' => mb_substr(trim((string)($daten['nummer'] ?? '')), 0, 32), 'nummer' => mb_substr(trim((string)($daten['nummer'] ?? '')), 0, 32),
'abgeglichen' => !empty($daten['abgeglichen']) ? 1 : 0, 'abgeglichen' => !empty($daten['abgeglichen']) ? 1 : 0,

View file

@ -17,6 +17,7 @@ use Fhp\Options\FinTsOptions;
use Finanzmanager\Auth; use Finanzmanager\Auth;
use Finanzmanager\Models\AbgleichRegel; use Finanzmanager\Models\AbgleichRegel;
use Finanzmanager\Models\Bankabruf; use Finanzmanager\Models\Bankabruf;
use Finanzmanager\Models\Bankinstitut;
use Finanzmanager\Models\Bankprotokoll; use Finanzmanager\Models\Bankprotokoll;
use Finanzmanager\Models\Bankumsatz; use Finanzmanager\Models\Bankumsatz;
use Finanzmanager\Models\Bankzugang; use Finanzmanager\Models\Bankzugang;
@ -387,6 +388,15 @@ class FinTsAbruf
$zweck = trim($umsatz->getMainDescription()); $zweck = trim($umsatz->getMainDescription());
$empfaenger = trim($umsatz->getName()); $empfaenger = trim($umsatz->getName());
// Die Kontonummer der Gegenseite – bei CAMT ihre IBAN, bei
// MT940 die alte Kontonummer; die Bankleitzahl bzw. BIC daneben
// löst sich sofort gegen das Institutsverzeichnis auf, statt bei
// jeder Anzeige im Kontoblatt neu nachzuschlagen.
$gegenIban = trim($umsatz->getAccountNumber());
$gegenBankname = (string)(
Bankinstitut::findeNachBicOderBlz($umsatz->getBankCode())['institut'] ?? ''
);
$externId = Bankumsatz::externId( $externId = Bankumsatz::externId(
(int)$bankkonto['id'], (int)$bankkonto['id'],
$buchungstag->format('Y-m-d'), $buchungstag->format('Y-m-d'),
@ -405,6 +415,8 @@ class FinTsAbruf
'betrag' => $betrag, 'betrag' => $betrag,
'empfaenger' => $empfaenger, 'empfaenger' => $empfaenger,
'verwendungszweck' => $zweck, 'verwendungszweck' => $zweck,
'gegen_iban' => $gegenIban,
'gegen_bankname' => $gegenBankname,
'buchungsart' => trim($umsatz->getBookingText()), 'buchungsart' => trim($umsatz->getBookingText()),
'roh' => json_encode([ 'roh' => json_encode([
'iban' => $bankkonto['iban'], 'iban' => $bankkonto['iban'],

View file

@ -1,5 +1,6 @@
<?php <?php
use Finanzmanager\Models\Adresse;
use Finanzmanager\Models\Wertpapier; use Finanzmanager\Models\Wertpapier;
use Finanzmanager\View; use Finanzmanager\View;
@ -116,6 +117,26 @@ $splituebersicht = static function (array $splitzeilen) use ($kategorieNamen, $k
$bargeldmarke = static fn(array $zeile): string $bargeldmarke = static fn(array $zeile): string
=> View::bargeldMarke($zeile['bargeld_status'] ?? null, $zeile['bargeld_hinweis'] ?? null); => View::bargeldMarke($zeile['bargeld_status'] ?? null, $zeile['bargeld_hinweis'] ?? null);
/**
* IBAN und Bank der Gegenseite, soweit der Bankabruf sie geliefert hat – der
* Tooltip über dem Verwendungszweck. kontoblatt.js hängt ihn zusätzlich zum
* (nur bei abgeschnittenem Text nötigen) vollständigen Text an; ohne
* JavaScript bleibt er unsichtbar, wie schon der Tooltip für abgeschnittenen
* Text.
*
* @param array<string,mixed> $zeile
*/
$herkunft = static function (array $zeile): string {
$teile = [];
if (($zeile['iban'] ?? '') !== '') {
$teile[] = 'IBAN ' . Adresse::ibanFormatiert((string)$zeile['iban']);
}
if (($zeile['bankname'] ?? '') !== '') {
$teile[] = (string)$zeile['bankname'];
}
return implode("\n", $teile);
};
include __DIR__ . '/../layout/kopf.php'; include __DIR__ . '/../layout/kopf.php';
?> ?>
@ -273,7 +294,7 @@ include __DIR__ . '/../layout/kopf.php';
<?php /* Ungekürzt: Was nicht auf zwei Zeilen passt, schneidet das <?php /* Ungekürzt: Was nicht auf zwei Zeilen passt, schneidet das
Stylesheet ab – der vollständige Text bleibt für den Stylesheet ab – der vollständige Text bleibt für den
Tooltip erhalten. */ ?> Tooltip erhalten. */ ?>
<td class="nebentext buchungstext"><div class="zellentext"><?= View::h($zeile['buchungstext']) ?></div></td> <td class="nebentext buchungstext"><div class="zellentext" data-herkunft="<?= View::h($herkunft($zeile)) ?>"><?= View::h($zeile['buchungstext']) ?></div></td>
<?php <?php
// Die Gegenseite einer fremden Umbuchung nennt das Konto, von dem // Die Gegenseite einer fremden Umbuchung nennt das Konto, von dem
// sie kommt – und zwar dort, wo sonst die Kategorie steht. Ohne // sie kommt – und zwar dort, wo sonst die Kategorie steht. Ohne

View file

@ -0,0 +1,136 @@
<?php
declare(strict_types=1);
use Finanzmanager\Models\Bankinstitut;
use Finanzmanager\Models\Bankumsatz;
use Finanzmanager\Models\Bankzugang;
use Finanzmanager\Models\Buchung;
use Finanzmanager\Models\Konto;
/**
* IBAN und Bank der Gegenseite: Der Bankabruf löst die Bankleitzahl bzw. BIC
* der Gegenseite schon beim Schreiben des Umsatzes gegen das Institutsverzeichnis
* auf (Models\Bankinstitut); beim Übernehmen wandert beides auf die Buchung, wo
* das Kontoblatt es im Tooltip über dem Verwendungszweck zeigt.
*/
class BuchungHerkunftTest extends TestCase
{
protected string $testName = 'Test 45: IBAN und Bank der Gegenseite';
private int $giro = 0;
private int $zugang = 0;
public function run(): void
{
$this->mandantAnlegen();
$this->giro = Konto::anlegen([
'name' => 'Herkunftsgiro', 'kontoart' => 'bank', 'anfangssaldo' => 0, 'aktiv' => 1,
]);
$this->zugang = Bankzugang::anlegen([
'name' => 'Herkunftsbank',
'bankname' => 'Herkunftsbank AG',
'blz' => '12030000',
'fints_url' => 'https://banking.example.org/fints',
'benutzerkennung' => 'testkunde',
'pin' => 'geheim1234',
'abrufzeitraum' => 'letzter_abruf',
'aktiv' => 1,
]);
$this->findeNachBicUndBlz();
$this->uebernehmenTraegtHerkunftEin();
$this->ohneGegenseiteBleibtLeer();
}
private function findeNachBicUndBlz(): void
{
Bankinstitut::ersetze([[
'nr' => 1, 'blz' => '12030000', 'bic' => 'BYLADEM1001',
'institut' => 'Deutsche Kreditbank Testinstitut', 'ort' => 'Berlin',
'rechenzentrum' => '', 'organisation' => '', 'hbci_adresse' => '',
'hbci_version' => '', 'pintan_url' => '', 'pintan_version' => '', 'stand' => null,
]]);
$nachBic = Bankinstitut::findeNachBic('BYLADEM1001');
$this->assertNotEmpty($nachBic, 'Die BIC findet das Institut');
$this->assertEquals('Deutsche Kreditbank Testinstitut', $nachBic['institut'], 'Mit dem richtigen Namen');
$this->assertEquals(
'Deutsche Kreditbank Testinstitut',
Bankinstitut::findeNachBicOderBlz('12030000')['institut'] ?? '',
'Eine reine Ziffernfolge gilt als Bankleitzahl'
);
$this->assertEquals(
'Deutsche Kreditbank Testinstitut',
Bankinstitut::findeNachBicOderBlz('BYLADEM1001')['institut'] ?? '',
'Buchstaben am Anfang gelten als BIC'
);
$this->assertNull(Bankinstitut::findeNachBicOderBlz(''), 'Ohne Angabe gibt es keinen Treffer');
$this->assertNull(Bankinstitut::findeNachBic('XXXXXXXX'), 'Eine unbekannte BIC bleibt ohne Treffer');
Bankinstitut::leeren();
}
private function uebernehmenTraegtHerkunftEin(): void
{
$umsatzId = Bankumsatz::anlegen([
'zugang_id' => $this->zugang,
'konto_id' => $this->giro,
'extern_id' => 'test-herkunft-1',
'buchungstag' => '2026-09-07',
'wertstellung' => '2026-09-07',
'betrag' => -5000.00,
'empfaenger' => 'Felix David Schepmann',
'verwendungszweck' => 'Privatentnahme',
'gegen_iban' => 'de36120300001010903027',
'gegen_bankname' => 'Deutsche Kreditbank Testinstitut',
]);
$this->assertNotEmpty($umsatzId, 'Der Umsatz wurde angelegt');
$umsatz = Bankumsatz::find((int)$umsatzId);
$this->assertEquals(
'DE36120300001010903027',
$umsatz['gegen_iban'],
'Die IBAN steht in Großbuchstaben im Umsatz'
);
$buchungId = Bankumsatz::uebernehmen((int)$umsatzId, []);
$buchung = Buchung::find($buchungId);
$this->assertEquals('DE36120300001010903027', $buchung['iban'], 'Die IBAN steht auf der Buchung');
$this->assertEquals(
'Deutsche Kreditbank Testinstitut',
$buchung['bankname'],
'Und der aufgelöste Bankname daneben'
);
$zeilen = Buchung::kontoblatt($this->giro);
$gefunden = null;
foreach ($zeilen as $zeile) {
if ((int)$zeile['id'] === $buchungId) {
$gefunden = $zeile;
}
}
$this->assertNotEmpty($gefunden, 'Die Buchung steht im Kontoblatt');
$this->assertEquals('DE36120300001010903027', $gefunden['iban'], 'Mit IBAN im Kontoblatt');
$this->assertEquals(
'Deutsche Kreditbank Testinstitut',
$gefunden['bankname'],
'Und Bankname im Kontoblatt'
);
}
private function ohneGegenseiteBleibtLeer(): void
{
$buchungId = Buchung::anlegen([
'konto_id' => $this->giro, 'datum' => '2026-09-08', 'betrag' => -12.00,
'empfaenger' => 'Kiosk', 'quelle' => 'manuell',
]);
$buchung = Buchung::find($buchungId);
$this->assertEquals('', $buchung['iban'], 'Ohne Bankabruf bleibt die IBAN leer');
$this->assertEquals('', $buchung['bankname'], 'Und der Bankname auch');
}
}