Steuerkram/tests/80_DashboardEuerAbgleichTest.php

201 lines
8.4 KiB
PHP
Raw Permalink 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 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. Die gezahlte Vorsteuer ist seit Migration 095
* Betriebsausgabe (EÜR-Zeile 57) und zählt in beiden Auswertungen mit.
*/
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'); // Betriebsausgabe, euer_zeile 57
$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 – Betriebsausgabe nach EÜR-Zeile 57 (Kz 185)
$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'] + 338.00, 2),
$nachher['ausgaben'],
'Ausgaben enthalten Aufwand, AfA und Vorsteuer, aber keine nicht abzugsfähigen 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'] + 662.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"
);
}
// Vorsteuer gehört in die Ausgabenliste, nicht abzugsfähige Kosten nicht
$kontonummern = array_column($calc->fetchBookingsForBucket($jahr, null, 'ausgaben'), 'kategorie_nr');
$this->assert(in_array('1576', $kontonummern, true), 'Vorsteuerkonto steht 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',
]
);
}
}