Damit ist der geplante Funktionsumfang beisammen. Phase 4 abgeschlossen - Konfigurierbare Cockpit-Kacheln; abgewählte werden nicht berechnet - Einnahmen/Ausgaben je Monat als Balkenpaare mit Saldolinie, aufgesetzt auf den Summenbericht statt auf eine eigene Abfrage - Meine Ausgaben als Ring auf der obersten Kategorieebene - Offene Vorgänge aus allen Modulen Phase 5 – Online-Banking - nemiah/php-fints als Fork auf Codeberg, psr/log direkt eingebaut: die Anwendung hängt an keinem fremden Update-Zyklus - PIN AES-256-GCM-verschlüsselt; Schlüssel aus APP_KEY oder aus data/schluessel/app_key, den der Entrypoint einmalig erzeugt - TAN-Dialog über mehrere Requests (FinTs::persist plus serialisierte Aktion in der Sitzung) – ein wartender Prozess wäre mit php-fpm der falsche Weg - Abgleich statt Direktbuchung: verbinden, übernehmen oder verwerfen - Regeln mit vier Vergleichsarten, Betragsgrenzen und Split-Schema Phase 6 – Wertpapiere - Depotbuchung und Geldbuchung als zwei Seiten eines Geschäfts; betrag ist der Bruttowert, Nebenkosten stehen daneben - Einstand nach gleitendem Durchschnitt, realisierte Gewinne, Erträge - Kursabruf über Leeway oder FMP, täglicher Lauf per Cron - QIF-Import für !Type:Invst Dabei behoben: Der Import buchte den Geldfluss jedes Wertpapiergeschäfts als Abgang – auch bei Verkäufen und Dividenden; und er erkannte das Verrechnungskonto nicht, wenn dem Konto im L-Feld eine Kategorie vorangeht (bei Verkäufen also immer). Phase 7 – Nebenmodule und Zahlungsverkehr - Buchungsvorlagen inklusive QIF-Import, Splits werden mitskaliert - Regelmäßige Buchungen mit Fälligkeitslauf, Finanzkalender, Erinnerungen - Adressen mit IBAN-Prüfung, Verträge mit Kündigungsfrist, Sparziele - Kredite mit gerechnetem Tilgungsplan - Überweisungen über FinTS; die Buchung entsteht erst nach Annahme durch die Bank. Die pain.001-Nachricht erzeugt SepaXml selbst Nebenbei: .dockerignore ergänzt (ohne sie brach der Build an data/mariadb ab), PlatzhalterController entfernt, zwei Inline-Handler beseitigt, die die Content-Security-Policy ohnehin blockierte. 888 PHP-Prüfungen und 45 E2E-Tests grün. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
173 lines
7.5 KiB
PHP
173 lines
7.5 KiB
PHP
<?php
|
||
|
||
use Finanzmanager\Models\Adresse;
|
||
use Finanzmanager\View;
|
||
|
||
/**
|
||
* @var array<string,mixed>|null $auftrag
|
||
* @var list<array<string,mixed>> $konten
|
||
* @var list<array<string,mixed>> $zugaenge
|
||
* @var list<array<string,mixed>> $adressen
|
||
* @var list<array<string,mixed>> $kategorien
|
||
* @var list<array<string,mixed>> $klassen
|
||
* @var string $csrfToken
|
||
*/
|
||
|
||
$neu = $auftrag === null;
|
||
$ziel = $neu ? '/zahlungsverkehr/neu' : '/zahlungsverkehr/' . (int)$auftrag['id'];
|
||
$wert = static fn(string $feld, string $standard = ''): string => (string)($auftrag[$feld] ?? $standard);
|
||
|
||
include __DIR__ . '/../layout/kopf.php';
|
||
?>
|
||
|
||
<div class="seitenaktionen">
|
||
<a href="/zahlungsverkehr" class="knopf">Zurück zur Übersicht</a>
|
||
<?php if (!$neu && $auftrag['status'] !== 'gesendet'): ?>
|
||
<form method="post" action="/zahlungsverkehr/<?= (int)$auftrag['id'] ?>/senden" class="inline"
|
||
data-bestaetigen="Überweisung jetzt an die Bank senden?">
|
||
<input type="hidden" name="csrf_token" value="<?= View::h($csrfToken) ?>">
|
||
<button type="submit" class="knopf primaer">An die Bank senden</button>
|
||
</form>
|
||
<?php endif; ?>
|
||
</div>
|
||
|
||
<?php if (!$neu && $auftrag['status'] === 'gesendet'): ?>
|
||
<p class="meldung meldung-erfolg" role="status">
|
||
Am <?= View::h(date('d.m.Y H:i', strtotime((string)$auftrag['gesendet_am']))) ?> an die Bank
|
||
übergeben. Ein gesendeter Auftrag lässt sich nicht mehr ändern.
|
||
</p>
|
||
<?php elseif (!$neu && ($auftrag['meldung'] ?? '') !== ''): ?>
|
||
<p class="meldung meldung-fehler" role="status"><?= View::h($auftrag['meldung']) ?></p>
|
||
<?php endif; ?>
|
||
|
||
<form method="post" action="<?= View::h($ziel) ?>" class="formular breit">
|
||
<input type="hidden" name="csrf_token" value="<?= View::h($csrfToken) ?>">
|
||
|
||
<label for="konto_id">Auftragskonto</label>
|
||
<select id="konto_id" name="konto_id" required>
|
||
<?php foreach ($konten as $konto): ?>
|
||
<option value="<?= (int)$konto['id'] ?>"
|
||
<?= (int)$wert('konto_id') === (int)$konto['id'] ? 'selected' : '' ?>>
|
||
<?= View::h($konto['name']) ?><?= $konto['iban'] !== '' ? ' (' . View::h($konto['iban']) . ')' : '' ?>
|
||
</option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
<p class="feldhinweis">
|
||
Die IBAN des Kontos muss zu einem Konto bei der Bank passen – daran erkennt der
|
||
Auftrag, von wo er abgeht.
|
||
</p>
|
||
|
||
<label for="zugang_id">Bankverbindung</label>
|
||
<?php /* Nicht required: Ein Auftrag darf erfasst werden, bevor eine
|
||
Bankverbindung eingerichtet ist. Beim Senden wird sie ohnehin
|
||
verlangt – dort mit einer Meldung, die erklärt, was fehlt. */ ?>
|
||
<select id="zugang_id" name="zugang_id">
|
||
<option value="">– beim Senden wählen –</option>
|
||
<?php foreach ($zugaenge as $zugang): ?>
|
||
<option value="<?= (int)$zugang['id'] ?>"
|
||
<?= (int)$wert('zugang_id') === (int)$zugang['id'] ? 'selected' : '' ?>>
|
||
<?= View::h($zugang['name']) ?>
|
||
</option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
<?php if ($zugaenge === []): ?>
|
||
<p class="feldhinweis">
|
||
Noch keine Bankverbindung eingerichtet – <a href="/banking/neu">jetzt anlegen</a>.
|
||
</p>
|
||
<?php endif; ?>
|
||
|
||
<h3>Empfänger</h3>
|
||
|
||
<?php if ($adressen !== []): ?>
|
||
<label for="adresse">Aus dem Adressbuch</label>
|
||
<select id="adresse" name="adresse" data-adressauswahl>
|
||
<option value="">– von Hand eingeben –</option>
|
||
<?php foreach ($adressen as $adresse): ?>
|
||
<option value="<?= (int)$adresse['id'] ?>"
|
||
data-name="<?= View::h($adresse['name']) ?>"
|
||
data-iban="<?= View::h($adresse['iban']) ?>"
|
||
data-bic="<?= View::h($adresse['bic']) ?>">
|
||
<?= View::h($adresse['name']) ?><?= $adresse['ort'] !== '' ? ', ' . View::h($adresse['ort']) : '' ?>
|
||
</option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
<p class="feldhinweis">Die Auswahl füllt Name, IBAN und BIC – ändern lässt sich alles.</p>
|
||
<?php endif; ?>
|
||
|
||
<label for="empfaenger">Name</label>
|
||
<input type="text" id="empfaenger" name="empfaenger" value="<?= View::h($wert('empfaenger')) ?>" required>
|
||
|
||
<label for="iban">IBAN</label>
|
||
<input type="text" id="iban" name="iban"
|
||
value="<?= View::h($neu ? '' : Adresse::ibanFormatiert($wert('iban'))) ?>" required>
|
||
<p class="feldhinweis">Wird beim Speichern geprüft (Prüfziffer nach ISO 13616).</p>
|
||
|
||
<label for="bic">BIC <span class="optional">(im SEPA-Raum nicht nötig)</span></label>
|
||
<input type="text" id="bic" name="bic" value="<?= View::h($wert('bic')) ?>">
|
||
|
||
<?php if ($neu): ?>
|
||
<label class="ankreuzfeld">
|
||
<input type="checkbox" name="adresse_merken" value="1">
|
||
Empfänger ins Adressbuch übernehmen
|
||
</label>
|
||
<?php endif; ?>
|
||
|
||
<h3>Zahlung</h3>
|
||
|
||
<label for="betrag">Betrag</label>
|
||
<input type="text" id="betrag" name="betrag" inputmode="decimal" required
|
||
value="<?= $neu ? '' : View::h(number_format((float)$wert('betrag', '0'), 2, ',', '')) ?>">
|
||
|
||
<label for="verwendungszweck">Verwendungszweck</label>
|
||
<input type="text" id="verwendungszweck" name="verwendungszweck" maxlength="140"
|
||
value="<?= View::h($wert('verwendungszweck')) ?>">
|
||
<p class="feldhinweis">
|
||
Höchstens 140 Zeichen. Umlaute werden für SEPA umgeschrieben (ä → ae), Zeichen
|
||
außerhalb des erlaubten Vorrats fallen weg – sonst weist die Bank den Auftrag ab.
|
||
</p>
|
||
|
||
<label for="ausfuehrung_am">Ausführung am <span class="optional">(leer = sofort)</span></label>
|
||
<input type="date" id="ausfuehrung_am" name="ausfuehrung_am"
|
||
value="<?= View::h($wert('ausfuehrung_am')) ?>">
|
||
|
||
<label for="kategorie_id">Kategorie</label>
|
||
<select id="kategorie_id" name="kategorie_id">
|
||
<option value="">– keine –</option>
|
||
<?php foreach ($kategorien as $kategorie): ?>
|
||
<option value="<?= (int)$kategorie['id'] ?>"
|
||
<?= (int)$wert('kategorie_id') === (int)$kategorie['id'] ? 'selected' : '' ?>>
|
||
<?= View::h($kategorie['pfad']) ?>
|
||
</option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
<p class="feldhinweis">Wird auf die Buchung übertragen, sobald die Bank den Auftrag annimmt.</p>
|
||
|
||
<label for="klasse_id">Klasse</label>
|
||
<select id="klasse_id" name="klasse_id">
|
||
<option value="">– keine –</option>
|
||
<?php foreach ($klassen as $klasse): ?>
|
||
<option value="<?= (int)$klasse['id'] ?>"
|
||
<?= (int)$wert('klasse_id') === (int)$klasse['id'] ? 'selected' : '' ?>>
|
||
<?= View::h($klasse['name']) ?>
|
||
</option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
|
||
<?php if ($neu || $auftrag['status'] !== 'gesendet'): ?>
|
||
<div class="knopfleiste">
|
||
<button type="submit" class="knopf primaer"><?= $neu ? 'Erfassen' : 'Speichern' ?></button>
|
||
</div>
|
||
<?php endif; ?>
|
||
</form>
|
||
|
||
<?php if (!$neu && $auftrag['status'] !== 'gesendet'): ?>
|
||
<form method="post" action="/zahlungsverkehr/<?= (int)$auftrag['id'] ?>/loeschen" class="formular breit"
|
||
data-bestaetigen="Zahlungsauftrag löschen?">
|
||
<input type="hidden" name="csrf_token" value="<?= View::h($csrfToken) ?>">
|
||
<div class="knopfleiste">
|
||
<button type="submit" class="knopf gefahr">Auftrag löschen</button>
|
||
</div>
|
||
</form>
|
||
<?php endif; ?>
|
||
|
||
<?php include __DIR__ . '/../layout/fuss.php'; ?>
|