Die Kachel war ein Platzhalter und zeigt jetzt die Kontostände der letzten zwölf Monate je Monatsende – ein Balken pro Monat, ein Farbton pro Konto. Gezählt wird, was auch in der Kontenübersicht in die Summe zählt: Konten mit Σ-Kennzeichen. Der Schalter „nur aktive“ oben rechts blendet stillgelegte Konten aus und merkt sich seinen Zustand über die Einstellungen. Mehr Konten als Farbtöne fasst „Übrige Konten (n)“ zusammen – zwei Konten in derselben Farbe wären in der Legende nicht auseinanderzuhalten. Eigenes SVG, keine Diagrammbibliothek. Die Farben stehen als Klasse am Element und folgen damit dem hellen wie dem dunklen Thema. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
248 lines
9.9 KiB
PHP
248 lines
9.9 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
use Finanzmanager\Models\Buchung;
|
||
use Finanzmanager\Models\Kategorie;
|
||
use Finanzmanager\Models\Konto;
|
||
use Finanzmanager\Services\Vermoegensentwicklung;
|
||
|
||
class VermoegensentwicklungTest extends TestCase
|
||
{
|
||
protected string $testName = 'Test 13: Vermögensentwicklung';
|
||
|
||
/** Ein fester Stichtag – sonst hinge das Ergebnis am Tag des Testlaufs. */
|
||
private const HEUTE = '2026-08-11';
|
||
|
||
private int $giro = 0;
|
||
private int $sparkonto = 0;
|
||
private int $kreditkarte = 0;
|
||
private int $auslagen = 0;
|
||
private int $kategorie = 0;
|
||
|
||
public function run(): void
|
||
{
|
||
$this->mandantAnlegen();
|
||
$this->stammdaten();
|
||
|
||
$this->zwoelfMonateBisHeute();
|
||
$this->staendeWachsenMonatFuerMonat();
|
||
$this->umbuchungVerschiebtZwischenKonten();
|
||
$this->ohneSummenkennzeichenKeinBalken();
|
||
$this->schalterFiltertStillgelegteKonten();
|
||
$this->mehrKontenAlsFarbenWerdenZusammengefasst();
|
||
$this->skalaRundetAufLesbareSchritte();
|
||
}
|
||
|
||
private function stammdaten(): void
|
||
{
|
||
$this->giro = Konto::anlegen([
|
||
'name' => 'Vermögensgiro', 'kontoart' => 'bank', 'anfangssaldo' => 1000, 'aktiv' => 1,
|
||
]);
|
||
$this->sparkonto = Konto::anlegen([
|
||
'name' => 'Vermögenssparbuch', 'kontoart' => 'sparkonto', 'anfangssaldo' => 0, 'aktiv' => 1,
|
||
]);
|
||
$this->kreditkarte = Konto::anlegen([
|
||
'name' => 'Vermögenskarte', 'kontoart' => 'kreditkarte', 'anfangssaldo' => -200, 'aktiv' => 1,
|
||
]);
|
||
// Durchlaufende Auslagen sind kein Vermögen und tragen kein Σ-Kennzeichen.
|
||
$this->auslagen = Konto::anlegen([
|
||
'name' => 'Vermögensauslagen', 'kontoart' => 'sonstiges', 'anfangssaldo' => 5000,
|
||
'aktiv' => 1, 'in_summe' => 0,
|
||
]);
|
||
|
||
$this->kategorie = Kategorie::findenOderAnlegenNachPfad('600 - Vermögenstest', 'ausgabe');
|
||
}
|
||
|
||
private function reihe(bool $nurAktive = true): array
|
||
{
|
||
return Vermoegensentwicklung::reihe($nurAktive, 12, self::HEUTE);
|
||
}
|
||
|
||
/** @return array<string,mixed>|null */
|
||
private function konto(array $reihe, int $id): ?array
|
||
{
|
||
foreach ($reihe['konten'] as $konto) {
|
||
if ($konto['id'] === $id) {
|
||
return $konto;
|
||
}
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
private function zwoelfMonateBisHeute(): void
|
||
{
|
||
$monate = Vermoegensentwicklung::monate(12, self::HEUTE);
|
||
|
||
$this->assertEquals(12, count($monate), 'Zwölf Monate');
|
||
$this->assertEquals(2025, $monate[0]['jahr'], 'Der Rückblick beginnt im Vorjahr');
|
||
$this->assertEquals(9, $monate[0]['monat'], 'Zwölf Monate vor August 2026 ist September 2025');
|
||
$this->assertEquals(2026, $monate[11]['jahr'], 'Der letzte Balken ist der laufende Monat');
|
||
$this->assertEquals(8, $monate[11]['monat'], 'August 2026');
|
||
$this->assertEquals('2026-08-31', $monate[11]['bis'], 'Stichtag ist das Monatsende');
|
||
$this->assertEquals('2026-02-28', $monate[5]['bis'], 'Auch im Februar das echte Monatsende');
|
||
$this->assertEquals('Sep', $monate[0]['kuerzel'], 'Monatskürzel für die Achse');
|
||
}
|
||
|
||
/**
|
||
* Der Stand eines Monats ist der Stand des Vormonats plus dessen Bewegungen –
|
||
* und Buchungen vor dem Zeitraum stecken im Anfangsstand des ersten Balkens.
|
||
*/
|
||
private function staendeWachsenMonatFuerMonat(): void
|
||
{
|
||
// Lange vor dem Zeitraum: zählt in den ersten Balken hinein.
|
||
Buchung::anlegen([
|
||
'konto_id' => $this->giro, 'datum' => '2024-05-10', 'betrag' => 500,
|
||
'empfaenger' => 'Altbestand', 'kategorie_id' => $this->kategorie,
|
||
]);
|
||
// Im Zeitraum: hebt den Stand ab Dezember 2025.
|
||
Buchung::anlegen([
|
||
'konto_id' => $this->giro, 'datum' => '2025-12-05', 'betrag' => 300,
|
||
'empfaenger' => 'Zufluss', 'kategorie_id' => $this->kategorie,
|
||
]);
|
||
// Nach dem letzten Balken: bleibt außen vor.
|
||
Buchung::anlegen([
|
||
'konto_id' => $this->giro, 'datum' => '2026-11-01', 'betrag' => 9999,
|
||
'empfaenger' => 'Zukunft', 'kategorie_id' => $this->kategorie,
|
||
]);
|
||
|
||
$reihe = $this->reihe();
|
||
$giro = $this->konto($reihe, $this->giro);
|
||
|
||
$this->assertNotEmpty($giro, 'Das Girokonto hat einen Balken');
|
||
$this->assertEquals(1500.0, $giro['werte'][0], 'September 2025: Anfangssaldo plus Altbestand');
|
||
$this->assertEquals(1500.0, $giro['werte'][2], 'November 2025 unverändert');
|
||
$this->assertEquals(1800.0, $giro['werte'][3], 'Dezember 2025 mit dem Zufluss');
|
||
$this->assertEquals(1800.0, $giro['werte'][11], 'Der Stand trägt bis in den letzten Monat');
|
||
|
||
// Die Kreditkarte steht im Minus – sie zieht die Summe herunter.
|
||
$karte = $this->konto($reihe, $this->kreditkarte);
|
||
$this->assertEquals(-200.0, $karte['werte'][11], 'Negativer Stand bleibt negativ');
|
||
$this->assertEquals(1600.0, $reihe['summen'][11], 'Summe ist Giro minus Kreditkarte');
|
||
$this->assert($reihe['hatWerte'], 'Es gibt etwas zu zeichnen');
|
||
}
|
||
|
||
private function umbuchungVerschiebtZwischenKonten(): void
|
||
{
|
||
Buchung::anlegen([
|
||
'konto_id' => $this->giro, 'datum' => '2026-03-15', 'betrag' => -400,
|
||
'empfaenger' => 'Umbuchung aufs Sparbuch', 'gegenkonto_id' => $this->sparkonto,
|
||
]);
|
||
|
||
$reihe = $this->reihe();
|
||
$giro = $this->konto($reihe, $this->giro);
|
||
$spar = $this->konto($reihe, $this->sparkonto);
|
||
|
||
$this->assertEquals(1800.0, $giro['werte'][5], 'Februar 2026 noch ohne Umbuchung');
|
||
$this->assertEquals(1400.0, $giro['werte'][6], 'März 2026: 400 weniger auf dem Giro');
|
||
$this->assertEquals(400.0, $spar['werte'][6], 'Dieselben 400 liegen auf dem Sparbuch');
|
||
$this->assertEquals(
|
||
$reihe['summen'][5],
|
||
$reihe['summen'][6],
|
||
'Eine Umbuchung ändert das Vermögen nicht'
|
||
);
|
||
}
|
||
|
||
private function ohneSummenkennzeichenKeinBalken(): void
|
||
{
|
||
$reihe = $this->reihe();
|
||
|
||
$this->assertNull(
|
||
$this->konto($reihe, $this->auslagen),
|
||
'Ein Konto ohne Σ-Kennzeichen zeichnet keinen Balken'
|
||
);
|
||
|
||
// Ein Konto, das im ganzen Zeitraum auf null steht, bläht nur die Legende auf.
|
||
$leer = Konto::anlegen([
|
||
'name' => 'Vermögensleerkonto', 'kontoart' => 'bank', 'anfangssaldo' => 0, 'aktiv' => 1,
|
||
]);
|
||
$this->assertNull($this->konto($this->reihe(), $leer), 'Ein durchgehend leeres Konto fehlt');
|
||
}
|
||
|
||
/**
|
||
* Der Schalter „nur aktive“ an der Kachel: eingeschaltet zählt nur, was noch
|
||
* geführt wird; ausgeschaltet auch das Vermögen auf stillgelegten Konten.
|
||
*/
|
||
private function schalterFiltertStillgelegteKonten(): void
|
||
{
|
||
$stillgelegt = Konto::anlegen([
|
||
'name' => 'Vermögensaltkonto', 'kontoart' => 'bank', 'anfangssaldo' => 2500, 'aktiv' => 0,
|
||
]);
|
||
|
||
$this->assertNull(
|
||
$this->konto($this->reihe(), $stillgelegt),
|
||
'Eingeschaltet bleibt das stillgelegte Konto draußen'
|
||
);
|
||
|
||
$alle = $this->reihe(false);
|
||
$this->assertNotEmpty($this->konto($alle, $stillgelegt), 'Ausgeschaltet ist es dabei');
|
||
$this->assertEquals(
|
||
2500.0,
|
||
$this->konto($alle, $stillgelegt)['werte'][11],
|
||
'Mit seinem Stand'
|
||
);
|
||
$this->assertEquals(
|
||
round($this->reihe()['summen'][11] + 2500.0, 2),
|
||
$alle['summen'][11],
|
||
'Und die Summe wächst um genau diesen Stand'
|
||
);
|
||
}
|
||
|
||
/**
|
||
* Zwölf Farbtöne hat die Palette; darüber hinaus behalten die größten Konten
|
||
* ihre Farbe, der Rest wird zu einem Stück zusammengefasst.
|
||
*/
|
||
private function mehrKontenAlsFarbenWerdenZusammengefasst(): void
|
||
{
|
||
for ($nummer = 1; $nummer <= 15; $nummer++) {
|
||
Konto::anlegen([
|
||
'name' => 'Vermögensklein ' . $nummer,
|
||
'kontoart' => 'bank',
|
||
'anfangssaldo' => $nummer,
|
||
'aktiv' => 1,
|
||
]);
|
||
}
|
||
|
||
$reihe = $this->reihe();
|
||
$konten = $reihe['konten'];
|
||
$letzte = $konten[count($konten) - 1];
|
||
|
||
$this->assertEquals(
|
||
Vermoegensentwicklung::SERIEN,
|
||
count($konten),
|
||
'Nicht mehr Balkenstücke als Farbtöne'
|
||
);
|
||
$this->assertEquals(0, $letzte['id'], 'Das letzte Stück steht für kein einzelnes Konto');
|
||
$this->assert(str_starts_with($letzte['name'], 'Übrige Konten ('), 'Es heißt „Übrige Konten“');
|
||
|
||
// Die größten behalten ihre eigene Farbe – das Sparbuch ist dabei.
|
||
$this->assertNotEmpty($this->konto($reihe, $this->sparkonto), 'Ein großes Konto bleibt eigenständig');
|
||
|
||
// Zusammengefasst wird, nicht weggelassen: Die Summe bleibt dieselbe.
|
||
$gestapelt = 0.0;
|
||
foreach ($konten as $konto) {
|
||
$gestapelt += $konto['werte'][11];
|
||
}
|
||
$this->assertEquals(
|
||
$reihe['summen'][11],
|
||
round($gestapelt, 2),
|
||
'Die Balkenstücke ergeben zusammen die Summe'
|
||
);
|
||
}
|
||
|
||
private function skalaRundetAufLesbareSchritte(): void
|
||
{
|
||
$skala = Vermoegensentwicklung::skala([0.0, 1234.0, 4870.0]);
|
||
$this->assertEquals(0.0, $skala['min'], 'Die Null gehört immer dazu');
|
||
$this->assertGreaterThan(4869.0, $skala['max'], 'Der größte Wert passt hinein');
|
||
$this->assertEquals(0.0, fmod($skala['max'], $skala['schritt']), 'Das Maximum liegt auf einem Schritt');
|
||
|
||
$mitMinus = Vermoegensentwicklung::skala([-820.0, 2500.0]);
|
||
$this->assert($mitMinus['min'] <= -820.0, 'Auch der negative Ausschlag passt hinein');
|
||
$this->assert($mitMinus['max'] >= 2500.0, 'Und der positive');
|
||
|
||
$leer = Vermoegensentwicklung::skala([]);
|
||
$this->assertGreaterThan(0.0, $leer['schritt'], 'Ohne Werte bleibt die Skala brauchbar');
|
||
}
|
||
}
|