Files
Webklar-Kundenbereich/server/services/finance/tax.js
T

87 lines
4.0 KiB
JavaScript

/**
* Steuer-Auswertung der Finanzen (rein, ohne I/O): Rücklage auf den Gewinn des laufenden Jahres, Zähler für die
* Kleinunternehmer-Grenzen (§ 19 UStG) und die nächste Einkommensteuer-Vorauszahlung. Beträge sind ganze Cent;
* "heute" kommt als Berliner Kalendertag von außen.
*/
import { daysBetween } from './dates.js'
const GRENZE_FOLGEJAHR_CENTS = 2_500_000 // 25.000 EUR: wer im Jahr darüber liegt, ist im Folgejahr kein Kleinunternehmer mehr
const GRENZE_HART_CENTS = 10_000_000 // 100.000 EUR: darüber endet die Regelung sofort
const GEWERBESTEUER_FREIBETRAG_CENTS = 2_450_000 // 24.500 EUR Gewinn bei Personengesellschaften
const DEFAULT_RESERVE_PERCENT = 30 // wie DEFAULT_SETTINGS.taxReservePercent (settings.js, hier nicht importiert: Modul bleibt rein)
const EST_MONTHS = [3, 6, 9, 12] // ESt-Vorauszahlung jeweils am 10.
const EST_DAY = 10
const isCents = (n) => Number.isSafeInteger(n)
const pad = (n) => String(n).padStart(2, '0')
/** Nächster ESt-Vorauszahlungstermin ab heute (heute zählt mit) als YYYY-MM-DD. */
function nextEstDate(today) {
const year = Number(today.slice(0, 4))
for (const month of EST_MONTHS) {
const date = `${String(year).padStart(4, '0')}-${pad(month)}-${pad(EST_DAY)}`
if (date >= today) return date
}
return `${String(year + 1).padStart(4, '0')}-${pad(EST_MONTHS[0])}-${pad(EST_DAY)}`
}
/**
* @param {object} args
* @param {string} args.today Berliner Tag YYYY-MM-DD
* @param {Array} [args.transactions] Bankbuchungen { status, bookingDate, amountCents (+ Eingang / - Ausgang), category }
* @param {Array} [args.invoices] Stripe-Rechnungen (gemappt) { paidCents, paidDate }
* @param {object} [args.settings] { taxReservePercent, previousYearRevenueCents, estPrepaymentCents }
*
* Einnahmen und Ausgaben: gebuchte Bankbuchungen (BOOK) des Kalenderjahres von today ohne Kategorie "privat".
* Der Kleinunternehmer-Zähler nimmt statt der Bankeingänge die Zahltage der Stripe-Rechnungen plus Buchungen
* "rechnung-extern"; Stripe-Auszahlungen und Rechnungsbuchungen zählen dort nicht (sonst doppelt).
*/
export function computeTax({ today, transactions = [], invoices = [], settings } = {}) {
daysBetween(today, today) // wirft bei einem ungültigen Tag
const jahr = Number(today.slice(0, 4))
const prefix = `${today.slice(0, 4)}-`
let einnahmenCents = 0
let ausgabenCents = 0
let externCents = 0
for (const tx of transactions || []) {
if (!tx || tx.status !== 'BOOK' || typeof tx.bookingDate !== 'string' || !tx.bookingDate.startsWith(prefix)) continue
if (!isCents(tx.amountCents) || tx.category === 'privat') continue
if (tx.amountCents > 0) {
einnahmenCents += tx.amountCents
if (tx.category === 'rechnung-extern') externCents += tx.amountCents
} else {
ausgabenCents -= tx.amountCents
}
}
let stripeCents = 0
for (const invoice of invoices || []) {
if (!invoice || typeof invoice.paidDate !== 'string' || !invoice.paidDate.startsWith(prefix)) continue
if (isCents(invoice.paidCents) && invoice.paidCents > 0) stripeCents += invoice.paidCents
}
const gewinnCents = einnahmenCents - ausgabenCents
const prozent = Number.isFinite(settings?.taxReservePercent) ? settings.taxReservePercent : DEFAULT_RESERVE_PERCENT
const jahrCents = stripeCents + externCents
const prepayment = settings?.estPrepaymentCents
return {
jahr,
einnahmenCents,
ausgabenCents,
gewinnCents,
ruecklageCents: Math.round((Math.max(0, gewinnCents) * prozent) / 100),
prozent,
gewerbesteuerHinweis: gewinnCents > GEWERBESTEUER_FREIBETRAG_CENTS,
kleinunternehmer: {
jahrCents,
vorjahrCents: isCents(settings?.previousYearRevenueCents) ? settings.previousYearRevenueCents : null,
grenzeFolgejahrCents: GRENZE_FOLGEJAHR_CENTS,
grenzeHartCents: GRENZE_HART_CENTS,
anteilFolgejahr: jahrCents / GRENZE_FOLGEJAHR_CENTS,
},
naechsteVorauszahlung: isCents(prepayment) && prepayment > 0 ? { date: nextEstDate(today), amountCents: prepayment } : null,
}
}