['81'], '66' => ['66'], '61' => ['61'], '83' => ['83'], ]; private const KZ_STEUERART_MAP = [ '89' => ['ig_erwerb_19'], ]; public function calculate(int $year, int $month): array { // ── 1. Kontobasierte Kennzahlen (EU-Steuerarten ausgeschlossen) ───────── $euPh = implode(',', array_fill(0, count(self::EU_STEUERARTEN), '?')); $accountRows = Database::fetchAll( 'SELECT a.ustva_kz, SUM(bk.betrag_netto) AS summe_netto FROM bookings bk JOIN fiscal_years fy ON bk.fiscal_year_id = fy.id JOIN accounts a ON a.id = bk.kategorie_konto_id WHERE fy.year = ? AND MONTH(bk.buchungs_datum) = ? AND a.ustva_kz IS NOT NULL AND a.ustva_kz != \'\' AND (bk.steuerart IS NULL OR bk.steuerart NOT IN (' . $euPh . ')) GROUP BY a.ustva_kz', array_merge([$year, $month], self::EU_STEUERARTEN) ); $byKz = []; foreach ($accountRows as $row) { $byKz[$row['ustva_kz']] = (float)$row['summe_netto']; } // ── 2. Steuerart-basiert: ig_erwerb_19 → Kz 89 ────────────────────── $stRow = Database::fetchOne( 'SELECT SUM(bk.betrag_netto) AS summe_netto FROM bookings bk JOIN fiscal_years fy ON bk.fiscal_year_id = fy.id WHERE fy.year = ? AND MONTH(bk.buchungs_datum) = ? AND bk.steuerart = ?', [$year, $month, 'ig_erwerb_19'] ); $kz89 = (float)($stRow['summe_netto'] ?? 0.0); // ── 3. Kennzahlen zusammensetzen ───────────────────────────────────── $n = fn(string $kz): float => (float)($byKz[$kz] ?? 0.0); // Kz 81 und 89 werden laut UStVA auf volle Euro abgerundet $kz81 = floor($n('81')); $kz89 = floor($kz89); $kz66 = $n('66'); $ust81 = round($kz81 * 0.19, 2); $ust89 = round($kz89 * 0.19, 2); // Kz 61 (VSt ig. Erwerb) = ust89: ReverseCharge ist immer symmetrisch zur USt aus Kz 89 $kz61 = $ust89; $kz83 = $ust81 + $ust89 - $kz66 - $kz61; return [ '81' => ['name' => 'Steuerpflichtige Umsätze zum Steuersatz von 19 %', 'betrag' => $kz81, 'steuer' => null, 'xml' => true, 'ganze_euro' => true], 'ust81' => ['name' => 'Abzuführende USt aus Kz 81 (19 %)', 'betrag' => null, 'steuer' => $ust81, 'xml' => false, 'ganze_euro' => false], '89' => ['name' => 'Steuerpfl. innergemeinschaftliche Erwerbe 19 %', 'betrag' => $kz89, 'steuer' => null, 'xml' => true, 'ganze_euro' => true], 'ust89' => ['name' => 'Abzuführende USt aus Kz 89 (19 %)', 'betrag' => null, 'steuer' => $ust89, 'xml' => false, 'ganze_euro' => false], '66' => ['name' => 'Gesamte Vorsteuerbeträge', 'betrag' => null, 'steuer' => round($kz66, 2), 'xml' => true, 'ganze_euro' => false], '61' => ['name' => 'Vorsteuerbeträge i.g. Erwerb', 'betrag' => null, 'steuer' => round($kz61, 2), 'xml' => true, 'ganze_euro' => false], '83' => ['name' => 'Vorauszahlung / Überschuss', 'betrag' => null, 'steuer' => round($kz83, 2), 'xml' => true, 'ganze_euro' => false], ]; } /** * Liefert alle Buchungszeilen, die zu einer Kennzahl beitragen (Drill-down). */ public function fetchBookingsForKz(int $year, int $month, string $kz): array { $accountKzList = self::KZ_ACCOUNT_MAP[$kz] ?? []; $steuerartList = self::KZ_STEUERART_MAP[$kz] ?? []; if (empty($accountKzList) && empty($steuerartList)) { return []; } $select = 'SELECT bk.id, bk.buchungs_datum, bk.buchungstyp, bk.steuerart, bk.ust_satz, bk.betrag_netto, bk.betrag_ust, bk.betrag_brutto, bk.buchungstext, bk.is_reversal, bk.beleg_id, bk.asset_id, bl.beleg_nr, k.kontonummer AS kategorie_nr, k.bezeichnung AS kategorie_name, z.kontonummer AS zahlungs_nr, z.bezeichnung AS zahlungs_name, a.anlage_nr, a.bezeichnung AS anlage_bezeichnung FROM bookings bk JOIN fiscal_years fy ON bk.fiscal_year_id = fy.id LEFT JOIN belege bl ON bk.beleg_id = bl.id JOIN accounts k ON bk.kategorie_konto_id = k.id JOIN accounts z ON bk.zahlungskonto_id = z.id LEFT JOIN assets a ON bk.asset_id = a.id WHERE fy.year = ? AND MONTH(bk.buchungs_datum) = ?'; $parts = []; $params = []; if (!empty($accountKzList)) { $ph = implode(',', array_fill(0, count($accountKzList), '?')); $euPh = implode(',', array_fill(0, count(self::EU_STEUERARTEN), '?')); $parts[] = $select . ' AND k.ustva_kz IN (' . $ph . ')' . ' AND (bk.steuerart IS NULL OR bk.steuerart NOT IN (' . $euPh . '))'; $params = array_merge($params, [$year, $month], $accountKzList, self::EU_STEUERARTEN); } if (!empty($steuerartList)) { $ph = implode(',', array_fill(0, count($steuerartList), '?')); $parts[] = $select . ' AND bk.steuerart IN (' . $ph . ')'; $params = array_merge($params, [$year, $month], $steuerartList); } return Database::fetchAll( implode(' UNION ALL ', $parts) . ' ORDER BY buchungs_datum, id', $params ); } }