Finanzmanager/tests/13_VermoegensentwicklungTest.php
fds2610-bot 0285b760fc Cockpit: Schalter filtert wirklich, Balken 18 statt 26 Punkte
Der Schalter „nur aktive“ änderte kaum etwas: Neben ihm filterte noch das
Σ-Kennzeichen, und stillgelegte Konten tragen es meist nicht – von 17
inaktiven Konten kam beim Ausschalten genau eines dazu. Das Kennzeichen
filtert hier nicht mehr mit, der Schalter ist das einzige Kriterium.

Balken jetzt 18 Bildpunkte breit mit 3 Punkten Abstand. Damit das auf dem
Schirm ankommt, trägt das SVG seine Maße als Attribute und wird nicht mehr
auf die Kachel gestreckt. Die Legende steht neben der Zeichnung, die sonst
die halbe Kachel leer ließe.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 13:45:55 +02:00

255 lines
10 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?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->summenkennzeichenFiltertNicht();
$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->assert($reihe['hatWerte'], 'Es gibt etwas zu zeichnen');
// Giro 1800, Kreditkarte -200, Auslagen 5000, Sparbuch 0.
$this->assertEquals(6600.0, $reihe['summen'][11], 'Die Summe ist der Stapel aller Konten');
}
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'
);
}
/**
* Das Σ-Kennzeichen entscheidet hier nichts: Stillgelegte Konten tragen es
* meist nicht, und dann fiele beim Ausschalten des Schalters genau das weg,
* was man sehen will.
*/
private function summenkennzeichenFiltertNicht(): void
{
$reihe = $this->reihe();
$this->assertNotEmpty(
$this->konto($reihe, $this->auslagen),
'Ein aktives Konto ohne Σ-Kennzeichen zeichnet trotzdem einen 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');
}
}