Fix: UStVA Vorsteuer-Kz-Aufteilung (Migration 022) + Tests korrigiert
This commit is contained in:
parent
aa36a16c5a
commit
e1f356d0cf
8 changed files with 195 additions and 111 deletions
48
BERICHTE.md
48
BERICHTE.md
|
|
@ -41,36 +41,29 @@ Die EÜR basiert auf dem Feld `accounts.kontenart` sowie `accounts.euer_zeile` d
|
|||
|
||||
**Datei:** `src/Services/UstvaCalculator.php`
|
||||
|
||||
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.
|
||||
Die UStVA kombiniert zwei Quellen (Migration 022):
|
||||
|
||||
### Berechnungslogik
|
||||
**Quelle 1 – kontobasiert** (`accounts.ustva_kz`): reguläre Umsätze und Vorsteuer. Buchungen mit EU-Steuerarten (s.u.) werden hier ausgeschlossen.
|
||||
|
||||
Pro Monat wird eine einzige Aggregationsabfrage ausgeführt, die `betrag_netto` und `betrag_ust` nach `ustva_kz` des Kategorie-Kontos gruppiert:
|
||||
|
||||
```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
|
||||
```
|
||||
**Quelle 2 – steuerartbasiert** (`bookings.steuerart`): EU-Sonderfälle (ig. Lieferung, Ausfuhr, ig. Erwerb). Diese Buchungen landen unabhängig vom Kategorie-Konto in den richtigen Kennzahlen.
|
||||
|
||||
### Kontozuordnung und Kennzahlen
|
||||
|
||||
| Kz | Bezeichnung | Quelle | Relevante Konten (Beispiele) |
|
||||
| Kz | Bezeichnung | Quelle | Erläuterung |
|
||||
|---|---|---|---|
|
||||
| **81** | Steuerpfl. Umsätze 19 % | `SUM(betrag_netto)` WHERE `ustva_kz='81'` | 8000, 8400, 8510, 8570, 8800, 8820, 8922 |
|
||||
| **81** | Steuerpfl. Umsätze 19 % | `SUM(betrag_netto)` WHERE `ustva_kz='81'` | 8400, 8570, 8720, 8801, 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 |
|
||||
| **86** | Steuerpfl. Umsätze 7 % | `SUM(betrag_netto)` WHERE `ustva_kz='86'` | 8300, 8710, 8921 |
|
||||
| **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 |
|
||||
| **35** | Steuerfreie Umsätze | `SUM(betrag_netto)` WHERE `ustva_kz='35'` | 8100, 8105, 8110 |
|
||||
| **41** | Steuerfreie ig. Lieferungen | kontobasiert (`ustva_kz='41'`) **+** `steuerart='ig_lieferung'` | 8125; oder Buchung mit steuerart |
|
||||
| **44** | Steuerfreie Ausfuhren | kontobasiert (`ustva_kz='44'`) **+** `steuerart='export'` | 8120; oder Buchung mit steuerart |
|
||||
| **61** | Vorsteuer 19 % | `SUM(betrag_ust)` WHERE `ustva_kz='61'` | alle Betriebsausgabe-Konten mit `steuercode='VSt19'` (Migration 022: früher kz=66) |
|
||||
| **62** | Vorsteuer 7 % | `SUM(betrag_ust)` WHERE `ustva_kz='62'` | alle Betriebsausgabe-Konten mit `steuercode='VSt7'` (Migration 022: früher kz=66) |
|
||||
| **89** | Ig. Erwerb 19 % – Bemessungsgrundlage | `SUM(betrag_netto)` WHERE `steuerart='ig_erwerb_19'` | kein spezielles Konto nötig |
|
||||
| **91** | USt auf ig. Erwerb 19 % | `Kz 89 × 0,19` | berechnet |
|
||||
| **93** | Ig. Erwerb 7 % – Bemessungsgrundlage | `SUM(betrag_netto)` WHERE `steuerart='ig_erwerb_7'` | kein spezielles Konto nötig |
|
||||
| **94** | USt auf ig. Erwerb 7 % | `Kz 93 × 0,07` | berechnet |
|
||||
|
||||
### Abgeleitete Kennzahlen
|
||||
|
||||
|
|
@ -80,11 +73,10 @@ GROUP BY a.ustva_kz
|
|||
| **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)
|
||||
### Migration 022 – Vorsteuer-Kz-Aufteilung
|
||||
|
||||
| Konto | Bezeichnung | ustva_kz |
|
||||
|---|---|---|
|
||||
| 3420 | Innergemeinschaftliche Erwerbe 7 % Vorsteuer | 93 |
|
||||
| 3425 | Innergemeinschaftliche Erwerbe 19 % Vorsteuer | 89 |
|
||||
Vor Migration 022 trugen alle Vorsteuer-Konten `ustva_kz='66'`, was dazu führte, dass Kz 61, 62 und 66 stets 0 ergaben. Migration 022 setzt:
|
||||
- `steuercode='VSt19'` → `ustva_kz='61'`
|
||||
- `steuercode='VSt7'` → `ustva_kz='62'`
|
||||
|
||||
Buchungen auf ig. Erwerb müssen auf Konto 3420 bzw. 3425 als Kategorie-Konto erfolgen.
|
||||
Kz 66 wird weiterhin rechnerisch als `Kz 61 + Kz 62 + Kz 67` aggregiert.
|
||||
|
|
|
|||
10
Changes.md
10
Changes.md
|
|
@ -2,6 +2,16 @@
|
|||
|
||||
---
|
||||
|
||||
## 2026-04-10 – UStVA Vorsteuer-Kz-Aufteilung (Migration 022)
|
||||
|
||||
### Fix
|
||||
- Migration 022: Vorsteuer-Konten von `ustva_kz='66'` auf `'61'` (VSt19) bzw. `'62'` (VSt7) aufgeteilt – Kz 61, 62 und 66 wurden zuvor stets als 0 ausgewiesen
|
||||
- `UstvaCalculator`: zweite Abfrage für steuerart-basierte EU-Kennzahlen (ig. Lieferung → Kz 41, Ausfuhr → Kz 44, ig. Erwerb → Kz 89/91/93/94); verhindert Doppelzählung gegenüber kontobasierter Berechnung
|
||||
- `fetchBookingsForKz`: UNION-Abfrage aus Konto-Kz und steuerart für korrekten Drill-down
|
||||
- Tests 4, 5, 6, 9: Kontonummern auf tatsächliche SKR03-Nummern korrigiert (8000→8400, 1400→1200)
|
||||
|
||||
---
|
||||
|
||||
## 2026-04-10 – Upload-Limit auf 20 MB erhöht
|
||||
|
||||
### Geändert
|
||||
|
|
|
|||
17
migrations/022_vorsteuer_kz_split.sql
Normal file
17
migrations/022_vorsteuer_kz_split.sql
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
-- Migration 022: Vorsteuer-Konten ustva_kz von '66' auf '61' (VSt19) bzw. '62' (VSt7) aufteilen.
|
||||
--
|
||||
-- Hintergrund: Der UstvaCalculator berechnet Kz 66 (Vorsteuer gesamt) als Summe
|
||||
-- aus Kz 61 + Kz 62 + Kz 67. Alle Vorsteuer-Konten hatten bisher ustva_kz='66',
|
||||
-- was dazu führte, dass Kz 61, 62 und damit auch 66 stets 0 ergaben.
|
||||
-- Nach dieser Migration tragen Konten mit VSt19-Steuercode zu Kz 61 bei,
|
||||
-- Konten mit VSt7 zu Kz 62. Kz 66 wird weiterhin rechnerisch aggregiert.
|
||||
|
||||
UPDATE accounts
|
||||
SET ustva_kz = '61'
|
||||
WHERE ustva_kz = '66'
|
||||
AND steuercode = 'VSt19';
|
||||
|
||||
UPDATE accounts
|
||||
SET ustva_kz = '62'
|
||||
WHERE ustva_kz = '66'
|
||||
AND steuercode = 'VSt7';
|
||||
|
|
@ -9,28 +9,38 @@ use Steuerkram\Database;
|
|||
/**
|
||||
* Berechnet alle ELSTER-Kennzahlen (Kz) für die Umsatzsteuervoranmeldung.
|
||||
*
|
||||
* Maßgeblich ist ausschließlich das Feld accounts.ustva_kz des Kategorie-Kontos.
|
||||
* Zwei Quellen fließen in die Berechnung ein:
|
||||
*
|
||||
* 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 %]
|
||||
* 1. Kontobasiert (accounts.ustva_kz des Kategorie-Kontos):
|
||||
* 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) [steuerfreie ig. Lieferungen via Konto]
|
||||
* ustva_kz = '44' → Kz 44 (betrag_netto) [steuerfreie Ausfuhren via Konto]
|
||||
* ustva_kz = '61' → Kz 61 (betrag_ust) [Vorsteuer 19 %, VSt19-Konten]
|
||||
* ustva_kz = '62' → Kz 62 (betrag_ust) [Vorsteuer 7 %, VSt7-Konten]
|
||||
*
|
||||
* 2. Steuerart-basiert (bookings.steuerart, unabhängig vom Konto):
|
||||
* steuerart = 'ig_lieferung' → Kz 41 (betrag_netto)
|
||||
* steuerart = 'export' → Kz 44 (betrag_netto)
|
||||
* steuerart = 'ig_erwerb_19' → Kz 89 (betrag_netto), Kz 91 (netto × 0,19)
|
||||
* steuerart = 'ig_erwerb_7' → Kz 93 (betrag_netto), Kz 94 (netto × 0,07)
|
||||
* Buchungen mit diesen steuerart-Werten bleiben aus der kontobasierten Berechnung
|
||||
* ausgeschlossen, um Doppelzählung zu vermeiden.
|
||||
*
|
||||
* 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)
|
||||
* 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
|
||||
{
|
||||
/** Steuerart-Werte die AUSSCHLIESSLICH steuerart-basiert berechnet werden. */
|
||||
private const EU_STEUERARTEN = ['ig_lieferung', 'export', 'ig_erwerb_19', 'ig_erwerb_7'];
|
||||
|
||||
/**
|
||||
* Welche accounts.ustva_kz-Werte liefern Buchungen für die Drill-down-Ansicht einer Kennzahl.
|
||||
* Welche accounts.ustva_kz-Werte liefern Buchungen für die Drill-down-Ansicht.
|
||||
* Nur für den kontobasierten Teil; EU-Steuerarten kommen aus KZ_STEUERART_MAP.
|
||||
*/
|
||||
private const KZ_ACCOUNT_MAP = [
|
||||
'81' => ['81'],
|
||||
|
|
@ -42,18 +52,28 @@ class UstvaCalculator
|
|||
'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'],
|
||||
];
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
$rows = Database::fetchAll(
|
||||
// ── 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
|
||||
|
|
@ -62,28 +82,49 @@ class UstvaCalculator
|
|||
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',
|
||||
[$year, $month]
|
||||
array_merge([$year, $month], self::EU_STEUERARTEN)
|
||||
);
|
||||
|
||||
$byKz = [];
|
||||
foreach ($rows as $row) {
|
||||
foreach ($accountRows as $row) {
|
||||
$byKz[$row['ustva_kz']] = [
|
||||
'netto' => (float)$row['summe_netto'],
|
||||
'ust' => (float)$row['summe_ust'],
|
||||
];
|
||||
}
|
||||
|
||||
$n = fn(string $kz): float => (float)($byKz[$kz]['netto'] ?? 0.0);
|
||||
$u = fn(string $kz): float => (float)($byKz[$kz]['ust'] ?? 0.0);
|
||||
// ── 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');
|
||||
$kz44 = $n('44');
|
||||
$kz89 = $n('89'); $kz91 = $u('89');
|
||||
$kz93 = $n('93'); $kz94 = $u('93');
|
||||
$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;
|
||||
|
|
@ -91,50 +132,74 @@ class UstvaCalculator
|
|||
$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)],
|
||||
'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] ?? null;
|
||||
if ($accountKzList === null) {
|
||||
$accountKzList = self::KZ_ACCOUNT_MAP[$kz] ?? [];
|
||||
$steuerartList = self::KZ_STEUERART_MAP[$kz] ?? [];
|
||||
|
||||
if (empty($accountKzList) && empty($steuerartList)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$placeholders = implode(',', array_fill(0, count($accountKzList), '?'));
|
||||
$params = array_merge([$year, $month], $accountKzList);
|
||||
$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) = ?';
|
||||
|
||||
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.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) = ?
|
||||
AND k.ustva_kz IN (' . $placeholders . ')
|
||||
ORDER BY bk.buchungs_datum, bk.id',
|
||||
$params
|
||||
);
|
||||
$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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,19 +16,19 @@ class InvoiceBuchungTest extends TestCase
|
|||
public function run(): void
|
||||
{
|
||||
// Benötigte Konten prüfen (SKR03)
|
||||
$zahlungsId = $this->findAccountId('1400'); // Forderungen (Zahlungskonto)
|
||||
$kategorieId = $this->findAccountId('8000'); // Erlöse 19 % (Betriebseinnahme)
|
||||
$zahlungsId = $this->findAccountId('1200'); // Bank (Zahlungskonto)
|
||||
$kategorieId = $this->findAccountId('8400'); // Erlöse 19 % USt (Betriebseinnahme)
|
||||
|
||||
if (!$zahlungsId) {
|
||||
$this->assert(false, 'Forderungskonto 1400 nicht gefunden – SKR03-Seed vorhanden?');
|
||||
$this->assert(false, 'Bankkonto 1200 nicht gefunden – SKR03-Seed vorhanden?');
|
||||
return;
|
||||
}
|
||||
if (!$kategorieId) {
|
||||
$this->assert(false, 'Erlöskonto 8000 nicht gefunden – SKR03-Seed vorhanden?');
|
||||
$this->assert(false, 'Erlöskonto 8400 nicht gefunden – SKR03-Seed vorhanden?');
|
||||
return;
|
||||
}
|
||||
|
||||
$this->assert(true, 'Benötigte Konten (1400, 8000) vorhanden');
|
||||
$this->assert(true, 'Benötigte Konten (1200, 8400) vorhanden');
|
||||
|
||||
// Testkunden anlegen
|
||||
$customerId = \Steuerkram\Models\Customer::create([
|
||||
|
|
|
|||
|
|
@ -19,15 +19,15 @@ class TaxSummenTest extends TestCase
|
|||
$datum = date('Y-m-d');
|
||||
|
||||
// Benötigte Konten (SKR03)
|
||||
$zahlungsId = $this->findAccountId('1400'); // Forderungen (Zahlungskonto)
|
||||
$erloesId = $this->findAccountId('8000'); // Erlöse 19 % (Betriebseinnahme)
|
||||
$zahlungsId = $this->findAccountId('1200'); // Bank (Zahlungskonto)
|
||||
$erloesId = $this->findAccountId('8400'); // Erlöse 19 % USt (Betriebseinnahme, kz=81)
|
||||
$ausgabeId = $this->findAccountId('4920'); // Bürobedarf (Betriebsausgabe)
|
||||
$bankId = $this->findAccountId('1200'); // Bank (Zahlungskonto)
|
||||
|
||||
// Fallback auf beliebige Konten der passenden Art
|
||||
// Fallback: explizit ein Konto mit ustva_kz=81 suchen, damit kz81/83 befüllt werden
|
||||
if (!$erloesId) {
|
||||
$row = \Steuerkram\Database::fetchOne(
|
||||
"SELECT id FROM accounts WHERE kontenart = 'Betriebseinnahme' AND aktiv = 1 LIMIT 1"
|
||||
"SELECT id FROM accounts WHERE kontenart = 'Betriebseinnahme' AND ustva_kz = '81' AND aktiv = 1 LIMIT 1"
|
||||
);
|
||||
$erloesId = $row ? (int)$row['id'] : null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,12 +42,12 @@ class EuerBuchungsTest extends TestCase
|
|||
);
|
||||
|
||||
// Konten laden
|
||||
$erloesId = $this->findAccountId('8000'); // Betriebseinnahme
|
||||
$ausgabeId = $this->findAccountId('4600'); // Bürobedarf (Betriebsausgabe)
|
||||
$erloesId = $this->findAccountId('8400'); // Erlöse 19 % USt (Betriebseinnahme)
|
||||
$ausgabeId = $this->findAccountId('4600'); // Werbekosten (Betriebsausgabe)
|
||||
$bankId = $this->findAccountId('1200'); // Bank (Zahlungskonto)
|
||||
|
||||
if (!$erloesId || !$ausgabeId || !$bankId) {
|
||||
$this->assert(false, 'Benötigte Konten (8000, 4600, 1200) nicht gefunden');
|
||||
$this->assert(false, 'Benötigte Konten (8400, 4600, 1200) nicht gefunden');
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,14 +20,14 @@ class InvoiceToReportsTest extends TestCase
|
|||
$datum = date('Y-m-d');
|
||||
|
||||
// ── Konten prüfen ────────────────────────────────────────────────────
|
||||
$zahlungsId = $this->findAccountId('1400'); // Forderungen
|
||||
$erloes19Id = $this->findAccountId('8000'); // Erlöse 19 %
|
||||
$zahlungsId = $this->findAccountId('1200'); // Bank
|
||||
$erloes19Id = $this->findAccountId('8400'); // Erlöse 19 % USt
|
||||
|
||||
if (!$zahlungsId || !$erloes19Id) {
|
||||
$this->assert(false, 'Benötigte Konten (1400, 8000) nicht gefunden – SKR03-Seed vorhanden?');
|
||||
$this->assert(false, 'Benötigte Konten (1200, 8400) nicht gefunden – SKR03-Seed vorhanden?');
|
||||
return;
|
||||
}
|
||||
$this->assert(true, 'Konten 1400 und 8000 vorhanden');
|
||||
$this->assert(true, 'Konten 1200 und 8400 vorhanden');
|
||||
|
||||
// ── Kunde und Rechnung anlegen ────────────────────────────────────────
|
||||
$customerId = \Steuerkram\Models\Customer::create([
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue