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>
170 lines
8 KiB
PHP
170 lines
8 KiB
PHP
<?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'; ?>
|