diff --git a/BERICHTE.md b/BERICHTE.md index 89b8047..d0d1786 100644 --- a/BERICHTE.md +++ b/BERICHTE.md @@ -42,30 +42,50 @@ Alle passenden Buchungen werden nach Kategorie-Konto gruppiert und als Betriebsa **Datei:** `src/Services/UstvaCalculator.php` -Die UStVA verwendet **keine** Kontonummern, sondern die Buchungsfelder `buchungstyp`, `steuerart`, `ust_satz` und `betrag_ust`. Stornobuchungen (`is_reversal = 1`) werden ausgeschlossen. +Die UStVA basiert ausschließlich auf dem Feld `accounts.ustva_kz` des Kategorie-Kontos einer Buchung. Die Felder `buchungstyp` und `steuerart` werden nicht ausgewertet. Stornobuchungen (`is_reversal = 1`) werden ausgeschlossen. -### Direkt befüllte Kennzahlen +### Berechnungslogik -| Kz | Bezeichnung | Bedingung | -|---|---|---| -| **81** | Steuerpfl. Umsätze 19 % – Netto | `buchungstyp='Einnahme'`, `steuerart='normal'`, `ust_satz=19` | -| **86** | Steuerpfl. Umsätze 7 % – Netto | `buchungstyp='Einnahme'`, `steuerart='normal'`, `ust_satz=7` | -| **35** | Steuerfreie Umsätze (ohne Vorsteuerabzug) – Netto | `buchungstyp='Einnahme'`, `steuerart='normal'`, `ust_satz=0` | -| **41** | Steuerfreie ig. Lieferungen (§ 4 Nr. 1b) – Netto | `buchungstyp='Einnahme'`, `steuerart='ig_lieferung'` | -| **44** | Steuerfreie Ausfuhren (§ 4 Nr. 1a) – Netto | `buchungstyp='Einnahme'`, `steuerart='export'` | -| **89** | Ig. Erwerb 19 % – Bemessungsgrundlage | `buchungstyp='Ausgabe'`, `steuerart='ig_erwerb_19'` | -| **93** | Ig. Erwerb 7 % – Bemessungsgrundlage | `buchungstyp='Ausgabe'`, `steuerart='ig_erwerb_7'` | -| **61** | Vorsteuer aus Eingangsrechnungen 19 % | `buchungstyp='Ausgabe'`, `steuerart='normal'`, `ust_satz=19`, `betrag_ust>0` | -| **62** | Vorsteuer aus Eingangsrechnungen 7 % | `buchungstyp='Ausgabe'`, `steuerart='normal'`, `ust_satz=7`, `betrag_ust>0` | +Pro Monat wird eine einzige Aggregationsabfrage ausgeführt, die `betrag_netto` und `betrag_ust` nach `ustva_kz` des Kategorie-Kontos gruppiert: -### Abgeleitete Kennzahlen (berechnet, keine direkte Buchungszuordnung) +```sql +SELECT a.ustva_kz, SUM(betrag_netto), SUM(betrag_ust) +FROM bookings JOIN accounts a ON a.id = kategorie_konto_id +WHERE ... AND a.ustva_kz IS NOT NULL +GROUP BY a.ustva_kz +``` + +### Kontozuordnung und Kennzahlen + +| Kz | Bezeichnung | Quelle | Relevante Konten (Beispiele) | +|---|---|---|---| +| **81** | Steuerpfl. Umsätze 19 % | `SUM(betrag_netto)` WHERE `ustva_kz='81'` | 8000, 8400, 8510, 8570, 8800, 8820, 8922 | +| **83** | USt auf steuerpfl. Umsätze 19 % | `SUM(betrag_ust)` WHERE `ustva_kz='81'` | s.o. | +| **86** | Steuerpfl. Umsätze 7 % | `SUM(betrag_netto)` WHERE `ustva_kz='86'` | 8300 | +| **84** | USt auf steuerpfl. Umsätze 7 % | `SUM(betrag_ust)` WHERE `ustva_kz='86'` | s.o. | +| **35** | Steuerfreie Umsätze | `SUM(betrag_netto)` WHERE `ustva_kz='35'` | 8100, 8105, 8110, 8160 | +| **41** | Steuerfreie ig. Lieferungen | `SUM(betrag_netto)` WHERE `ustva_kz='41'` | 8120, 8125, 8130 | +| **44** | Steuerfreie Ausfuhren | `SUM(betrag_netto)` WHERE `ustva_kz='44'` | 8150 | +| **61** | Vorsteuer 19 % | `SUM(betrag_ust)` WHERE `ustva_kz='61'` | alle Betriebsausgabe-Konten mit `steuercode='VSt19'` (z.B. 3100, 4200ff, 4900ff) | +| **62** | Vorsteuer 7 % | `SUM(betrag_ust)` WHERE `ustva_kz='62'` | alle Betriebsausgabe-Konten mit `steuercode='VSt7'` (z.B. 3110, 4610, 4680) | +| **89** | Ig. Erwerb 19 % – Bemessungsgrundlage | `SUM(betrag_netto)` WHERE `ustva_kz='89'` | 3425 | +| **91** | USt auf ig. Erwerb 19 % | `SUM(betrag_ust)` WHERE `ustva_kz='89'` | 3425 | +| **93** | Ig. Erwerb 7 % – Bemessungsgrundlage | `SUM(betrag_netto)` WHERE `ustva_kz='93'` | 3420 | +| **94** | USt auf ig. Erwerb 7 % | `SUM(betrag_ust)` WHERE `ustva_kz='93'` | 3420 | + +### Abgeleitete Kennzahlen | Kz | Bezeichnung | Berechnung | |---|---|---| -| **83** | USt auf steuerpfl. Umsätze 19 % | Kz 81 × 19 % | -| **84** | USt auf steuerpfl. Umsätze 7 % | Kz 86 × 7 % | -| **91** | USt auf ig. Erwerb 19 % | Kz 89 × 19 % | -| **94** | USt auf ig. Erwerb 7 % | Kz 93 × 7 % | | **67** | Vorsteuer aus ig. Erwerb | Kz 91 + Kz 94 | | **66** | Vorsteuerbeträge gesamt | Kz 61 + Kz 62 + Kz 67 | | **69** | Verbleibende USt-Vorauszahlung / Erstattung | (Kz 83 + Kz 84 + Kz 91 + Kz 94) − Kz 66 | + +### Neue Konten in Klasse 3 (Migration 010) + +| Konto | Bezeichnung | ustva_kz | +|---|---|---| +| 3420 | Innergemeinschaftliche Erwerbe 7 % Vorsteuer | 93 | +| 3425 | Innergemeinschaftliche Erwerbe 19 % Vorsteuer | 89 | + +Buchungen auf ig. Erwerb müssen auf Konto 3420 bzw. 3425 als Kategorie-Konto erfolgen. diff --git a/Changes.md b/Changes.md index 2ff5562..443a512 100644 --- a/Changes.md +++ b/Changes.md @@ -2,6 +2,46 @@ --- +## 2026-03-29 – UStVA: kontonummernbasierte Kennzahlenberechnung + +### Geändert +- `UstvaCalculator::calculate()` wertet ausschließlich `accounts.ustva_kz` des Kategorie-Kontos aus – `buchungstyp` und `steuerart` werden nicht mehr benötigt +- `UstvaCalculator::fetchBookingsForKz()` filtert über `k.ustva_kz IN (...)` statt über Buchungsfelder +- Kz 83 und 84 kommen jetzt direkt aus `betrag_ust` der Erlöskonten (statt `Kz81 × 19 %`) +- Kz 91 und 94 kommen direkt aus `betrag_ust` der ig.-Erwerb-Konten (statt abgeleitet) + +### Betrifft +- `src/Services/UstvaCalculator.php` +- Migration `010_ustva_kz_konto_mapping.sql` +- `BERICHTE.md` (UStVA-Kapitel vollständig neu) + +### Migration 010 – Kontenanpassungen +- Alle Betriebsausgabe-Konten mit `steuercode='VSt19'`: `ustva_kz` von `'66'` auf `'61'` +- Alle Betriebsausgabe-Konten mit `steuercode='VSt7'`: `ustva_kz` von `'66'` auf `'62'` +- Neue Konten: 3425 „Innergemeinschaftliche Erwerbe 19 %" (`ustva_kz='89'`), 3420 „Innergemeinschaftliche Erwerbe 7 %" (`ustva_kz='93'`) + +--- + +## 2026-03-28 – 30% Eigenanteil-Kennzeichnung und Korrekturbuchungen + +### Hinzugefügt +- Checkbox „30% Eigenanteil buchen" in der Buchungszeilen-Erfassung (zwischen Nettobetrag und Submit-Button) +- Neues Feld `eigenanteil_30 TINYINT(1)` in der `belege`-Tabelle (Migration `009_beleg_eigenanteil_30.sql`) +- Der Checkbox-Wert wird beim Speichern einer Buchung in die Beleg-Metadaten übernommen +- Bei gesetzter Checkbox werden automatisch zwei Korrekturbuchungen angelegt oder aktualisiert: + - 30% des Nettobetrags an Konto 8922 (nicht abzugsfähiger Eigenanteil), Zahlungskonto 1800 + - 30% der errechneten USt an Konto 1776 (Vorsteuerkorrektur), Zahlungskonto 1800 – nur wenn USt-Satz > 0 +- Im Testmodus: bestehende Korrekturbuchungen werden direkt überschrieben +- Im Produktivmodus: bestehende Korrekturbuchungen werden GoBD-konform storniert und neu angelegt + +### Betrifft +- `src/Views/belege/detail.php` +- `src/Controllers/BookingController.php` +- `src/Models/Booking.php` (neue Methoden `upsertEigenanteil30`, `upsertKorrekturbuchung`) +- Migration: `009_beleg_eigenanteil_30.sql` + +--- + ## 2026-03-27 – DATEV-konformer SKR03 Kontenrahmen (#11) ### Geändert diff --git a/migrations/009_beleg_eigenanteil_30.sql b/migrations/009_beleg_eigenanteil_30.sql new file mode 100644 index 0000000..1595c5b --- /dev/null +++ b/migrations/009_beleg_eigenanteil_30.sql @@ -0,0 +1,5 @@ +-- Migration 009: Eigenanteil-30%-Kennzeichnung in belege-Tabelle +ALTER TABLE belege + ADD COLUMN eigenanteil_30 TINYINT(1) NOT NULL DEFAULT 0 + COMMENT '1 = 30% Eigenanteil wurde beim Buchen markiert' + AFTER betrag_brutto; diff --git a/migrations/010_ustva_kz_konto_mapping.sql b/migrations/010_ustva_kz_konto_mapping.sql new file mode 100644 index 0000000..0fd3aaf --- /dev/null +++ b/migrations/010_ustva_kz_konto_mapping.sql @@ -0,0 +1,25 @@ +-- Migration 010: UStVA-Kennzahlen direkt aus Kontozuordnung +-- +-- 1. Betriebsausgabe-Konten: ustva_kz '66' → '61' (VSt19) bzw. '62' (VSt7) +-- Damit werden Kz 61 und 62 direkt aus betrag_ust der jeweiligen Buchungen summiert. +UPDATE accounts + SET ustva_kz = '61' + WHERE kontenart = 'Betriebsausgabe' + AND steuercode = 'VSt19'; + +UPDATE accounts + SET ustva_kz = '62' + WHERE kontenart = 'Betriebsausgabe' + AND steuercode = 'VSt7'; + +-- 2. Konten für Innergemeinschaftlichen Erwerb anlegen (Kz 89 / 93) +-- 3425: ig. Erwerb 19 % → Kz 89 (Bemessungsgrundlage); betrag_ust → Kz 91 +-- 3420: ig. Erwerb 7 % → Kz 93 (Bemessungsgrundlage); betrag_ust → Kz 94 +INSERT INTO accounts (kontonummer, bezeichnung, kontenart, steuercode, ustva_kz, aktiv, is_system) +VALUES + ('3425', 'Innergemeinschaftliche Erwerbe 19 % Vorsteuer', 'Betriebsausgabe', 'VSt19', '89', 1, 1), + ('3420', 'Innergemeinschaftliche Erwerbe 7 % Vorsteuer', 'Betriebsausgabe', 'VSt7', '93', 1, 1) +ON DUPLICATE KEY UPDATE + bezeichnung = VALUES(bezeichnung), + steuercode = VALUES(steuercode), + ustva_kz = VALUES(ustva_kz); diff --git a/public/index.php b/public/index.php index 8a1f8e9..5da8efa 100644 --- a/public/index.php +++ b/public/index.php @@ -98,6 +98,7 @@ $router->get('/tax/ustva', fn($p) => (new UstvaController())->index($p)); $router->get('/tax/ustva/{year}/{month}', fn($p) => (new UstvaController())->show($p)); $router->post('/tax/ustva/{year}/{month}/save', fn($p) => (new UstvaController())->save($p)); $router->get('/tax/ustva/{year}/{month}/xml', fn($p) => (new UstvaController())->downloadXml($p)); +$router->get('/tax/ustva/{year}/{month}/kz/{kz}', fn($p) => (new UstvaController())->showKzBookings($p)); // ── EÜR ───────────────────────────────────────────────────── $router->get('/tax/euer', fn($p) => (new EuerController())->index($p)); diff --git a/src/Controllers/BookingController.php b/src/Controllers/BookingController.php index b73918a..636dabd 100644 --- a/src/Controllers/BookingController.php +++ b/src/Controllers/BookingController.php @@ -110,6 +110,22 @@ class BookingController try { $data['beleg_id'] = $belegId; Booking::create($data); + + $eigenanteil = isset($data['eigenanteil_30']) ? 1 : 0; + \Steuerkram\Database::execute( + 'UPDATE belege SET eigenanteil_30 = ?, updated_at = NOW() WHERE id = ?', + [$eigenanteil, $belegId] + ); + + if ($eigenanteil === 1) { + Booking::upsertEigenanteil30( + $belegId, + (float)$data['betrag_netto'], + (float)($data['ust_satz'] ?? 0), + $data['buchungs_datum'] + ); + } + View::redirect("/belege/{$belegId}", 'Buchung erfasst.'); } catch (\PDOException $e) { error_log('[Steuerkram] PDO-Fehler in BookingController::store(): ' . $e->getMessage()); diff --git a/src/Controllers/UstvaController.php b/src/Controllers/UstvaController.php index c644fb1..059c174 100644 --- a/src/Controllers/UstvaController.php +++ b/src/Controllers/UstvaController.php @@ -98,6 +98,38 @@ class UstvaController View::redirect("/tax/ustva/{$year}/{$month}", 'UStVA gespeichert.'); } + public function showKzBookings(array $params): void + { + $year = (int)$params['year']; + $month = (int)$params['month']; + $kz = $params['kz'] ?? ''; + + $validKz = ['81','83','86','84','35','41','44','89','91','93','94','61','62','67','66','69']; + if (!in_array($kz, $validKz, true)) { + View::redirect("/tax/ustva/{$year}/{$month}", 'Ungültige Kennzahl.', 'error'); + return; + } + + $calc = new UstvaCalculator(); + $kzData = $calc->calculate($year, $month); + $bookings = $calc->fetchBookingsForKz($year, $month, $kz); + + $months = ['Januar','Februar','März','April','Mai','Juni','Juli','August','September','Oktober','November','Dezember']; + + $view = new View(); + $view->render('layout/base.php', [ + 'pageTitle' => "Kz {$kz} – {$months[$month-1]} {$year}", + 'content' => $this->renderView('tax/ustva_kz_bookings', [ + 'year' => $year, + 'month' => $month, + 'kz' => $kz, + 'kzName' => $kzData[$kz]['name'] ?? '', + 'bookings' => $bookings, + 'months' => $months, + ]), + ]); + } + public function downloadXml(array $params): void { $year = (int)$params['year']; diff --git a/src/Models/Booking.php b/src/Models/Booking.php index 4ab52fd..04c9d0a 100644 --- a/src/Models/Booking.php +++ b/src/Models/Booking.php @@ -182,14 +182,113 @@ class Booking return $reversalId; } + public static function upsertEigenanteil30( + int $belegId, + float $betragNetto, + float $ustSatz, + string $buchungsDatum + ): void { + $acc8922 = Account::findByKontonummer('8922'); + $acc1800 = Account::findByKontonummer('1800'); + $acc1776 = Account::findByKontonummer('1776'); + + if (!$acc8922 || !$acc1800) { + throw new \RuntimeException('Konten 1800 und/oder 8922 nicht gefunden.'); + } + + $netto30 = round($betragNetto * 0.3, 2); + $ust30 = round($betragNetto * $ustSatz / 100 * 0.3, 2); + $isTest = Setting::isTestMode(); + + // Buchung 1: 30% Netto an 8922 (Eigenanteil Betriebsausgabe) + self::upsertKorrekturbuchung( + belegId: $belegId, + kategorieId: (int)$acc8922['id'], + zahlungsId: (int)$acc1800['id'], + betragNetto: $netto30, + buchungsDatum: $buchungsDatum, + buchungstext: '30% Eigenanteil (nicht abzugsfähig)', + isTest: $isTest, + ); + + // Buchung 2: 30% USt-Korrektur 1800→1776 (nur wenn USt > 0) + if ($ustSatz > 0 && $acc1776) { + self::upsertKorrekturbuchung( + belegId: $belegId, + kategorieId: (int)$acc1776['id'], + zahlungsId: (int)$acc1800['id'], + betragNetto: $ust30, + buchungsDatum: $buchungsDatum, + buchungstext: '30% Vorsteuerkorrektur Eigenanteil', + isTest: $isTest, + ); + } + } + + private static function upsertKorrekturbuchung( + int $belegId, + int $kategorieId, + int $zahlungsId, + float $betragNetto, + string $buchungsDatum, + string $buchungstext, + bool $isTest, + ): void { + $existing = Database::fetchOne( + 'SELECT * FROM bookings + WHERE beleg_id = ? AND kategorie_konto_id = ? AND zahlungskonto_id = ? + AND is_reversal = 0 AND reversed_by IS NULL + LIMIT 1', + [$belegId, $kategorieId, $zahlungsId] + ); + + if ($existing) { + if ($isTest) { + Database::execute( + 'UPDATE bookings + SET betrag_netto = ?, betrag_ust = 0, betrag_brutto = ?, + buchungs_datum = ?, buchungstext = ? + WHERE id = ?', + [$betragNetto, $betragNetto, $buchungsDatum, $buchungstext, $existing['id']] + ); + } else { + self::reverse((int)$existing['id'], 'Anpassung 30% Eigenanteil'); + self::create([ + 'beleg_id' => $belegId, + 'buchungs_datum' => $buchungsDatum, + 'buchungstyp' => 'Ausgabe', + 'kategorie_konto_id' => $kategorieId, + 'zahlungskonto_id' => $zahlungsId, + 'betrag_netto' => $betragNetto, + 'ust_satz' => 0, + 'steuerart' => 'normal', + 'buchungstext' => $buchungstext, + ]); + } + } else { + self::create([ + 'beleg_id' => $belegId, + 'buchungs_datum' => $buchungsDatum, + 'buchungstyp' => 'Ausgabe', + 'kategorie_konto_id' => $kategorieId, + 'zahlungskonto_id' => $zahlungsId, + 'betrag_netto' => $betragNetto, + 'ust_satz' => 0, + 'steuerart' => 'normal', + 'buchungstext' => $buchungstext, + ]); + } + } + public static function generateBuchungsNr(): string { $year = date('Y'); $result = Database::fetchOne( - 'SELECT COUNT(*) as cnt FROM bookings WHERE YEAR(buchungs_datum) = ?', - [$year] + "SELECT MAX(CAST(SUBSTRING_INDEX(buchungs_nr, '-', -1) AS UNSIGNED)) as maxnr + FROM bookings WHERE buchungs_nr LIKE ?", + ["BU-{$year}-%"] ); - $next = (int)($result['cnt'] ?? 0) + 1; + $next = (int)($result['maxnr'] ?? 0) + 1; return sprintf('BU-%s-%06d', $year, $next); } diff --git a/src/Services/ElsterXmlService.php b/src/Services/ElsterXmlService.php index 989ef61..01aefe3 100644 --- a/src/Services/ElsterXmlService.php +++ b/src/Services/ElsterXmlService.php @@ -8,8 +8,7 @@ use Steuerkram\Models\Setting; /** * Erzeugt eine ELSTER-konforme XML-Datei für die UStVA. - * Kompatibel mit dem Elster Online Portal (Dateiupload, keine ERiC-Signatur nötig). - * Schema: ElsterOnline v11 + * Format: Anmeldungssteuern Upload-Schema v{year} (kein ERiC nötig). */ class ElsterXmlService { @@ -17,204 +16,152 @@ class ElsterXmlService { $settings = Setting::get(); - $steuernummer = preg_replace('/[^0-9]/', '', $settings['ustva_steuernummer'] ?? ''); - $bundesland = strtoupper($settings['ustva_bundesland'] ?? 'DE'); - $erstelltAm = date('Y-m-d\TH:i:s'); - $zeitraum = sprintf('%04d%02d', $year, $month); - $firma = htmlspecialchars($settings['firma'] ?? '', ENT_XML1); - $email = htmlspecialchars($settings['email'] ?? '', ENT_XML1); + $steuernummer = preg_replace('/[^0-9]/', '', $settings['ustva_steuernummer'] ?? ''); + $inhaber = $settings['inhaber'] ?? $settings['firma'] ?? ''; + $firma = $settings['firma'] ?? ''; - // Build Kz fields XML - $kzXml = ''; - $kzMap = [ - '09' => 'Kz09', // Steuernummer (wird im Header gesetzt) - '10' => 'Kz10', // Voranmeldungszeitraum - '22' => 'Kz22', // Berichtigungsvoranmeldung (0=nein) - '81' => 'Kz81', - '86' => 'Kz86', - '35' => 'Kz35', - '41' => 'Kz41', - '44' => 'Kz44', - '83' => 'Kz83', - '84' => 'Kz84', - '66' => 'Kz66', - '61' => 'Kz61', - '62' => 'Kz62', - '69' => 'Kz69', - ]; + [$strasse, $hausnummer] = $this->splitStrasse($settings['strasse'] ?? ''); + [$vorname, $nachname] = $this->splitName($inhaber); - // Helper: format amount for ELSTER (integer cents, no decimals for Beträge; or in EUR with 2 dec) - $fmtBetrag = fn($v) => $v !== null ? number_format((float)$v, 2, '.', '') : '0.00'; - $fmtSteuer = fn($v) => $v !== null ? number_format((float)$v, 2, '.', '') : '0.00'; + $x = fn(string $v): string => htmlspecialchars($v, ENT_XML1, 'UTF-8'); - $fields = []; - - // Steuernummer und Voranmeldungszeitraum - $fields['09'] = $steuernummer; - $fields['10'] = $zeitraum; - $fields['22'] = '0'; // Keine Berichtigungsvoranmeldung - - foreach ($kz as $nr => $data) { - if ($data['betrag'] !== null && $data['betrag'] != 0) { - $fields[$nr . '_betrag'] = $fmtBetrag($data['betrag']); - } - if ($data['steuer'] !== null && $data['steuer'] != 0) { - $fields[$nr . '_steuer'] = $fmtSteuer($data['steuer']); - } - } - - // Build XML $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->formatOutput = true; - $elster = $dom->createElementNS('http://www.elster.de/elsterxml/schema/v11', 'Elster'); - $dom->appendChild($elster); - - // TransferHeader - $th = $dom->createElement('TransferHeader'); - $th->appendChild($dom->createElement('Version', '11')); - $th->appendChild($dom->createElement('TransferTicket', '')); - $th->appendChild($dom->createElement('Testmerker', '700000004')); - $th->appendChild($dom->createElement('HerstellerID', '74931')); - $th->appendChild($dom->createElement('DatenArt', 'UStVA')); - $th->appendChild($dom->createElement('Vorgang', 'send-Auth')); - $elster->appendChild($th); - - // DatenTeil - $dt = $dom->createElement('DatenTeil'); - $elster->appendChild($dt); - - $nb = $dom->createElement('Nutzdatenblock'); - $dt->appendChild($nb); - - // NutzdatenHeader - $ndh = $dom->createElement('NutzdatenHeader'); - $ndh->setAttribute('version', '11'); - $ndh->appendChild($dom->createElement('NutzdatenTicket', '1')); - $empf = $dom->createElement('Empfaenger'); - $empf->setAttribute('id', 'F'); - $empf->nodeValue = $this->getFinanzamtCode($bundesland); - $ndh->appendChild($empf); - $hersteller = $dom->createElement('Hersteller'); - $hersteller->appendChild($dom->createElement('ProduktName', 'Steuerkram')); - $hersteller->appendChild($dom->createElement('ProduktVersion', '1.0')); - $ndh->appendChild($hersteller); - $nb->appendChild($ndh); - - // Nutzdaten - $nd = $dom->createElement('Nutzdaten'); - $nb->appendChild($nd); - - // Anmeldungssteuern - $anm = $dom->createElement('Anmeldungssteuern'); - $anm->setAttribute('art', 'UStVA'); - $anm->setAttribute('version', sprintf('%04d%02d', $year, $month)); - $nd->appendChild($anm); + $ns = sprintf('http://finkonsens.de/elster/elsteranmeldung/ustva/v%d', $year); + $anm = $dom->createElementNS($ns, 'Anmeldungssteuern'); + $anm->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns', $ns); + $anm->setAttribute('version', (string)$year); + $dom->appendChild($anm); // DatenLieferant $dl = $dom->createElement('DatenLieferant'); - $dl->appendChild($dom->createElement('Name', $firma)); - $dl->appendChild($dom->createElement('Strasse', htmlspecialchars($settings['strasse'] ?? '', ENT_XML1))); - $dl->appendChild($dom->createElement('PLZ', htmlspecialchars($settings['plz'] ?? '', ENT_XML1))); - $dl->appendChild($dom->createElement('Ort', htmlspecialchars($settings['ort'] ?? '', ENT_XML1))); - if ($email) $dl->appendChild($dom->createElement('Email', $email)); + foreach ([ + 'Name' => $x($inhaber), + 'Strasse' => $x($settings['strasse'] ?? ''), + 'PLZ' => $x($settings['plz'] ?? ''), + 'Ort' => $x($settings['ort'] ?? ''), + 'Telefon' => $x($settings['telefon'] ?? ''), + 'Email' => $x($settings['email'] ?? ''), + ] as $tag => $val) { + $dl->appendChild($dom->createElement($tag, $val)); + } $anm->appendChild($dl); - // Erstellungsdatum $anm->appendChild($dom->createElement('Erstellungsdatum', date('Ymd'))); // Steuerfall $sf = $dom->createElement('Steuerfall'); $anm->appendChild($sf); + // Berater (leer) + $berater = $dom->createElement('Berater'); + foreach ([ + 'Bezeichnung','Name','Vorname','Namensvorsatz','Namenszusatz', + 'Str','Hausnummer','HNrZusatz','AnschriftenZusatz','Ort','PLZ', + 'AuslandsPLZ','Land','PostfachOrt','Postfach','PostfachPLZ', + 'GKPLZ','Telefon','Email', + ] as $tag) { + $berater->appendChild($dom->createElement($tag)); + } + $sf->appendChild($berater); + + // Mandant + $mandant = $dom->createElement('Mandant'); + foreach ([ + 'Name' => $x($nachname), + 'Vorname' => $x($vorname), + 'MandantenNr' => '', + 'Bearbeiterkennzeichen' => '', + ] as $tag => $val) { + $mandant->appendChild($dom->createElement($tag, $val)); + } + $sf->appendChild($mandant); + + // Unternehmer + $unternehmer = $dom->createElement('Unternehmer'); + foreach ([ + 'Bezeichnung' => $x($firma), + 'Name' => $x($nachname), + 'Vorname' => $x($vorname), + 'Namensvorsatz' => '', + 'Namenszusatz' => '', + 'Str' => $x($strasse), + 'Hausnummer' => $x($hausnummer), + 'HNrZusatz' => '', + 'AnschriftenZusatz' => '', + 'Ort' => $x($settings['ort'] ?? ''), + 'PLZ' => $x($settings['plz'] ?? ''), + 'AuslandsPLZ' => '', + 'Land' => 'Deutschland', + 'PostfachOrt' => '', + 'Postfach' => '', + 'PostfachPLZ' => '', + 'GKPLZ' => '', + 'Telefon' => $x($settings['telefon'] ?? ''), + 'Email' => $x($settings['email'] ?? ''), + ] as $tag => $val) { + $unternehmer->appendChild($dom->createElement($tag, $val)); + } + $sf->appendChild($unternehmer); + + // Umsatzsteuervoranmeldung $ustva = $dom->createElement('Umsatzsteuervoranmeldung'); $sf->appendChild($ustva); - // Jahr, Zeitraum $ustva->appendChild($dom->createElement('Jahr', (string)$year)); $ustva->appendChild($dom->createElement('Zeitraum', sprintf('%02d', $month))); - - // Steuernummer $ustva->appendChild($dom->createElement('Steuernummer', $steuernummer)); - // Kz-Felder - // Kz81 (Umsatz 19%) - if (!empty($kz['81']['betrag'])) { - $kzEl = $dom->createElement('Kz81', (string)round($kz['81']['betrag'])); - $ustva->appendChild($kzEl); + // Kz-Felder: Bemessungsgrundlagen ganzzahlig, Steuerbeträge mit 2 Dezimalstellen. + // Kz83/Kz84 werden nicht übertragen (serverseitig aus Kz81/Kz86 abgeleitet). + // Kz69 (Zahllast) wird als Kz39 übertragen. + $kzBetrag = fn(float $v): string => (string)(int)round($v); + $kzSteuer = fn(float $v): string => number_format($v, 2, '.', ''); + + $betragsfelder = ['81', '86', '35', '41', '44', '89', '93']; + foreach ($betragsfelder as $nr) { + $val = (float)($kz[$nr]['betrag'] ?? 0.0); + if ($val != 0.0) { + $ustva->appendChild($dom->createElement('Kz' . $nr, $kzBetrag($val))); + } } - // Kz86 (Umsatz 7%) - if (!empty($kz['86']['betrag'])) { - $kzEl = $dom->createElement('Kz86', (string)round($kz['86']['betrag'])); - $ustva->appendChild($kzEl); + + $steuerfelder = ['61', '62', '67', '66']; + foreach ($steuerfelder as $nr) { + $val = (float)($kz[$nr]['steuer'] ?? 0.0); + if ($val != 0.0) { + $ustva->appendChild($dom->createElement('Kz' . $nr, $kzSteuer($val))); + } } - // Kz35 (steuerfreie Umsätze) - if (!empty($kz['35']['betrag'])) { - $kzEl = $dom->createElement('Kz35', (string)round($kz['35']['betrag'])); - $ustva->appendChild($kzEl); - } - // Kz41 (steuerfreie ig. Lieferungen) - if (!empty($kz['41']['betrag'])) { - $kzEl = $dom->createElement('Kz41', (string)round($kz['41']['betrag'])); - $ustva->appendChild($kzEl); - } - // Kz44 (steuerfreie Ausfuhren) - if (!empty($kz['44']['betrag'])) { - $kzEl = $dom->createElement('Kz44', (string)round($kz['44']['betrag'])); - $ustva->appendChild($kzEl); - } - // Kz83 (USt 19%) - $kzEl = $dom->createElement('Kz83', (string)(round($kz['83']['steuer'] * 100))); - $ustva->appendChild($kzEl); - // Kz84 (USt 7%) - $kzEl = $dom->createElement('Kz84', (string)(round($kz['84']['steuer'] * 100))); - $ustva->appendChild($kzEl); - // Kz89 (ig. Erwerb 19% Bemessungsgrundlage) - if (!empty($kz['89']['betrag'])) { - $kzEl = $dom->createElement('Kz89', (string)round($kz['89']['betrag'])); - $ustva->appendChild($kzEl); - } - // Kz91 (USt auf ig. Erwerb 19%) - if (!empty($kz['91']['steuer'])) { - $kzEl = $dom->createElement('Kz91', (string)(round($kz['91']['steuer'] * 100))); - $ustva->appendChild($kzEl); - } - // Kz93 (ig. Erwerb 7% Bemessungsgrundlage) - if (!empty($kz['93']['betrag'])) { - $kzEl = $dom->createElement('Kz93', (string)round($kz['93']['betrag'])); - $ustva->appendChild($kzEl); - } - // Kz94 (USt auf ig. Erwerb 7%) - if (!empty($kz['94']['steuer'])) { - $kzEl = $dom->createElement('Kz94', (string)(round($kz['94']['steuer'] * 100))); - $ustva->appendChild($kzEl); - } - // Kz66 (Vorsteuer gesamt) - if (!empty($kz['66']['steuer'])) { - $kzEl = $dom->createElement('Kz66', (string)(round($kz['66']['steuer'] * 100))); - $ustva->appendChild($kzEl); - } - // Kz67 (Vorsteuer aus ig. Erwerb) - if (!empty($kz['67']['steuer'])) { - $kzEl = $dom->createElement('Kz67', (string)(round($kz['67']['steuer'] * 100))); - $ustva->appendChild($kzEl); - } - // Kz69 (Vorauszahlung / Überschuss) - $kzEl = $dom->createElement('Kz69', (string)(round($kz['69']['steuer'] * 100))); - $ustva->appendChild($kzEl); + + // Zahllast / Erstattung → Kz39 + $zahllast = (float)($kz['69']['steuer'] ?? 0.0); + $ustva->appendChild($dom->createElement('Kz39', $kzSteuer($zahllast))); return $dom->saveXML(); } - private function getFinanzamtCode(string $bundesland): string + /** + * Trennt Straßenname und Hausnummer. "Bietigheimer Str. 67" → ["Bietigheimer Str.", "67"] + */ + private function splitStrasse(string $strasse): array { - // Standard-Finanzamt-Code pro Bundesland für ELSTER - $codes = [ - 'BW' => '2800', 'BY' => '9200', 'BE' => '1100', 'BB' => '3000', - 'HB' => '2400', 'HH' => '2200', 'HE' => '2600', 'MV' => '4000', - 'NI' => '2300', 'NW' => '5100', 'RP' => '2700', 'SL' => '1001', - 'SN' => '3200', 'ST' => '3100', 'SH' => '2100', 'TH' => '4100', - ]; - return $codes[$bundesland] ?? '9999'; + if (preg_match('/^(.*?)\s+(\d+\w*)$/', trim($strasse), $m)) { + return [$m[1], $m[2]]; + } + return [$strasse, '']; + } + + /** + * Trennt Vorname und Nachname. "Felix Schepmann" → ["Felix", "Schepmann"] + */ + private function splitName(string $name): array + { + $parts = explode(' ', trim($name), 2); + if (count($parts) === 2) { + return [$parts[0], $parts[1]]; + } + return ['', $name]; } } diff --git a/src/Services/UstvaCalculator.php b/src/Services/UstvaCalculator.php index 2149451..a54820b 100644 --- a/src/Services/UstvaCalculator.php +++ b/src/Services/UstvaCalculator.php @@ -9,137 +9,134 @@ use Steuerkram\Database; /** * Berechnet alle ELSTER-Kennzahlen (Kz) für die Umsatzsteuervoranmeldung. * - * Maßgeblich sind buchungstyp, steuerart, ust_satz und betrag_ust der Buchung. + * Maßgeblich ist ausschließlich das Feld accounts.ustva_kz des Kategorie-Kontos. * - * Buchungslogik: - * Umsatzsteuer (Kz 81/86): buchungstyp = 'Einnahme', steuerart = 'normal', ust_satz > 0 - * Steuerfreie Umsätze (Kz 35): buchungstyp = 'Einnahme', steuerart = 'normal', ust_satz = 0 - * Steuerfreie ig. Lieferungen (Kz 41): buchungstyp = 'Einnahme', steuerart = 'ig_lieferung' - * Ausfuhren (Kz 44): buchungstyp = 'Einnahme', steuerart = 'export' - * Vorsteuer (Kz 61/62): buchungstyp = 'Ausgabe', steuerart = 'normal', ust_satz > 0 - * Ig. Erwerb 19 % (Kz 89/91): steuerart = 'ig_erwerb_19' - * Ig. Erwerb 7 % (Kz 93/94): steuerart = 'ig_erwerb_7' - * Vorsteuer aus ig. Erwerb (Kz 67): abgeleitet aus Kz 91 + Kz 94 + * Kontozuordnung: + * ustva_kz = '81' → Kz 81 (betrag_netto), Kz 83 (betrag_ust) + * ustva_kz = '86' → Kz 86 (betrag_netto), Kz 84 (betrag_ust) + * ustva_kz = '35' → Kz 35 (betrag_netto) + * ustva_kz = '41' → Kz 41 (betrag_netto) + * ustva_kz = '44' → Kz 44 (betrag_netto) + * ustva_kz = '61' → Kz 61 (betrag_ust) [Betriebsausgabe-Konten mit VSt19] + * ustva_kz = '62' → Kz 62 (betrag_ust) [Betriebsausgabe-Konten mit VSt7] + * ustva_kz = '89' → Kz 89 (betrag_netto), Kz 91 (betrag_ust) [ig. Erwerb 19 %] + * ustva_kz = '93' → Kz 93 (betrag_netto), Kz 94 (betrag_ust) [ig. Erwerb 7 %] + * + * Abgeleitete Kennzahlen: + * Kz 67 = Kz 91 + Kz 94 (Vorsteuer aus ig. Erwerb) + * Kz 66 = Kz 61 + Kz 62 + Kz 67 (Vorsteuer gesamt) + * Kz 69 = (Kz 83 + Kz 84 + Kz 91 + Kz 94) − Kz 66 (Zahllast) */ class UstvaCalculator { + /** + * Welche accounts.ustva_kz-Werte liefern Buchungen für die Drill-down-Ansicht einer Kennzahl. + */ + private const KZ_ACCOUNT_MAP = [ + '81' => ['81'], + '83' => ['81'], + '86' => ['86'], + '84' => ['86'], + '35' => ['35'], + '41' => ['41'], + '44' => ['44'], + '61' => ['61'], + '62' => ['62'], + '89' => ['89'], + '91' => ['89'], + '93' => ['93'], + '94' => ['93'], + '67' => ['89', '93'], + '66' => ['61', '62', '89', '93'], + '69' => ['81', '86', '35', '41', '44', '61', '62', '89', '93'], + ]; + public function calculate(int $year, int $month): array { - $kz = [ - // Steuerpflichtige und steuerfreie Umsätze - '81' => ['name' => 'Steuerpfl. Umsätze 19 %', 'betrag' => 0.0, 'steuer' => 0.0], - '86' => ['name' => 'Steuerpfl. Umsätze 7 %', 'betrag' => 0.0, 'steuer' => 0.0], - '35' => ['name' => 'Steuerfreie Umsätze (ohne Vorsteuerabzug)', 'betrag' => 0.0, 'steuer' => null], - '41' => ['name' => 'Steuerfreie ig. Lieferungen (§ 4 Nr. 1b)', 'betrag' => 0.0, 'steuer' => null], - '44' => ['name' => 'Steuerfreie Ausfuhren (§ 4 Nr. 1a)', 'betrag' => 0.0, 'steuer' => null], - // USt auf Umsätze (abgeleitet) - '83' => ['name' => 'USt auf steuerpfl. Umsätze 19 %', 'betrag' => null, 'steuer' => 0.0], - '84' => ['name' => 'USt auf steuerpfl. Umsätze 7 %', 'betrag' => null, 'steuer' => 0.0], - // Innergemeinschaftlicher Erwerb - '89' => ['name' => 'Ig. Erwerb 19 % – Bemessungsgrundlage', 'betrag' => 0.0, 'steuer' => null], - '91' => ['name' => 'USt auf ig. Erwerb 19 %', 'betrag' => null, 'steuer' => 0.0], - '93' => ['name' => 'Ig. Erwerb 7 % – Bemessungsgrundlage', 'betrag' => 0.0, 'steuer' => null], - '94' => ['name' => 'USt auf ig. Erwerb 7 %', 'betrag' => null, 'steuer' => 0.0], - // Vorsteuer - '61' => ['name' => 'Vorsteuer aus Eingangsrechnungen 19 %', 'betrag' => null, 'steuer' => 0.0], - '62' => ['name' => 'Vorsteuer aus Eingangsrechnungen 7 %', 'betrag' => null, 'steuer' => 0.0], - '67' => ['name' => 'Vorsteuer aus ig. Erwerb', 'betrag' => null, 'steuer' => 0.0], - '66' => ['name' => 'Vorsteuerbeträge gesamt', 'betrag' => null, 'steuer' => 0.0], - // Ergebnis - '69' => ['name' => 'Verbleibende USt-Vorauszahlung/Erstattung', 'betrag' => null, 'steuer' => 0.0], - ]; - - $bookings = $this->fetchBookings($year, $month); - - foreach ($bookings as $b) { - $steuerart = $b['steuerart'] ?? 'normal'; - $ustSatz = (float)$b['ust_satz']; - $betragUst = abs((float)$b['betrag_ust']); - $betragNetto = abs((float)$b['betrag_netto']); - $buchungstyp = $b['buchungstyp']; - - // ── Einnahmen ─────────────────────────────────────────────── - if ($buchungstyp === 'Einnahme') { - switch ($steuerart) { - case 'ig_lieferung': - $kz['41']['betrag'] += $betragNetto; - break; - case 'export': - $kz['44']['betrag'] += $betragNetto; - break; - default: // normal - if ($ustSatz > 0) { - if (abs($ustSatz - 19) < 0.01) { - $kz['81']['betrag'] += $betragNetto; - } elseif (abs($ustSatz - 7) < 0.01) { - $kz['86']['betrag'] += $betragNetto; - } - } else { - $kz['35']['betrag'] += $betragNetto; - } - } - } - - // ── Ausgaben ──────────────────────────────────────────────── - if ($buchungstyp === 'Ausgabe') { - switch ($steuerart) { - case 'ig_erwerb_19': - $kz['89']['betrag'] += $betragNetto; - break; - case 'ig_erwerb_7': - $kz['93']['betrag'] += $betragNetto; - break; - default: // normal - if ($ustSatz > 0 && $betragUst > 0) { - if (abs($ustSatz - 19) < 0.01) { - $kz['61']['steuer'] += $betragUst; - } elseif (abs($ustSatz - 7) < 0.01) { - $kz['62']['steuer'] += $betragUst; - } - } - } - } - } - - // ── Abgeleitete Kennzahlen ─────────────────────────────────────── - $kz['83']['steuer'] = round($kz['81']['betrag'] * 0.19, 2); - $kz['84']['steuer'] = round($kz['86']['betrag'] * 0.07, 2); - - $kz['91']['steuer'] = round($kz['89']['betrag'] * 0.19, 2); - $kz['94']['steuer'] = round($kz['93']['betrag'] * 0.07, 2); - - // Vorsteuer aus ig. Erwerb = Steuer auf ig. Erwerb (vollständig abziehbar) - $kz['67']['steuer'] = round($kz['91']['steuer'] + $kz['94']['steuer'], 2); - - // Vorsteuer gesamt - $kz['66']['steuer'] = round( - $kz['61']['steuer'] + $kz['62']['steuer'] + $kz['67']['steuer'], - 2 - ); - - // Verbleibende Zahllast - $ustGesamt = $kz['83']['steuer'] + $kz['84']['steuer'] - + $kz['91']['steuer'] + $kz['94']['steuer']; - $kz['69']['steuer'] = round($ustGesamt - $kz['66']['steuer'], 2); - - // Runden - foreach ($kz as &$field) { - if ($field['betrag'] !== null) $field['betrag'] = round($field['betrag'], 2); - if ($field['steuer'] !== null) $field['steuer'] = round($field['steuer'], 2); - } - - return $kz; - } - - private function fetchBookings(int $year, int $month): array - { - return Database::fetchAll( - 'SELECT bk.buchungstyp, bk.steuerart, bk.ust_satz, bk.betrag_netto, bk.betrag_ust + $rows = 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 bk.is_reversal = 0', + AND bk.is_reversal = 0 + AND a.ustva_kz IS NOT NULL AND a.ustva_kz != \'\' + GROUP BY a.ustva_kz', [$year, $month] ); + + $byKz = []; + foreach ($rows as $row) { + $byKz[$row['ustva_kz']] = [ + 'netto' => (float)$row['summe_netto'], + 'ust' => (float)$row['summe_ust'], + ]; + } + + $n = fn(string $kz): float => abs($byKz[$kz]['netto'] ?? 0.0); + $u = fn(string $kz): float => abs($byKz[$kz]['ust'] ?? 0.0); + + $kz81 = $n('81'); $kz83 = $u('81'); + $kz86 = $n('86'); $kz84 = $u('86'); + $kz35 = $n('35'); + $kz41 = $n('41'); + $kz44 = $n('44'); + $kz89 = $n('89'); $kz91 = $u('89'); + $kz93 = $n('93'); $kz94 = $u('93'); + $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)], + ]; + } + + public function fetchBookingsForKz(int $year, int $month, string $kz): array + { + $accountKzList = self::KZ_ACCOUNT_MAP[$kz] ?? null; + if ($accountKzList === null) { + return []; + } + + $placeholders = implode(',', array_fill(0, count($accountKzList), '?')); + $params = array_merge([$year, $month], $accountKzList); + + return Database::fetchAll( + '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.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) = ? + AND bk.is_reversal = 0 + AND k.ustva_kz IN (' . $placeholders . ') + ORDER BY bk.buchungs_datum, bk.id', + $params + ); } } diff --git a/src/Views/belege/detail.php b/src/Views/belege/detail.php index ddaf798..0694118 100644 --- a/src/Views/belege/detail.php +++ b/src/Views/belege/detail.php @@ -391,7 +391,7 @@ $suppliers = $suppliers ?? [];
Keine Buchungen für diesen Zeitraum.
+| Datum | +Beleg | +Buchungstext | +Kategorie | +Zahlungskonto | +Netto | += $kz === '69' ? 'USt-Beitrag' : 'USt' ?> | +Brutto | +
|---|---|---|---|---|---|---|---|
| = $h($b['buchungs_datum']) ?> | ++ + = $h($b['beleg_nr'] ?? '#' . $b['beleg_id']) ?> + + – + + | += $h($b['buchungstext']) ?> | += $h($b['kategorie_nr']) ?> = $h($b['kategorie_name']) ?> | += $h($b['zahlungs_nr']) ?> = $h($b['zahlungs_name']) ?> | += $fmtKz($netto) ?> | += $fmtKz($ust) ?> | += $fmtKz($brutto) ?> | +
| Summe (= count($bookings) ?> Buchungen) | += $fmtKz($sumNetto) ?> | += $fmtKz($sumUst) ?> | += $fmtKz($sumBrutto) ?> | +||||