import { test } from 'node:test' import assert from 'node:assert/strict' import { computeTax } from '../server/services/finance/tax.js' const TODAY = '2026-10-01' const tx = (extra = {}) => ({ $id: 't', status: 'BOOK', bookingDate: '2026-09-15', amountCents: -449, counterpartyName: 'Hetzner', remittance: '', category: 'fixkosten', ...extra }) const settings = (extra = {}) => ({ taxReservePercent: 30, previousYearRevenueCents: null, estPrepaymentCents: 0, ...extra }) const invoice = (extra = {}) => ({ id: 'in_1', number: 'WK-2026-0001', status: 'bezahlt', paidCents: 5000, paidDate: '2026-09-20', source: 'projekt', ...extra }) const run = (extra = {}) => computeTax({ today: TODAY, transactions: [], invoices: [], settings: settings(), ...extra }) // Beispiel aus dem Plan: 168,79 EUR Eingang, 59,86 EUR Ausgaben, 100 EUR privat const planTransactions = () => [ tx({ $id: 'in', amountCents: 16879, category: 'rechnung-extern', bookingDate: '2026-08-05' }), ...[449, 449, 449, 50, 50, 50, 131, 4358].map((cents, i) => tx({ $id: `out${i}`, amountCents: -cents })), tx({ $id: 'privat', amountCents: -10000, category: 'privat' }), ] test('Plan-Beispiel: Einnahmen, Ausgaben, Gewinn, Rücklage und Kleinunternehmer-Zähler', () => { const r = run({ transactions: planTransactions() }) assert.equal(r.jahr, 2026) assert.equal(r.einnahmenCents, 16879) assert.equal(r.ausgabenCents, 5986) assert.equal(r.gewinnCents, 10893) assert.equal(r.ruecklageCents, 3268) assert.equal(r.prozent, 30) assert.equal(r.kleinunternehmer.jahrCents, 16879) assert.equal(r.gewerbesteuerHinweis, false) assert.equal(r.naechsteVorauszahlung, null) }) test('Kleinunternehmer-Grenzen und Anteil am Folgejahr-Limit', () => { const r = run({ transactions: planTransactions() }) assert.equal(r.kleinunternehmer.grenzeFolgejahrCents, 2500000) assert.equal(r.kleinunternehmer.grenzeHartCents, 10000000) assert.equal(r.kleinunternehmer.anteilFolgejahr, 16879 / 2500000) assert.equal(r.kleinunternehmer.vorjahrCents, null) }) test('Vorjahresumsatz kommt aus den Einstellungen, ungültige Werte werden zu null', () => { assert.equal(run({ settings: settings({ previousYearRevenueCents: 1234500 }) }).kleinunternehmer.vorjahrCents, 1234500) assert.equal(run({ settings: settings({ previousYearRevenueCents: 0 }) }).kleinunternehmer.vorjahrCents, 0) assert.equal(run({ settings: settings({ previousYearRevenueCents: 'viel' }) }).kleinunternehmer.vorjahrCents, null) }) test('negativer Gewinn: Rücklage 0, Gewinn bleibt negativ', () => { const r = run({ transactions: [tx({ amountCents: 1000, category: 'rechnung-extern' }), tx({ amountCents: -5000 })] }) assert.equal(r.gewinnCents, -4000) assert.equal(r.ruecklageCents, 0) }) test('Rücklage wird kaufmännisch auf ganze Cent gerundet und folgt dem Prozentsatz der Einstellungen', () => { const rows = [tx({ amountCents: 10893, category: 'rechnung-extern' })] assert.equal(run({ transactions: rows, settings: settings({ taxReservePercent: 30 }) }).ruecklageCents, 3268) assert.equal(run({ transactions: rows, settings: settings({ taxReservePercent: 25 }) }).ruecklageCents, 2723) // 2723,25 assert.equal(run({ transactions: rows, settings: settings({ taxReservePercent: 0 }) }).ruecklageCents, 0) assert.equal(run({ transactions: rows, settings: settings({ taxReservePercent: 25 }) }).prozent, 25) }) test('ohne Einstellungen gelten 30 Prozent', () => { const rows = [tx({ amountCents: 10000, category: 'rechnung-extern' })] const r = computeTax({ today: TODAY, transactions: rows, invoices: [], settings: null }) assert.equal(r.prozent, 30) assert.equal(r.ruecklageCents, 3000) assert.equal(r.kleinunternehmer.vorjahrCents, null) }) test('nur gebuchte Buchungen des laufenden Kalenderjahres zählen', () => { const r = run({ transactions: [ tx({ amountCents: 1000, category: 'rechnung-extern', bookingDate: '2026-01-01' }), tx({ amountCents: 2000, category: 'rechnung-extern', bookingDate: '2025-12-31' }), tx({ amountCents: 4000, category: 'rechnung-extern', bookingDate: '2027-01-01' }), tx({ amountCents: 8000, category: 'rechnung-extern', status: 'PDNG' }), tx({ amountCents: -300, bookingDate: '2025-12-31' }), tx({ amountCents: -70, status: 'PDNG' }), tx({ amountCents: -20, bookingDate: '2026-12-31' }), ], }) assert.equal(r.einnahmenCents, 1000) assert.equal(r.ausgabenCents, 20) assert.equal(r.kleinunternehmer.jahrCents, 1000) }) test('Privat zählt weder bei Einnahmen noch bei Ausgaben, Rechnung/Auszahlung zählen als Einnahmen', () => { const r = run({ transactions: [ tx({ amountCents: 700, category: 'privat' }), tx({ amountCents: -900, category: 'privat' }), tx({ amountCents: 5000, category: 'rechnung' }), tx({ amountCents: 3000, category: 'stripe-auszahlung' }), tx({ amountCents: 1000, category: '' }), ], }) assert.equal(r.einnahmenCents, 9000) assert.equal(r.ausgabenCents, 0) }) test('Kleinunternehmer: Stripe-Rechnungen mit Zahltag im Jahr zählen dazu, Auszahlungen und Rechnungsbuchungen nicht', () => { const base = run({ transactions: planTransactions() }) const r = run({ transactions: [ ...planTransactions(), tx({ amountCents: 9999, category: 'stripe-auszahlung' }), // sonst doppelt tx({ amountCents: 7777, category: 'rechnung' }), // gehört zu einer Stripe-Rechnung ], invoices: [ invoice({ paidCents: 5000, paidDate: '2026-10-02' }), invoice({ id: 'in_2', paidCents: 300, paidDate: '2026-01-01' }), invoice({ id: 'in_3', paidCents: 4000, paidDate: '2025-12-31' }), invoice({ id: 'in_4', paidCents: 6000, paidDate: '2027-01-01' }), invoice({ id: 'in_5', paidCents: 0, paidDate: null, status: 'offen' }), ], }) assert.equal(base.kleinunternehmer.jahrCents, 16879) assert.equal(r.kleinunternehmer.jahrCents, 16879 + 5000 + 300) assert.equal(r.kleinunternehmer.anteilFolgejahr, (16879 + 5300) / 2500000) }) test('Gewerbesteuer-Hinweis erst bei Gewinn über 24.500 Euro', () => { const gain = (cents) => run({ transactions: [tx({ amountCents: cents, category: 'rechnung-extern' })] }) assert.equal(gain(2450000).gewerbesteuerHinweis, false) assert.equal(gain(2450001).gewerbesteuerHinweis, true) // Ausgaben mindern den Gewinn const r = run({ transactions: [tx({ amountCents: 2500000, category: 'rechnung-extern' }), tx({ amountCents: -100000 })] }) assert.equal(r.gewinnCents, 2400000) assert.equal(r.gewerbesteuerHinweis, false) }) test('nächste ESt-Vorauszahlung: nächster 10.03./10.06./10.09./10.12., heute zählt mit', () => { const next = (today, cents = 50000) => computeTax({ today, transactions: [], invoices: [], settings: settings({ estPrepaymentCents: cents }) }).naechsteVorauszahlung assert.deepEqual(next('2026-10-01'), { date: '2026-12-10', amountCents: 50000 }) assert.deepEqual(next('2026-01-15').date, '2026-03-10') assert.deepEqual(next('2026-03-10').date, '2026-03-10') assert.deepEqual(next('2026-03-11').date, '2026-06-10') assert.deepEqual(next('2026-06-11').date, '2026-09-10') assert.deepEqual(next('2026-09-11').date, '2026-12-10') assert.deepEqual(next('2026-12-10').date, '2026-12-10') assert.deepEqual(next('2026-12-11'), { date: '2027-03-10', amountCents: 50000 }) }) test('keine ESt-Vorauszahlung eingestellt: naechsteVorauszahlung ist null', () => { assert.equal(run({ settings: settings({ estPrepaymentCents: 0 }) }).naechsteVorauszahlung, null) assert.equal(run({ settings: settings({ estPrepaymentCents: null }) }).naechsteVorauszahlung, null) assert.equal(run({ settings: null }).naechsteVorauszahlung, null) }) test('Jahr folgt today (Jahreswechsel) und Berichtsjahr wird mitgegeben', () => { const rows = [tx({ amountCents: 1000, category: 'rechnung-extern', bookingDate: '2026-12-31' }), tx({ amountCents: 2000, category: 'rechnung-extern', bookingDate: '2027-01-02' })] const r = computeTax({ today: '2027-01-05', transactions: rows, invoices: [], settings: settings() }) assert.equal(r.jahr, 2027) assert.equal(r.einnahmenCents, 2000) }) test('leere und fehlende Listen, kaputte Datensätze: keine Ausnahme', () => { const empty = computeTax({ today: TODAY, settings: settings() }) assert.equal(empty.einnahmenCents, 0) assert.equal(empty.gewinnCents, 0) assert.equal(empty.ruecklageCents, 0) assert.equal(empty.kleinunternehmer.jahrCents, 0) const r = run({ transactions: [tx({ amountCents: 1.5 }), tx({ amountCents: 'x' }), tx({ bookingDate: undefined }), tx({ amountCents: 1000, category: 'rechnung-extern' }), null], invoices: [invoice({ paidCents: 1.5 }), invoice({ paidDate: 'gestern' }), invoice({ paidCents: -500 }), null], }) assert.equal(r.einnahmenCents, 1000) assert.equal(r.kleinunternehmer.jahrCents, 1000) }) test('Eingaben werden nicht verändert', () => { const transactions = planTransactions() const invoices = [invoice()] const copy = JSON.stringify({ transactions, invoices }) run({ transactions, invoices }) assert.equal(JSON.stringify({ transactions, invoices }), copy) })