Files
Webklar-Kundenbereich/test/financeTax.test.js
T

175 lines
8.9 KiB
JavaScript

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)
})