['81'], '83' => ['81'], '86' => ['86'], '84' => ['86'], '35' => ['35'], '41' => ['41'], '44' => ['44'], '61' => ['61'], '62' => ['62'], ]; /** * Welche steuerart-Werte liefern Buchungen für eine Kennzahl. */ private const KZ_STEUERART_MAP = [ '41' => ['ig_lieferung'], '44' => ['export'], '89' => ['ig_erwerb_19'], '91' => ['ig_erwerb_19'], '93' => ['ig_erwerb_7'], '94' => ['ig_erwerb_7'], '67' => ['ig_erwerb_19', 'ig_erwerb_7'], '66' => ['ig_erwerb_19', 'ig_erwerb_7'], '69' => ['ig_lieferung', 'export', 'ig_erwerb_19', 'ig_erwerb_7'], ]; public function calculate(int $year, int $month): array { // ── 1. Kontobasierte Kennzahlen (EU-Steuerarten ausgeschlossen) ────── $euPlaceholders = implode(',', array_fill(0, count(self::EU_STEUERARTEN), '?')); $accountRows = Database::fetchAll( 'SELECT a.ustva_kz, SUM(bk.betrag_netto) AS summe_netto, SUM(bk.betrag_ust) AS summe_ust 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 (' . $euPlaceholders . ')) GROUP BY a.ustva_kz', array_merge([$year, $month], self::EU_STEUERARTEN) ); $byKz = []; foreach ($accountRows as $row) { $byKz[$row['ustva_kz']] = [ 'netto' => (float)$row['summe_netto'], 'ust' => (float)$row['summe_ust'], ]; } // ── 2. Steuerart-basierte Kennzahlen ───────────────────────────────── $stRows = Database::fetchAll( 'SELECT bk.steuerart, 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 IN (' . $euPlaceholders . ') GROUP BY bk.steuerart', array_merge([$year, $month], self::EU_STEUERARTEN) ); $bySteuerart = []; foreach ($stRows as $row) { $bySteuerart[$row['steuerart']] = (float)$row['summe_netto']; } // ── 3. Kennzahlen zusammensetzen ───────────────────────────────────── $n = fn(string $kz): float => (float)($byKz[$kz]['netto'] ?? 0.0); $u = fn(string $kz): float => (float)($byKz[$kz]['ust'] ?? 0.0); $st = fn(string $art): float => (float)($bySteuerart[$art] ?? 0.0); $kz81 = $n('81'); $kz83 = $u('81'); $kz86 = $n('86'); $kz84 = $u('86'); $kz35 = $n('35'); $kz41 = $n('41') + $st('ig_lieferung'); $kz44 = $n('44') + $st('export'); $kz89 = $st('ig_erwerb_19'); $kz91 = round($kz89 * 0.19, 2); $kz93 = $st('ig_erwerb_7'); $kz94 = round($kz93 * 0.07, 2); $kz61 = $u('61'); $kz62 = $u('62'); $kz67 = $kz91 + $kz94; $kz66 = $kz61 + $kz62 + $kz67; $kz69 = ($kz83 + $kz84 + $kz91 + $kz94) - $kz66; return [ '81' => ['name' => 'Steuerpfl. Umsätze 19 %', 'betrag' => round($kz81, 2), 'steuer' => null], '83' => ['name' => 'USt auf steuerpfl. Umsätze 19 %', 'betrag' => null, 'steuer' => round($kz83, 2)], '86' => ['name' => 'Steuerpfl. Umsätze 7 %', 'betrag' => round($kz86, 2), 'steuer' => null], '84' => ['name' => 'USt auf steuerpfl. Umsätze 7 %', 'betrag' => null, 'steuer' => round($kz84, 2)], '35' => ['name' => 'Steuerfreie Umsätze (ohne Vorsteuerabzug)', 'betrag' => round($kz35, 2), 'steuer' => null], '41' => ['name' => 'Steuerfreie ig. Lieferungen (§ 4 Nr. 1b)', 'betrag' => round($kz41, 2), 'steuer' => null], '44' => ['name' => 'Steuerfreie Ausfuhren (§ 4 Nr. 1a)', 'betrag' => round($kz44, 2), 'steuer' => null], '89' => ['name' => 'Ig. Erwerb 19 % – Bemessungsgrundlage', 'betrag' => round($kz89, 2), 'steuer' => null], '91' => ['name' => 'USt auf ig. Erwerb 19 %', 'betrag' => null, 'steuer' => round($kz91, 2)], '93' => ['name' => 'Ig. Erwerb 7 % – Bemessungsgrundlage', 'betrag' => round($kz93, 2), 'steuer' => null], '94' => ['name' => 'USt auf ig. Erwerb 7 %', 'betrag' => null, 'steuer' => round($kz94, 2)], '61' => ['name' => 'Vorsteuer aus Eingangsrechnungen 19 %', 'betrag' => null, 'steuer' => round($kz61, 2)], '62' => ['name' => 'Vorsteuer aus Eingangsrechnungen 7 %', 'betrag' => null, 'steuer' => round($kz62, 2)], '67' => ['name' => 'Vorsteuer aus ig. Erwerb', 'betrag' => null, 'steuer' => round($kz67, 2)], '66' => ['name' => 'Vorsteuerbeträge gesamt', 'betrag' => null, 'steuer' => round($kz66, 2)], '69' => ['name' => 'Verbleibende USt-Vorauszahlung/Erstattung', 'betrag' => null, 'steuer' => round($kz69, 2)], ]; } /** * Liefert alle Buchungszeilen, die zu einer Kennzahl beitragen (Drill-down). * Kombiniert kontobasierte und steuerart-basierte Quellen per UNION. */ 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, bl.beleg_nr, k.kontonummer AS kategorie_nr, k.bezeichnung AS kategorie_name, z.kontonummer AS zahlungs_nr, z.bezeichnung AS zahlungs_name 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 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); } $sql = implode(' UNION ALL ', $parts) . ' ORDER BY buchungs_datum, id'; return Database::fetchAll($sql, $params); } }