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>
176 lines
7.4 KiB
PHP
176 lines
7.4 KiB
PHP
<?php
|
||
|
||
use Finanzmanager\View;
|
||
|
||
/**
|
||
* Die Depotübersicht: Bestände mit Einstand, aktuellem Wert und Gewinn.
|
||
*
|
||
* @var list<array<string,mixed>> $depots
|
||
* @var int|null $depot
|
||
* @var array<string,mixed> $bestand
|
||
* @var list<array{monat:string, kuerzel:string, wert:float, einstand:float}> $entwicklung
|
||
* @var bool $kursabruf
|
||
* @var string $csrfToken
|
||
*/
|
||
|
||
include __DIR__ . '/../layout/kopf.php';
|
||
?>
|
||
|
||
<div class="tableiste">
|
||
<span class="tab aktiv">Bestände</span>
|
||
<a href="/wertpapiere/buchungen" class="tab">Depotbuchungen</a>
|
||
<a href="/wertpapiere/stammdaten" class="tab">Wertpapiere</a>
|
||
<a href="/wertpapiere/auswertung?art=veraeusserungen" class="tab">Veräußerungen</a>
|
||
<a href="/wertpapiere/auswertung?art=ertraege" class="tab">Erträge</a>
|
||
</div>
|
||
|
||
<?php if ($depots === []): ?>
|
||
<p class="platzhalter">
|
||
Noch kein Depot vorhanden. Ein Depot ist ein Konto der Kontoart <em>Depot</em> –
|
||
anzulegen in der Kontenverwaltung oder über den QIF-Import.
|
||
</p>
|
||
<div class="knopfleiste">
|
||
<a href="/konten/neu" class="knopf primaer">Konto anlegen</a>
|
||
</div>
|
||
<?php else: ?>
|
||
<div class="seitenaktionen">
|
||
<form method="get" action="/wertpapiere" class="inline">
|
||
<?php /* Ohne Inline-Handler – die Content-Security-Policy verbietet
|
||
sie; abgeschickt wird über den Knopf daneben. */ ?>
|
||
<select name="depot">
|
||
<option value="">Alle Depots</option>
|
||
<?php foreach ($depots as $eintrag): ?>
|
||
<option value="<?= (int)$eintrag['id'] ?>" <?= $depot === (int)$eintrag['id'] ? 'selected' : '' ?>>
|
||
<?= View::h($eintrag['name']) ?>
|
||
</option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
<button type="submit" class="knopf klein">Anzeigen</button>
|
||
</form>
|
||
<a href="/wertpapiere/buchungen/neu" class="knopf primaer">Depotbuchung erfassen</a>
|
||
</div>
|
||
|
||
<div class="kennzahlenleiste">
|
||
<div class="kennzahlkachel">
|
||
<span class="kennzahltitel">Aktueller Wert</span>
|
||
<span class="kennzahl"><?= View::betrag($bestand['wert']) ?></span>
|
||
</div>
|
||
<div class="kennzahlkachel">
|
||
<span class="kennzahltitel">Einstandswert</span>
|
||
<span class="kennzahl"><?= View::betrag($bestand['einstand']) ?></span>
|
||
</div>
|
||
<div class="kennzahlkachel">
|
||
<span class="kennzahltitel">Nicht realisiert</span>
|
||
<span class="kennzahl <?= View::betragKlasse($bestand['gewinn']) ?>">
|
||
<?= View::betrag($bestand['gewinn']) ?>
|
||
</span>
|
||
</div>
|
||
<div class="kennzahlkachel">
|
||
<span class="kennzahltitel">Realisiert</span>
|
||
<span class="kennzahl <?= View::betragKlasse($bestand['realisiert']) ?>">
|
||
<?= View::betrag($bestand['realisiert']) ?>
|
||
</span>
|
||
</div>
|
||
<div class="kennzahlkachel">
|
||
<span class="kennzahltitel">Erträge</span>
|
||
<span class="kennzahl"><?= View::betrag($bestand['ertraege']) ?></span>
|
||
</div>
|
||
</div>
|
||
|
||
<?php if (!$bestand['bewertbar']): ?>
|
||
<p class="meldung meldung-hinweis" role="status">
|
||
Für mindestens ein Papier fehlt ein Kurs – der ausgewiesene Wert ist deshalb
|
||
unvollständig.
|
||
<?php if ($kursabruf): ?>
|
||
<a href="/wertpapiere/stammdaten">Kurse abrufen</a>.
|
||
<?php else: ?>
|
||
Kurse lassen sich unter <a href="/wertpapiere/stammdaten">Wertpapiere</a> von Hand
|
||
eintragen oder über den Kursdienst holen
|
||
(<a href="/einstellungen?bereich=wertpapiere">Einstellungen</a>).
|
||
<?php endif; ?>
|
||
</p>
|
||
<?php endif; ?>
|
||
|
||
<?php if ($bestand['positionen'] === []): ?>
|
||
<p class="platzhalter">
|
||
Keine Bestände. Depotbuchungen kommen aus dem QIF-Import oder werden hier erfasst.
|
||
</p>
|
||
<?php else: ?>
|
||
<table class="liste zahlen">
|
||
<thead>
|
||
<tr>
|
||
<th>Wertpapier</th>
|
||
<th>Depot</th>
|
||
<th class="rechts">Stück</th>
|
||
<th class="rechts">Einstand je Stück</th>
|
||
<th class="rechts">Kurs</th>
|
||
<th class="rechts">Einstand</th>
|
||
<th class="rechts">Wert</th>
|
||
<th class="rechts">Gewinn</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<?php foreach ($bestand['positionen'] as $position): ?>
|
||
<tr>
|
||
<td>
|
||
<?= View::h($position['name']) ?>
|
||
<?php if ($position['isin'] !== ''): ?>
|
||
<span class="nebentext"><?= View::h($position['isin']) ?></span>
|
||
<?php endif; ?>
|
||
</td>
|
||
<td><?= View::h($position['depot']) ?></td>
|
||
<td class="rechts"><?= View::h(rtrim(rtrim(number_format($position['stueck'], 6, ',', '.'), '0'), ',')) ?></td>
|
||
<td class="rechts">
|
||
<?= $position['einstand_je_stueck'] === null
|
||
? '–' : View::h(number_format($position['einstand_je_stueck'], 2, ',', '.')) ?>
|
||
</td>
|
||
<td class="rechts">
|
||
<?php if ($position['kurs'] === null): ?>
|
||
<span class="nebentext">kein Kurs</span>
|
||
<?php else: ?>
|
||
<?= View::h(number_format((float)$position['kurs'], 2, ',', '.')) ?>
|
||
<span class="nebentext"><?= View::h(View::datum($position['kurs_datum'])) ?></span>
|
||
<?php endif; ?>
|
||
</td>
|
||
<td class="rechts"><?= View::betrag($position['einstand']) ?></td>
|
||
<td class="rechts">
|
||
<?= $position['wert'] === null ? '–' : View::betrag($position['wert']) ?>
|
||
</td>
|
||
<td class="rechts <?= $position['gewinn'] === null ? '' : View::betragKlasse($position['gewinn']) ?>">
|
||
<?= $position['gewinn'] === null ? '–' : View::betrag($position['gewinn']) ?>
|
||
</td>
|
||
</tr>
|
||
<?php endforeach; ?>
|
||
</tbody>
|
||
<tfoot>
|
||
<tr class="summenzeile">
|
||
<td colspan="5">Summe</td>
|
||
<td class="rechts"><?= View::betrag($bestand['einstand']) ?></td>
|
||
<td class="rechts"><?= View::betrag($bestand['wert']) ?></td>
|
||
<td class="rechts <?= View::betragKlasse($bestand['gewinn']) ?>">
|
||
<?= View::betrag($bestand['gewinn']) ?>
|
||
</td>
|
||
</tr>
|
||
</tfoot>
|
||
</table>
|
||
<?php endif; ?>
|
||
|
||
<?php
|
||
// Die Entwicklung braucht Kurshistorie; ohne sie stünde eine Linie auf null.
|
||
$hatEntwicklung = false;
|
||
foreach ($entwicklung as $punkt) {
|
||
$hatEntwicklung = $hatEntwicklung || $punkt['wert'] > 0.0;
|
||
}
|
||
?>
|
||
<?php if ($hatEntwicklung): ?>
|
||
<section class="kachel">
|
||
<header>
|
||
<h2>Wertentwicklung</h2>
|
||
<p class="kachelhinweis">Bewerteter Bestand je Monatsende, dazu der Einstand.</p>
|
||
</header>
|
||
<?php $reihe = $entwicklung; include __DIR__ . '/entwicklungsdiagramm.php'; ?>
|
||
</section>
|
||
<?php endif; ?>
|
||
<?php endif; ?>
|
||
|
||
<?php include __DIR__ . '/../layout/fuss.php'; ?>
|