Steuerkram/tests/80_DashboardEuerAbgleichTest.php

200 lines
8.3 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 Steuerkram\Database;
use Steuerkram\Services\EuerCalculator;
/**
* Test 80: Dashboard-Kacheln stimmen mit der EÜR überein (Issue #167)
*
* Das Dashboard summierte bisher nach bookings.buchungstyp, die EÜR nach
* accounts.kontenart. Dadurch zählten Vorsteuer- und Anlagenbuchungen als
* Betriebsausgabe, AfA-Buchungen fielen ganz heraus und nicht abzugsfähige
* Kosten minderten das angezeigte Ergebnis. Geprüft wird, dass beide
* Auswertungen jetzt dieselbe Zuordnung verwenden und dass die Detailliste
* hinter einer Kachel exakt auf deren Summe kommt.
*/
class DashboardEuerAbgleichTest extends TestCase
{
protected string $testName = 'Test 80: Dashboard-Kacheln und EÜR liefern dieselben Summen';
public function run(): void
{
$calc = new EuerCalculator();
$jahr = (int)date('Y');
$erloese = $this->findAccountId('8400'); // Betriebseinnahme
$buerobedarf = $this->findAccountId('4930'); // Betriebsausgabe
$bewirtung = $this->findAccountId('4654'); // Betriebsausgabe, euer_zeile 74
$vorsteuer = $this->findAccountId('1576'); // Zahlungskonto
$bank = $this->findAccountId('1200'); // Zahlungskonto
$this->assertNotEmpty($erloese, 'Erlöskonto 8400 vorhanden');
$this->assertNotEmpty($buerobedarf, 'Aufwandskonto 4930 vorhanden');
$this->assertNotEmpty($bewirtung, 'Bewirtungskonto 4654 vorhanden');
$this->assertNotEmpty($vorsteuer, 'Vorsteuerkonto 1576 vorhanden');
$this->assertNotEmpty($bank, 'Bankkonto 1200 vorhanden');
$vorher = $calc->summary($jahr);
$belegId = $this->createBeleg();
$monat = 6;
$datum = sprintf('%04d-%02d-15', $jahr, $monat);
// Einnahme 1.000,00 – zählt in beiden Auswertungen
$this->createBooking($belegId, $datum, 'Einnahme', $erloese, $bank, 1000.00);
// Betriebsausgabe 200,00 – zählt in beiden Auswertungen
$this->createBooking($belegId, $datum, 'Ausgabe', $buerobedarf, $bank, 200.00);
// AfA 100,00 – fiel im alten Dashboard komplett unter den Tisch
$this->createBooking($belegId, $datum, 'AfA', $buerobedarf, $bank, 100.00);
// Vorsteuer 38,00 – wurde im alten Dashboard als Ausgabe mitgezählt
$this->createBooking($belegId, $datum, 'Ausgabe', $vorsteuer, $bank, 38.00);
// Nicht abzugsfähige Bewirtung 30,00 – mindert das Ergebnis nicht
$this->createBooking($belegId, $datum, 'Ausgabe', $bewirtung, $bank, 30.00);
$nachher = $calc->summary($jahr);
$this->assertEquals(
round($vorher['einnahmen'] + 1000.00, 2),
$nachher['einnahmen'],
'Einnahmen steigen um genau den Erlös'
);
$this->assertEquals(
round($vorher['ausgaben'] + 300.00, 2),
$nachher['ausgaben'],
'Ausgaben enthalten Aufwand und AfA, aber weder Vorsteuer noch nicht abzugsfähige Kosten'
);
$this->assertEquals(
round($vorher['nicht_abzugsfaehig'] + 30.00, 2),
$nachher['nicht_abzugsfaehig'],
'Nicht abzugsfähige Kosten werden getrennt ausgewiesen'
);
$this->assertEquals(
round($vorher['ergebnis'] + 700.00, 2),
$nachher['ergebnis'],
'Ergebnis = Einnahmen − Ausgaben'
);
// Deckungsgleich mit der vollständigen EÜR-Berechnung
$euer = $calc->calculate($jahr);
$this->assertEquals($euer['total_einnahmen'], $nachher['einnahmen'], 'Einnahmen wie in der EÜR');
$this->assertEquals($euer['total_ausgaben'], $nachher['ausgaben'], 'Ausgaben wie in der EÜR');
$this->assertEquals($euer['gewinn'], $nachher['ergebnis'], 'Ergebnis wie in der EÜR');
// Detaillisten summieren auf den Kachelwert
foreach (['einnahmen', 'ausgaben', 'ergebnis'] as $bucket) {
$bookings = $calc->fetchBookingsForBucket($jahr, null, $bucket);
$summe = 0.0;
foreach ($bookings as $b) {
$summe += ($bucket === 'ergebnis' && $b['kontenart'] === 'Betriebsausgabe')
? -(float)$b['betrag_netto']
: (float)$b['betrag_netto'];
}
$this->assertEquals(
$nachher[$bucket === 'ergebnis' ? 'ergebnis' : $bucket],
round($summe, 2),
"Detailliste '{$bucket}' summiert auf den Kachelwert"
);
}
// Keine Vorsteuer- und keine nicht abzugsfähige Buchung in der Ausgabenliste
$kontonummern = array_column($calc->fetchBookingsForBucket($jahr, null, 'ausgaben'), 'kategorie_nr');
$this->assert(!in_array('1576', $kontonummern, true), 'Vorsteuerkonto fehlt in der Ausgabenliste');
$this->assert(!in_array('4654', $kontonummern, true), 'Nicht abzugsfähige Bewirtung fehlt in der Ausgabenliste');
// Monatsfilter greift
$juni = $calc->summary($jahr, $monat);
$this->assert($juni['einnahmen'] >= 1000.00, 'Monatsauswertung enthält die Junibuchung');
$andererMonat = $calc->summary($jahr, $monat === 12 ? 1 : $monat + 1);
$this->assert(
$andererMonat['einnahmen'] < $juni['einnahmen'],
'Ein anderer Monat enthält die Junibuchung nicht'
);
// Unbekannte Gruppe wird abgewiesen
$abgewiesen = false;
try {
$calc->fetchBookingsForBucket($jahr, null, 'unsinn');
} catch (\InvalidArgumentException) {
$abgewiesen = true;
}
$this->assert($abgewiesen, 'Unbekannte Auswertungsgruppe löst eine Ausnahme aus');
// Route und Controller-Action vorhanden
$routes = (string)file_get_contents(__DIR__ . '/../public/index.php');
$this->assert(
str_contains($routes, '/dashboard/buchungen/{bucket}/{year}')
&& str_contains($routes, '/dashboard/buchungen/{bucket}/{year}/{month}'),
'Routen für Jahres- und Monatsdetails sind registriert'
);
$dashboard = (string)file_get_contents(__DIR__ . '/../src/Views/dashboard.php');
$this->assert(
substr_count($dashboard, '/dashboard/buchungen/') >= 1 && str_contains($dashboard, "\$detail("),
'Die Kachelzahlen verlinken auf die Detailansicht'
);
$this->assertEquals(
9,
substr_count($dashboard, '$detail('),
'Alle neun Zahlen der ersten drei Kacheln sind verlinkt'
);
}
private function createBeleg(): int
{
Database::execute(
'INSERT INTO belege (beleg_nr, filename, storage_path, mime_type, file_hash, beleg_datum, belegtyp)
VALUES (?, ?, ?, ?, ?, ?, ?)',
[
'TST-DASH-' . substr(bin2hex(random_bytes(4)), 0, 8),
'dashboard-test.pdf',
'test/dashboard-test.pdf',
'application/pdf',
hash('sha256', uniqid('dash', true)),
date('Y-m-d'),
'Eingangsrechnung',
]
);
return (int)Database::lastInsertId();
}
private function createBooking(
int $belegId,
string $datum,
string $typ,
int $kategorieKonto,
int $zahlungsKonto,
float $netto
): void {
$fy = Database::fetchOne('SELECT id FROM fiscal_years WHERE year = ?', [(int)date('Y', strtotime($datum))]);
if (!$fy) {
Database::execute('INSERT INTO fiscal_years (year) VALUES (?)', [(int)date('Y', strtotime($datum))]);
$fiscalYearId = (int)Database::lastInsertId();
} else {
$fiscalYearId = (int)$fy['id'];
}
Database::execute(
'INSERT INTO bookings
(beleg_id, fiscal_year_id, buchungs_nr, buchungs_datum, buchungstyp,
kategorie_konto_id, zahlungskonto_id, ust_satz, betrag_netto, betrag_ust,
betrag_brutto, buchungstext)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?)',
[
$belegId,
$fiscalYearId,
'TST-' . substr(bin2hex(random_bytes(5)), 0, 10),
$datum,
$typ,
$kategorieKonto,
$zahlungsKonto,
0,
$netto,
0,
$netto,
'Testbuchung Dashboard-Abgleich',
]
);
}
}