Feature: USt-Buchungszeilen für Ausgangsrechnungen einführen
- Migration 046: ustva_kz='83'/'84' für Konten 1776/1771, Bestandsmigration für Einnahme-Buchungen - Booking::create(): USt-Kindzeile auf 1771/1776 für Einnahmen mit 7/19 % (analog VSt) - UstvaCalculator: Kz 83/84 kontobasiert via 1771/1776, KZ_ACCOUNT_MAP aktualisiert - Invoice::cancel(): nur Hauptbuchungen direkt stornieren (parent_booking_id IS NULL) - Tests 10, 27 angepasst; Test 29 neu
This commit is contained in:
parent
d3a6c379c9
commit
55c02b54de
9 changed files with 251 additions and 29 deletions
10
Changes.md
10
Changes.md
|
|
@ -2,6 +2,16 @@
|
|||
|
||||
---
|
||||
|
||||
## 2026-05-10 – Feature: USt-Buchungszeilen für Ausgangsrechnungen
|
||||
|
||||
- Migration 046: Konten 1771 (USt 7 %) und 1776 (USt 19 %) erhalten `ustva_kz='84'`/'83'`; bestehende Einnahme-Buchungen mit USt erhalten nachträglich USt-Kindzeilen
|
||||
- `Booking::create()`: Bei Einnahme-Buchungen mit 7/19 % und `steuerart=normal` wird analog zur VSt-Kindzeile eine USt-Zeile auf 1771/1776 angelegt
|
||||
- `UstvaCalculator`: Kz 83/84 jetzt kontobasiert via 1771/1776 (`betrag_netto`), nicht mehr via `betrag_ust` der Erlöskonten; `KZ_ACCOUNT_MAP` aktualisiert
|
||||
- `Invoice::cancel()`: Nur Hauptbuchungen (`parent_booking_id IS NULL`) werden direkt storniert; Kindzeilen werden von `Booking::reverse()` mitaufgehoben (verhindert Doppelstorno)
|
||||
- Tests 10 und 27 an geänderte Buchungsstruktur angepasst; Test 29 neu
|
||||
|
||||
---
|
||||
|
||||
## 2026-05-05 – Feature: Stornorechnung (§ 14 UStG)
|
||||
|
||||
### Hinzugefügt
|
||||
|
|
|
|||
|
|
@ -55,8 +55,8 @@ Diese Berechnung wird ausschließlich für **monatliche** Voranmeldungen unterst
|
|||
**Quelle 1 – kontobasiert** (`accounts.ustva_kz` des Kategorie-Kontos)
|
||||
SKR03-Erlös- und Steuerkonten tragen ihre Kennzahl direkt am Konto. Buchungen mit gefülltem `bookings.steuerart` werden aus dieser Quelle ausgeschlossen, um Doppelzählung mit Quelle 3 zu vermeiden. Konten mit `ustva_kz='61'` oder `'62'` werden **nicht mehr** kontobasiert ausgewertet (siehe Quelle 2).
|
||||
|
||||
**Quelle 2 – buchungsbasiert** (`bookings.ust_satz`, nur Vorsteuer)
|
||||
Jede Buchung mit `buchungstyp='Ausgabe'` und `betrag_ust ≠ 0` fließt anhand ihres tatsächlichen USt-Satzes in Kz 61 (19 %) bzw. Kz 62 (7 %) ein. Damit ist es egal, ob das Konto auf USt 19 % oder 7 % gebucht wird – maßgeblich ist allein der Satz auf der Eingangsrechnung. EU-Buchungen (`steuerart` ∈ {ig_lieferung, export, ig_erwerb_19, ig_erwerb_7}) werden hier ausgeschlossen, da deren Vorsteuer über Kz 67 läuft.
|
||||
**Quelle 2 – Steuerkonten-Buchungszeilen** (Kz 61/62 via 1571/1576, Kz 83/84 via 1771/1776)
|
||||
Jede Ausgabe-Buchung mit 7/19 % und `steuerart=normal` erzeugt automatisch eine VSt-Kindzeile auf Konto 1571 (7 %) bzw. 1576 (19 %); jede Einnahme-Buchung entsprechend eine USt-Kindzeile auf 1771 (7 %) bzw. 1776 (19 %). Kz 61/62 und Kz 83/84 werden aus `betrag_netto` dieser Kindzeilen aggregiert. Maßgeblich ist der tatsächliche USt-Satz der Buchung, nicht das Erlöskonto. EU-Buchungen (`steuerart` ∈ {ig_lieferung, export, ig_erwerb_19, ig_erwerb_7}) erhalten keine Steuerkonten-Kindzeilen.
|
||||
|
||||
**Quelle 3 – steuerartbasiert** (`bookings.steuerart`)
|
||||
Auslandssachverhalte unabhängig vom gebuchten Konto:
|
||||
|
|
@ -70,9 +70,9 @@ Auslandssachverhalte unabhängig vom gebuchten Konto:
|
|||
| Kz | Bezeichnung (ELSTER) | Materiell-rechtliche Grundlage | Quelle | Berechnung |
|
||||
|---|---|---|---|---|
|
||||
| **81** | Steuerpflichtige Umsätze 19 % – Bemessungsgrundlage | § 12 Abs. 1 UStG | Konto | `SUM(betrag_netto)` WHERE `ustva_kz='81'` |
|
||||
| **83** | USt auf steuerpflichtige Umsätze 19 % | § 12 Abs. 1 UStG | Konto | `SUM(betrag_ust)` WHERE `ustva_kz='81'` |
|
||||
| **83** | USt auf steuerpflichtige Umsätze 19 % | § 12 Abs. 1 UStG | Steuerkonto | `SUM(betrag_netto)` WHERE `ustva_kz='83'` (Konto 1776) |
|
||||
| **86** | Steuerpflichtige Umsätze 7 % – Bemessungsgrundlage | § 12 Abs. 2 UStG | Konto | `SUM(betrag_netto)` WHERE `ustva_kz='86'` |
|
||||
| **84** | USt auf steuerpflichtige Umsätze 7 % | § 12 Abs. 2 UStG | Konto | `SUM(betrag_ust)` WHERE `ustva_kz='86'` |
|
||||
| **84** | USt auf steuerpflichtige Umsätze 7 % | § 12 Abs. 2 UStG | Steuerkonto | `SUM(betrag_netto)` WHERE `ustva_kz='84'` (Konto 1771) |
|
||||
| **35** | Umsätze zu anderen Steuersätzen | Restkategorie für nicht-Standard-Sätze | Konto | `SUM(betrag_netto)` WHERE `ustva_kz='35'` |
|
||||
| **41** | Steuerfreie ig. Lieferungen mit USt-IdNr. | § 4 Nr. 1b i.V.m. § 6a UStG | Konto + Steuerart | `ustva_kz='41'` ODER `steuerart='ig_lieferung'` |
|
||||
| **44** | Steuerfreie Ausfuhrlieferungen Drittland | § 4 Nr. 1a i.V.m. § 6 UStG | Konto + Steuerart | `ustva_kz='44'` ODER `steuerart='export'` |
|
||||
|
|
@ -80,8 +80,8 @@ Auslandssachverhalte unabhängig vom gebuchten Konto:
|
|||
| **91** | USt auf ig. Erwerb 19 % | § 1a UStG | berechnet | `Kz 89 × 0,19` |
|
||||
| **93** | Ig. Erwerb 7 % – Bemessungsgrundlage | § 1a UStG, § 12 Abs. 2 UStG | Steuerart | `SUM(betrag_netto)` WHERE `steuerart='ig_erwerb_7'` |
|
||||
| **94** | USt auf ig. Erwerb 7 % | § 1a UStG | berechnet | `Kz 93 × 0,07` |
|
||||
| **61** | Vorsteuerbeträge aus Eingangsrechnungen 19 % | § 15 Abs. 1 Nr. 1 UStG | Buchung | `SUM(betrag_ust)` WHERE `buchungstyp='Ausgabe'` AND `ROUND(ust_satz)=19` |
|
||||
| **62** | Vorsteuerbeträge aus Eingangsrechnungen 7 % | § 15 Abs. 1 Nr. 1 UStG | Buchung | `SUM(betrag_ust)` WHERE `buchungstyp='Ausgabe'` AND `ROUND(ust_satz)=7` |
|
||||
| **61** | Vorsteuerbeträge aus Eingangsrechnungen 19 % | § 15 Abs. 1 Nr. 1 UStG | Steuerkonto | `SUM(betrag_netto)` WHERE `ustva_kz='61'` (Konto 1576) |
|
||||
| **62** | Vorsteuerbeträge aus Eingangsrechnungen 7 % | § 15 Abs. 1 Nr. 1 UStG | Steuerkonto | `SUM(betrag_netto)` WHERE `ustva_kz='62'` (Konto 1571) |
|
||||
| **67** | Vorsteuer aus innergemeinschaftlichem Erwerb | § 15 Abs. 1 Nr. 3 UStG | abgeleitet | `Kz 91 + Kz 94` |
|
||||
| **66** | Vorsteuerbeträge gesamt | § 15 UStG | abgeleitet | `Kz 61 + Kz 62 + Kz 67` |
|
||||
| **69** | Verbleibende USt-Vorauszahlung / Erstattung | § 18 Abs. 1 UStG | abgeleitet | `(Kz 83 + Kz 84 + Kz 91 + Kz 94) − Kz 66` |
|
||||
|
|
@ -99,8 +99,10 @@ Die folgenden Konten tragen heute eine UStVA-Kennzahl (Stand Migration 026):
|
|||
| **35** | (derzeit kein Konto) | Restkategorie |
|
||||
| **41** | 8125 (falls vorhanden) | Steuerfreie ig. Lieferung |
|
||||
| **44** | 8120 (falls vorhanden) | Ausfuhren |
|
||||
| **61** | alle VSt19-Konten (3106, 3400, 4230, 4240, 4250, 4530–4590, 4600–4636, 4650, 4654, 4666, 4676, 4780, 4800–4809, 4900, 4920, 4925, 4930, 4945, 4950, 4955, 4960–4985, …) | wird vom Calculator **ignoriert**, Wert kommt aus `bookings.ust_satz` |
|
||||
| **62** | alle VSt7-Konten (3108, 3300, 4651, 4656, 4940, …) | wird vom Calculator **ignoriert**, Wert kommt aus `bookings.ust_satz` |
|
||||
| **61** | 1576 (Abziehbare Vorsteuer 19 %) | VSt-Buchungszeilen; Betriebsausgabe-Konten tragen diese Kennzahl **nicht** mehr |
|
||||
| **62** | 1571 (Abziehbare Vorsteuer 7 %) | VSt-Buchungszeilen; Betriebsausgabe-Konten tragen diese Kennzahl **nicht** mehr |
|
||||
| **83** | 1776 (Umsatzsteuer 19 %) | USt-Buchungszeilen für Ausgangsrechnungen |
|
||||
| **84** | 1771 (Umsatzsteuer 7 %) | USt-Buchungszeilen für Ausgangsrechnungen |
|
||||
|
||||
Eingangsumsätze ohne Vorsteuer (Konten 8100, 8105, 8110, 8120) sollten gemäß Migration 017 die Kennzahlen 48 (steuerfrei ohne VoSt-Abzug, § 4 Nr. 8–29) bzw. 43 (Ausfuhr Drittland) tragen. **Diese Kennzahlen werden vom `UstvaCalculator` aktuell nicht berechnet** – sie würden also weder im Bericht noch im XML auftauchen, selbst wenn ein Konto so gepflegt ist. Siehe „Offene Punkte".
|
||||
|
||||
|
|
|
|||
54
migrations/046_ust_buchungszeilen.sql
Normal file
54
migrations/046_ust_buchungszeilen.sql
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
-- Migration 046: USt-Buchungszeilen für Ausgangsrechnungen einführen
|
||||
--
|
||||
-- Hintergrund: Bisher wurde Umsatzsteuer für Einnahmen implizit über betrag_ust
|
||||
-- der Hauptbuchung und ustva_kz des Kategorie-Kontos (81/86) ermittelt.
|
||||
-- Ab jetzt erhält jede Einnahme-Buchung mit 7 % oder 19 % USt eine explizite
|
||||
-- USt-Buchungszeile auf Konto 1771 (7 %) bzw. 1776 (19 %).
|
||||
-- Kz 83/84 werden danach kontobasiert via 1771/1776 (betrag_netto) befüllt.
|
||||
|
||||
-- ── 1. ustva_kz für 1771 und 1776 setzen ─────────────────────────────────────
|
||||
|
||||
UPDATE accounts SET ustva_kz = '84' WHERE kontonummer = '1771';
|
||||
UPDATE accounts SET ustva_kz = '83' WHERE kontonummer = '1776';
|
||||
|
||||
-- ── 2. USt-Buchungszeilen für Bestandsbuchungen ───────────────────────────────
|
||||
-- Kriterien: buchungstyp=Einnahme, ust_satz IN (7,19), steuerart=normal, betrag_ust!=0
|
||||
-- Idempotenz: WHERE NOT EXISTS verhindert Doppel-Einfügung
|
||||
|
||||
INSERT INTO bookings (
|
||||
buchungs_nr, beleg_id, parent_booking_id, buchungs_datum, buchungstyp,
|
||||
kategorie_konto_id, zahlungskonto_id,
|
||||
betrag_netto, ust_satz, steuerart, betrag_ust, betrag_brutto,
|
||||
buchungstext, is_reversal, fiscal_year_id
|
||||
)
|
||||
SELECT
|
||||
CONCAT('UST-M-', LPAD(b.id, 8, '0')),
|
||||
b.beleg_id,
|
||||
b.id,
|
||||
b.buchungs_datum,
|
||||
'Einnahme',
|
||||
a_ust.id,
|
||||
b.zahlungskonto_id,
|
||||
b.betrag_ust,
|
||||
0.00,
|
||||
'normal',
|
||||
0.00,
|
||||
b.betrag_ust,
|
||||
CONCAT('USt zu ', b.buchungs_nr),
|
||||
b.is_reversal,
|
||||
b.fiscal_year_id
|
||||
FROM bookings b
|
||||
JOIN accounts a_ust ON (
|
||||
(ROUND(b.ust_satz) = 19 AND a_ust.kontonummer = '1776')
|
||||
OR
|
||||
(ROUND(b.ust_satz) = 7 AND a_ust.kontonummer = '1771')
|
||||
)
|
||||
WHERE b.buchungstyp = 'Einnahme'
|
||||
AND b.betrag_ust != 0
|
||||
AND b.steuerart = 'normal'
|
||||
AND ROUND(b.ust_satz) IN (7, 19)
|
||||
AND b.parent_booking_id IS NULL
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM bookings ust
|
||||
WHERE ust.parent_booking_id = b.id
|
||||
);
|
||||
|
|
@ -133,17 +133,24 @@ class Booking
|
|||
|
||||
$mainId = (int)Database::lastInsertId();
|
||||
|
||||
// VSt-Buchungszeile für normale Ausgaben mit 7 % oder 19 % anlegen
|
||||
// VSt-/USt-Buchungszeile für normale Buchungen mit 7 % oder 19 % anlegen
|
||||
$ustSatzInt = (int)round((float)($data['ust_satz'] ?? 0));
|
||||
if ($data['buchungstyp'] === 'Ausgabe'
|
||||
if (in_array($data['buchungstyp'], ['Ausgabe', 'Einnahme'], true)
|
||||
&& in_array($ustSatzInt, [7, 19], true)
|
||||
&& $steuerart === 'normal'
|
||||
&& abs($ust) > 0.0
|
||||
) {
|
||||
$vstKontonummer = $ustSatzInt === 19 ? '1576' : '1571';
|
||||
$vstAcc = Account::findByKontonummer($vstKontonummer);
|
||||
if ($vstAcc) {
|
||||
$vstNr = self::generateBuchungsNr();
|
||||
$isAusgabe = $data['buchungstyp'] === 'Ausgabe';
|
||||
if ($isAusgabe) {
|
||||
$steuerKontonummer = $ustSatzInt === 19 ? '1576' : '1571';
|
||||
$steuerText = 'Vorsteuer zu ' . $nr;
|
||||
} else {
|
||||
$steuerKontonummer = $ustSatzInt === 19 ? '1776' : '1771';
|
||||
$steuerText = 'USt zu ' . $nr;
|
||||
}
|
||||
$steuerAcc = Account::findByKontonummer($steuerKontonummer);
|
||||
if ($steuerAcc) {
|
||||
$steuerNr = self::generateBuchungsNr();
|
||||
Database::execute(
|
||||
'INSERT INTO bookings (buchungs_nr, beleg_id, parent_booking_id, buchungs_datum, buchungstyp,
|
||||
kategorie_konto_id, zahlungskonto_id,
|
||||
|
|
@ -151,19 +158,19 @@ class Booking
|
|||
buchungstext, fiscal_year_id)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
|
||||
[
|
||||
$vstNr,
|
||||
$steuerNr,
|
||||
(int)$data['beleg_id'],
|
||||
$mainId,
|
||||
$data['buchungs_datum'],
|
||||
'Ausgabe',
|
||||
(int)$vstAcc['id'],
|
||||
$data['buchungstyp'],
|
||||
(int)$steuerAcc['id'],
|
||||
(int)$data['zahlungskonto_id'],
|
||||
$ust,
|
||||
0.0,
|
||||
'normal',
|
||||
0.0,
|
||||
$ust,
|
||||
'Vorsteuer zu ' . $nr,
|
||||
$steuerText,
|
||||
$fy['id'],
|
||||
]
|
||||
);
|
||||
|
|
|
|||
|
|
@ -203,7 +203,7 @@ class Invoice
|
|||
// Stornobuchungen für alle gebuchten Buchungen des Belegs erzeugen
|
||||
if (!empty($invoice['beleg_id'])) {
|
||||
$bookings = Database::fetchAll(
|
||||
'SELECT id FROM bookings WHERE beleg_id = ? AND is_reversal = 0 AND reversed_by IS NULL',
|
||||
'SELECT id FROM bookings WHERE beleg_id = ? AND is_reversal = 0 AND reversed_by IS NULL AND parent_booking_id IS NULL',
|
||||
[(int)$invoice['beleg_id']]
|
||||
);
|
||||
foreach ($bookings as $bk) {
|
||||
|
|
|
|||
|
|
@ -12,8 +12,10 @@ use Steuerkram\Database;
|
|||
* Zwei Quellen fließen in die Berechnung ein:
|
||||
*
|
||||
* 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 = '81' → Kz 81 (betrag_netto)
|
||||
* ustva_kz = '83' → Kz 83 (betrag_netto) [USt 19 % via Konto 1776]
|
||||
* ustva_kz = '86' → Kz 86 (betrag_netto)
|
||||
* ustva_kz = '84' → Kz 84 (betrag_netto) [USt 7 % via Konto 1771]
|
||||
* 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]
|
||||
|
|
@ -44,9 +46,9 @@ class UstvaCalculator
|
|||
*/
|
||||
private const KZ_ACCOUNT_MAP = [
|
||||
'81' => ['81'],
|
||||
'83' => ['81'],
|
||||
'83' => ['83'],
|
||||
'86' => ['86'],
|
||||
'84' => ['86'],
|
||||
'84' => ['84'],
|
||||
'35' => ['35'],
|
||||
'41' => ['41'],
|
||||
'44' => ['44'],
|
||||
|
|
@ -117,8 +119,9 @@ class UstvaCalculator
|
|||
$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');
|
||||
// Kz 83/84 kontobasiert: betrag_netto der USt-Buchungszeilen (1776/1771)
|
||||
$kz81 = $n('81'); $kz83 = $n('83');
|
||||
$kz86 = $n('86'); $kz84 = $n('84');
|
||||
$kz35 = $n('35');
|
||||
$kz41 = $n('41') + $st('ig_lieferung');
|
||||
$kz44 = $n('44') + $st('export');
|
||||
|
|
|
|||
|
|
@ -100,8 +100,11 @@ class IstVersteuerungTest extends TestCase
|
|||
$this->assertGreaterThan(0, $buchungId, 'Buchung mit Zahldatum erfasst');
|
||||
|
||||
// Buchung hat das Zahldatum – nicht das Rechnungsdatum
|
||||
$buchungenNachZahlung = \Steuerkram\Models\Booking::forBeleg($belegId);
|
||||
$this->assertEquals(1, count($buchungenNachZahlung), 'Genau eine Buchung vorhanden');
|
||||
$buchungenNachZahlung = array_values(array_filter(
|
||||
\Steuerkram\Models\Booking::forBeleg($belegId),
|
||||
fn($b) => $b['parent_booking_id'] === null
|
||||
));
|
||||
$this->assertEquals(1, count($buchungenNachZahlung), 'Genau eine Hauptbuchung vorhanden');
|
||||
|
||||
$bk = $buchungenNachZahlung[0];
|
||||
$this->assertEquals($zahlDatum, $bk['buchungs_datum'], 'Buchungsdatum ist das Zahldatum');
|
||||
|
|
|
|||
|
|
@ -146,10 +146,10 @@ class StornorechnungTest extends TestCase
|
|||
|
||||
// ── 5. Stornobuchung muss am Originalbeleg hängen ─────────────
|
||||
$reversalRows = \Steuerkram\Database::fetchAll(
|
||||
'SELECT * FROM bookings WHERE beleg_id = ? AND is_reversal = 1',
|
||||
'SELECT * FROM bookings WHERE beleg_id = ? AND is_reversal = 1 AND parent_booking_id IS NULL',
|
||||
[$belegId]
|
||||
);
|
||||
$this->assertEquals(1, count($reversalRows), 'Genau eine Stornobuchung am Originalbeleg');
|
||||
$this->assertEquals(1, count($reversalRows), 'Genau eine Hauptstornobuchung am Originalbeleg');
|
||||
$reversal = $reversalRows[0];
|
||||
$this->assertEquals(-250.00, round((float)$reversal['betrag_netto'], 2), 'Stornobuchung Netto = -250');
|
||||
$this->assertEquals(-47.50, round((float)$reversal['betrag_ust'], 2), 'Stornobuchung USt = -47,50');
|
||||
|
|
|
|||
143
tests/29_UmsatzsteuerkontenTest.php
Normal file
143
tests/29_UmsatzsteuerkontenTest.php
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Test 29: USt-Buchungszeilen für Ausgangsrechnungen.
|
||||
*
|
||||
* Prüft, dass Einnahme-Buchungen mit USt 7 % / 19 % automatisch eine
|
||||
* USt-Buchungszeile auf 1771 / 1776 erhalten, die UStVA Kz 83/84 daraus
|
||||
* kontobasiert ableitet und ein Storno die USt-Zeilen mitaufhebt.
|
||||
*/
|
||||
class UmsatzsteuerkontenTest extends TestCase
|
||||
{
|
||||
protected string $testName = 'Test 29: USt-Buchungszeilen für Ausgangsrechnungen';
|
||||
|
||||
public function run(): void
|
||||
{
|
||||
$year = (int)date('Y');
|
||||
$month = (int)date('n');
|
||||
$datum = date('Y-m-d');
|
||||
|
||||
$zahlungsId = $this->findAccountId('1200');
|
||||
$einnahmeId = $this->findAccountId('8400');
|
||||
$konto1776 = \Steuerkram\Database::fetchOne(
|
||||
"SELECT id FROM accounts WHERE kontonummer = '1776' AND aktiv = 1"
|
||||
);
|
||||
$konto1771 = \Steuerkram\Database::fetchOne(
|
||||
"SELECT id FROM accounts WHERE kontonummer = '1771' AND aktiv = 1"
|
||||
);
|
||||
|
||||
if (!$zahlungsId || !$einnahmeId || !$konto1776 || !$konto1771) {
|
||||
$this->assert(false, 'Benötigte Konten nicht gefunden (1200, 8400, 1771, 1776)');
|
||||
return;
|
||||
}
|
||||
$this->assert(true, 'Konten 1200, 8400, 1771, 1776 vorhanden');
|
||||
|
||||
// ── UStVA-Stand vor den Testbuchungen ─────────────────────────
|
||||
$calc = new \Steuerkram\Services\UstvaCalculator();
|
||||
$kzPre = $calc->calculate($year, $month);
|
||||
|
||||
// ── Beleg anlegen ──────────────────────────────────────────────
|
||||
$belegId = \Steuerkram\Models\Beleg::create([
|
||||
'beleg_nr' => 'UST-TEST-' . uniqid(),
|
||||
'filename' => 'ust-test.pdf',
|
||||
'storage_path' => '',
|
||||
'mime_type' => 'application/pdf',
|
||||
'file_hash' => hash('sha256', 'ust-test-' . uniqid()),
|
||||
'beleg_datum' => $datum,
|
||||
'belegtyp' => 'Ausgangsrechnung',
|
||||
]);
|
||||
|
||||
// ── Einnahme-Buchung 19 % (Netto=500, USt=95) ─────────────────
|
||||
$bookingId19 = \Steuerkram\Models\Booking::create([
|
||||
'beleg_id' => $belegId,
|
||||
'buchungs_datum' => $datum,
|
||||
'buchungstyp' => 'Einnahme',
|
||||
'kategorie_konto_id' => $einnahmeId,
|
||||
'zahlungskonto_id' => $zahlungsId,
|
||||
'betrag_netto' => 500.00,
|
||||
'ust_satz' => 19.0,
|
||||
'buchungstext' => 'Test USt 19%',
|
||||
]);
|
||||
|
||||
// USt-Buchungszeile muss auf 1776 angelegt worden sein
|
||||
$ustRow19 = \Steuerkram\Database::fetchOne(
|
||||
'SELECT bk.*, a.kontonummer FROM bookings bk
|
||||
JOIN accounts a ON bk.kategorie_konto_id = a.id
|
||||
WHERE bk.parent_booking_id = ?',
|
||||
[$bookingId19]
|
||||
);
|
||||
$this->assert($ustRow19 !== false, 'USt-Buchungszeile für 19%-Buchung angelegt');
|
||||
$this->assertEquals('1776', $ustRow19['kontonummer'] ?? '', 'USt-Buchungszeile auf Konto 1776');
|
||||
$this->assertEquals(95.00, round((float)($ustRow19['betrag_netto'] ?? 0), 2), 'USt betrag_netto = 95');
|
||||
$this->assertEquals(0.00, round((float)($ustRow19['betrag_ust'] ?? 0), 2), 'USt betrag_ust = 0');
|
||||
|
||||
// ── Einnahme-Buchung 7 % (Netto=200, USt=14) ──────────────────
|
||||
$bookingId7 = \Steuerkram\Models\Booking::create([
|
||||
'beleg_id' => $belegId,
|
||||
'buchungs_datum' => $datum,
|
||||
'buchungstyp' => 'Einnahme',
|
||||
'kategorie_konto_id' => $einnahmeId,
|
||||
'zahlungskonto_id' => $zahlungsId,
|
||||
'betrag_netto' => 200.00,
|
||||
'ust_satz' => 7.0,
|
||||
'buchungstext' => 'Test USt 7%',
|
||||
]);
|
||||
|
||||
$ustRow7 = \Steuerkram\Database::fetchOne(
|
||||
'SELECT bk.*, a.kontonummer FROM bookings bk
|
||||
JOIN accounts a ON bk.kategorie_konto_id = a.id
|
||||
WHERE bk.parent_booking_id = ?',
|
||||
[$bookingId7]
|
||||
);
|
||||
$this->assert($ustRow7 !== false, 'USt-Buchungszeile für 7%-Buchung angelegt');
|
||||
$this->assertEquals('1771', $ustRow7['kontonummer'] ?? '', 'USt-Buchungszeile auf Konto 1771');
|
||||
$this->assertEquals(14.00, round((float)($ustRow7['betrag_netto'] ?? 0), 2), 'USt betrag_netto = 14');
|
||||
|
||||
// ── UStVA: Kz 83 und 84 aus Konten lesen ──────────────────────
|
||||
$kzMit = $calc->calculate($year, $month);
|
||||
|
||||
$this->assert(
|
||||
round((float)$kzMit['83']['steuer'] - (float)$kzPre['83']['steuer'], 2) >= 95.00,
|
||||
'UStVA Kz 83 stieg durch 19%-Buchung um ≥ 95 €'
|
||||
);
|
||||
$this->assert(
|
||||
round((float)$kzMit['84']['steuer'] - (float)$kzPre['84']['steuer'], 2) >= 14.00,
|
||||
'UStVA Kz 84 stieg durch 7%-Buchung um ≥ 14 €'
|
||||
);
|
||||
|
||||
// ── Drill-down Kz 83 enthält die USt-Buchungszeile ────────────
|
||||
$drilldown = $calc->fetchBookingsForKz($year, $month, '83');
|
||||
$ustFound = false;
|
||||
foreach ($drilldown as $b) {
|
||||
if ((int)$b['id'] === (int)$ustRow19['id']) {
|
||||
$ustFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$this->assert($ustFound, 'Drill-down Kz 83 enthält die USt-Buchungszeile');
|
||||
|
||||
// ── Storno: USt-Zeile muss mitaufgehoben werden ───────────────
|
||||
$reversalId = \Steuerkram\Models\Booking::reverse($bookingId19, 'Testfall USt-Storno');
|
||||
|
||||
$ustStornoRow = \Steuerkram\Database::fetchOne(
|
||||
'SELECT * FROM bookings WHERE parent_booking_id = ? AND is_reversal = 1',
|
||||
[$reversalId]
|
||||
);
|
||||
$this->assert($ustStornoRow !== false, 'USt-Stornobuchung wurde angelegt');
|
||||
$this->assertEquals(-95.00, round((float)($ustStornoRow['betrag_netto'] ?? 0), 2), 'USt-Storno betrag_netto = -95');
|
||||
|
||||
// Original-USt-Zeile muss als storniert markiert sein
|
||||
$ustOriginal = \Steuerkram\Database::fetchOne(
|
||||
'SELECT reversed_by FROM bookings WHERE id = ?',
|
||||
[(int)$ustRow19['id']]
|
||||
);
|
||||
$this->assert(!empty($ustOriginal['reversed_by']), 'Original-USt-Buchungszeile hat reversed_by gesetzt');
|
||||
|
||||
// UStVA Kz 83 nach Storno: netto Änderung = nur noch 7%-Buchung sichtbar, 19% aufgehoben
|
||||
$kzNach = $calc->calculate($year, $month);
|
||||
$delta83 = round((float)$kzNach['83']['steuer'] - (float)$kzPre['83']['steuer'], 2);
|
||||
$this->assertEquals(0.00, $delta83, 'Kz 83: Buchung+Storno heben sich gegenüber Ausgangslage auf');
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue