Der Name in Bestandsübersicht, Depotbuchungen und Stammdaten führt jetzt auf
/wertpapiere/papier/{id}: Kennzahlen über die ganze Historie, Stammdaten und
Kurs, Bestand je Depot, Wertentwicklung und die Buchungen eines Zeitraums.
Depot::bestand() und Depot::entwicklung() nehmen dafür eine Wertpapiernummer
entgegen – die Detailseite rechnet nicht selbst, sondern bekommt dieselben
Zahlen wie die Übersicht. Neu sind Wertpapier::kurseckwerte() und
Depotbuchung::datumsgrenzen().
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
141 lines
4.4 KiB
PHP
141 lines
4.4 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace Finanzmanager;
|
||
|
||
class View
|
||
{
|
||
private string $viewDir;
|
||
private array $flash = [];
|
||
|
||
public function __construct()
|
||
{
|
||
$this->viewDir = __DIR__ . '/Views';
|
||
|
||
Auth::startSession();
|
||
if (isset($_SESSION['flash'])) {
|
||
$this->flash = $_SESSION['flash'];
|
||
unset($_SESSION['flash']);
|
||
}
|
||
}
|
||
|
||
public function render(string $template, array $daten = []): void
|
||
{
|
||
$daten['flash'] = $this->flash;
|
||
$daten['aktuellerPfad'] = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH);
|
||
|
||
extract($daten, EXTR_SKIP);
|
||
include $this->viewDir . '/' . $template;
|
||
}
|
||
|
||
public static function redirect(string $pfad, string $meldung = '', string $typ = 'erfolg'): void
|
||
{
|
||
Auth::startSession();
|
||
if ($meldung !== '') {
|
||
$_SESSION['flash'][] = ['typ' => $typ, 'meldung' => $meldung];
|
||
}
|
||
header('Location: ' . $pfad);
|
||
exit;
|
||
}
|
||
|
||
public static function h(mixed $wert): string
|
||
{
|
||
return htmlspecialchars((string)$wert, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||
}
|
||
|
||
/**
|
||
* Beträge im deutschen Format. Das Leerzeichen vor dem Euro-Zeichen ist
|
||
* geschützt, sonst rutscht das Zeichen in schmalen Tabellenspalten allein
|
||
* in die nächste Zeile.
|
||
*/
|
||
public static function betrag(mixed $wert, bool $mitWaehrung = true): string
|
||
{
|
||
$text = number_format((float)$wert, 2, ',', '.');
|
||
return $mitWaehrung ? $text . "\u{00A0}€" : $text;
|
||
}
|
||
|
||
/**
|
||
* Stückzahlen. Fonds werden in Bruchteilen geführt, Aktien in ganzen
|
||
* Stücken – dieselbe Spalte trägt beides, deshalb sechs Nachkommastellen
|
||
* ohne die Nullen, die niemand liest: „12“ statt „12,000000“.
|
||
*/
|
||
public static function stueck(mixed $wert): string
|
||
{
|
||
return rtrim(rtrim(number_format((float)$wert, 6, ',', '.'), '0'), ',');
|
||
}
|
||
|
||
/**
|
||
* Monatsnamen auf Deutsch. Nicht über date('F') oder IntlDateFormatter –
|
||
* date() ist immer englisch und intl ist keine Standarderweiterung. Der
|
||
* Budgetplaner und die Auswertungen greifen später ebenfalls hierauf zu.
|
||
*/
|
||
public const MONATE = [
|
||
1 => 'Januar', 2 => 'Februar', 3 => 'März', 4 => 'April',
|
||
5 => 'Mai', 6 => 'Juni', 7 => 'Juli', 8 => 'August',
|
||
9 => 'September', 10 => 'Oktober', 11 => 'November', 12 => 'Dezember',
|
||
];
|
||
|
||
/** Kurzform für schmale Spalten (zwölf Monatsspalten im Budgetplaner). */
|
||
public const MONATE_KURZ = [
|
||
1 => 'Jan', 2 => 'Feb', 3 => 'Mär', 4 => 'Apr',
|
||
5 => 'Mai', 6 => 'Jun', 7 => 'Jul', 8 => 'Aug',
|
||
9 => 'Sep', 10 => 'Okt', 11 => 'Nov', 12 => 'Dez',
|
||
];
|
||
|
||
public static function monat(int $monat, bool $kurz = false): string
|
||
{
|
||
$namen = $kurz ? self::MONATE_KURZ : self::MONATE;
|
||
return $namen[$monat] ?? (string)$monat;
|
||
}
|
||
|
||
/** Wochentagskürzel, Montag zuerst – wie MONATE nicht über date() oder intl. */
|
||
public const WOCHENTAGE = ['Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So'];
|
||
|
||
public static function wochentag(?string $datum): string
|
||
{
|
||
if (!$datum) {
|
||
return '';
|
||
}
|
||
|
||
$zeit = strtotime(substr($datum, 0, 10));
|
||
if ($zeit === false) {
|
||
return '';
|
||
}
|
||
|
||
// date('N') zählt Montag als 1.
|
||
return self::WOCHENTAGE[(int)date('N', $zeit) - 1] ?? '';
|
||
}
|
||
|
||
public static function datum(?string $datum): string
|
||
{
|
||
if (!$datum) {
|
||
return '–';
|
||
}
|
||
$d = \DateTime::createFromFormat('Y-m-d', substr($datum, 0, 10));
|
||
return $d ? $d->format('d.m.Y') : $datum;
|
||
}
|
||
|
||
/** CSS-Klasse für Beträge: negative Werte werden rot dargestellt. */
|
||
public static function betragKlasse(mixed $wert): string
|
||
{
|
||
return (float)$wert < 0 ? 'betrag negativ' : 'betrag';
|
||
}
|
||
|
||
/**
|
||
* Farbschema für das data-thema-Attribut: 'hell', 'dunkel' oder '' für die
|
||
* Systemvorgabe. Fällt auf die Systemvorgabe zurück, wenn die Einstellung
|
||
* nicht lesbar ist – die Fehlerseite muss auch ohne Datenbank darstellbar
|
||
* bleiben.
|
||
*/
|
||
public static function thema(): string
|
||
{
|
||
try {
|
||
$thema = \Finanzmanager\Models\Einstellung::get('darstellung_thema', 'system');
|
||
} catch (\Throwable) {
|
||
return '';
|
||
}
|
||
|
||
return in_array($thema, ['hell', 'dunkel'], true) ? $thema : '';
|
||
}
|
||
}
|