Finanzmanager/src/Views/banking/konten.php
fds2610-bot 78f1b91bc0 Online-Banking: Kontenzuordnung, Sammellöschen und drei Umsatz-Fehler
Kontenzuordnung als dritter Einrichtungsschritt: Welches Bankkonto gehört zu
welchem eigenen Konto? Umsätze merken sich über `bank_konto_id`, woher sie
stammen, und ziehen mit, wenn die Zuordnung erst nach einem Abruf fällt.
Abgerufene Umsätze buchen sich auf Wunsch selbst (`Services\Bankabgleich`).

Im Kontoblatt lassen sich Buchungen sammelweise löschen; Statusmeldungen liegen
jetzt über der Ansicht, statt den Inhalt springen zu lassen.

Dazu drei Fehler, die zusammen dafür sorgten, dass abgerufene Umsätze spurlos
verschwanden – der Abruf meldete sie als vorhanden, das Kontoblatt zeigte
nichts, und in der Vorschlagsliste standen sie ebenfalls nicht:

- Eine gelöschte Buchung ließ ihren Umsatz auf „uebernommen“ stehen. Der
  Fremdschlüssel setzte nur `buchung_id` auf NULL. `Buchung::loeschen()` gibt
  den Umsatz jetzt vorher frei, `Bankumsatz::verwaisteOeffnen()` fängt die Wege
  ab, die daran vorbeiführen – Kaskaden lösen in MariaDB keine Trigger aus.
- Ein Bankkonto entstand doppelt, wenn die Bank die IBAN nicht bei jedem Dialog
  mitliefert. Gesucht wird jetzt über die Kontonummer; eine nachgelieferte IBAN
  wird am vorhandenen Eintrag nachgetragen.
- Der Fingerabdruck hing an der IBAN und kippte mit ihr. Er nimmt jetzt die
  `bank_konto_id`, die fest steht, solange es den Bankzugang gibt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 11:54:23 +02:00

170 lines
8 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
use Finanzmanager\Controllers\BankingController;
use Finanzmanager\Models\Konto;
use Finanzmanager\View;
/**
* Schritt 3 der Einrichtung: Welches Bankkonto gehört zu welchem eigenen Konto?
*
* Die Liste links kommt von der Bank, die Auswahl rechts aus der eigenen
* Kontenliste. Fehlt dort das passende Konto, legt „neues Konto anlegen“ es an –
* IBAN und Bank stehen schon fest, einzutragen bleibt der Name.
*
* @var array<string,mixed> $zugang
* @var list<array<string,mixed>> $konten Bankkonten dieses Zugangs
* @var list<array<string,mixed>> $eigene Konten der Anwendung
* @var bool $einrichtung Teil des Einrichtungsablaufs?
* @var string $csrfToken
*/
$zugangId = (int)$zugang['id'];
$bank = (string)($zugang['bankname'] !== '' ? $zugang['bankname'] : $zugang['name']);
include __DIR__ . '/../layout/kopf.php';
?>
<?php if ($einrichtung): ?>
<p class="schrittmarke">Schritt 3 von 3 · Konten zuordnen</p>
<?php endif; ?>
<div class="seitenaktionen">
<?php /* Wer die Einrichtung hier verlässt, landet in der Übersicht – dort
steht die Verbindung fertig zwischen den anderen. Beim einzelnen
Aufruf führt der Weg zurück, woher er kam. */ ?>
<a href="<?= $einrichtung ? '/banking' : '/banking/' . $zugangId ?>" class="knopf">
<?= $einrichtung ? 'Später zuordnen' : 'Zurück zur Bankverbindung' ?>
</a>
<form method="post" action="/banking/<?= $zugangId ?>/konten/abfragen" class="inline">
<input type="hidden" name="csrf_token" value="<?= View::h($csrfToken) ?>">
<input type="hidden" name="einrichtung" value="<?= $einrichtung ? '1' : '0' ?>">
<button type="submit" class="knopf">
<?= $konten === [] ? 'Konten bei der Bank abfragen' : 'Erneut bei der Bank abfragen' ?>
</button>
</form>
</div>
<?php if ($konten === []): ?>
<p class="platzhalter">
Noch kein Konto bekannt. Die Abfrage meldet sich bei der Bank an – dafür
verlangen die meisten Häuser eine TAN.
</p>
<?php else: ?>
<p class="feldhinweis">
Jedes Bankkonto braucht ein eigenes Konto, sonst lassen sich seine Umsätze nicht
buchen. Was nicht abgerufen werden soll, hier abwählen. Schon abgerufene Umsätze
eines Kontos ziehen mit der Zuordnung nach.
</p>
<form method="post" action="/banking/<?= $zugangId ?>/konten" class="formular breit">
<input type="hidden" name="csrf_token" value="<?= View::h($csrfToken) ?>">
<input type="hidden" name="einrichtung" value="<?= $einrichtung ? '1' : '0' ?>">
<table class="liste">
<thead>
<tr>
<th>Bei der Bank</th>
<th>Eigenes Konto</th>
<th class="rechts">Saldo</th>
<th>Abrufen</th>
</tr>
</thead>
<tbody>
<?php foreach ($konten as $bankkonto): ?>
<?php
$id = (int)$bankkonto['id'];
$kennung = (string)($bankkonto['iban'] ?: $bankkonto['kontonummer']);
// Namensvorschlag für ein neues Konto: Bank und die letzten vier
// Stellen – „Sparkasse 2051“ ist in der Kontenliste eindeutiger
// als eine nackte IBAN.
$endung = substr($kennung, -4);
$vorschlag = trim($bank . ($endung === '' ? '' : ' ' . $endung));
?>
<tr>
<td>
<?= View::h($kennung) ?>
<?php if ($bankkonto['bezeichnung'] !== '' && $bankkonto['bezeichnung'] !== $kennung): ?>
<span class="nebentext"><?= View::h($bankkonto['bezeichnung']) ?></span>
<?php endif; ?>
</td>
<td>
<select name="bankkonto[<?= $id ?>][konto_id]"
data-neues-konto="neues-konto-<?= $id ?>">
<option value="">– nicht zugeordnet –</option>
<?php foreach ($eigene as $konto): ?>
<option value="<?= (int)$konto['id'] ?>"
<?= (int)$bankkonto['konto_id'] === (int)$konto['id'] ? 'selected' : '' ?>>
<?= View::h($konto['name']) ?>
</option>
<?php endforeach; ?>
<option value="<?= View::h(BankingController::KONTO_NEU) ?>">
+ neues Konto anlegen
</option>
</select>
</td>
<td class="rechts">
<?= $bankkonto['saldo'] === null
? '<span class="nebentext">–</span>'
: View::betrag($bankkonto['saldo']) ?>
</td>
<td>
<label class="ankreuzfeld">
<input type="checkbox" name="bankkonto[<?= $id ?>][abrufen]" value="1"
<?= (int)$bankkonto['abrufen'] === 1 ? 'checked' : '' ?>>
ja
</label>
</td>
</tr>
<?php /* Die Felder des neuen Kontos stehen in einer eigenen Zeile:
In der Hauptzeile wären es vier Spalten mehr, die in den
meisten Fällen leer blieben. app.js blendet sie ein,
sobald „neues Konto anlegen“ gewählt ist. */ ?>
<tr class="unterzeile" id="neues-konto-<?= $id ?>">
<td></td>
<td colspan="3" class="neues-konto">
<label for="neuer-name-<?= $id ?>">Name</label>
<input type="text" id="neuer-name-<?= $id ?>"
name="bankkonto[<?= $id ?>][neuer_name]"
value="<?= View::h($vorschlag) ?>">
<label for="neue-kontoart-<?= $id ?>">Kontoart</label>
<select id="neue-kontoart-<?= $id ?>" name="bankkonto[<?= $id ?>][neue_kontoart]">
<?php foreach (Konto::ARTEN as $schluessel => $beschriftung): ?>
<option value="<?= View::h($schluessel) ?>"
<?= $schluessel === 'bank' ? 'selected' : '' ?>>
<?= View::h($beschriftung) ?>
</option>
<?php endforeach; ?>
</select>
<label for="anfangssaldo-<?= $id ?>">Anfangssaldo</label>
<input type="text" id="anfangssaldo-<?= $id ?>"
name="bankkonto[<?= $id ?>][anfangssaldo]" value="0,00" inputmode="decimal">
<span class="nebentext">
IBAN <?= View::h($kennung) ?> und Bank werden übernommen. Der Anfangssaldo
deckt die Zeit ab, die der Abruf nicht mehr hergibt.
</span>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<div class="knopfleiste">
<button type="submit" class="knopf primaer">
<?= $einrichtung ? 'Einrichtung abschließen' : 'Zuordnung speichern' ?>
</button>
</div>
</form>
<p class="hinweis">
Nach dem Abruf bucht der Finanzmanager die Umsätze selbst in das zugeordnete Konto –
verbunden mit einer schon erfassten Buchung, wenn eine passt, sonst als neue Buchung
mit der Kategorie aus der Regel. Konten mit Buchungen ohne Kategorie stehen in der
Kontenübersicht fett. Abschalten lässt sich das in den
<a href="/einstellungen?bereich=banking">Einstellungen</a>.
</p>
<?php endif; ?>
<?php include __DIR__ . '/../layout/fuss.php'; ?>