Gewinnermittlung nach §4 Abs. 3 EStG für Einzelunternehmen und Freiberufler. Node/Fastify mit SQLite im Backend, React/Vite im Frontend; die Anwendung läuft lokal ohne Cloud-Anbindung. Fachlicher Kern ist eine Zeitachse der Besteuerungsform: das umsatzsteuerliche Regime wird je Buchung aus dem Leistungsdatum aufgelöst, statt aus einer globalen Ja/Nein-Flagge. Dadurch ist auch ein unterjähriger Wechsel von der Kleinunternehmerregelung zur Regelbesteuerung abbildbar, wie ihn §19 Abs. 1 Satz 3 UStG seit 2025 beim Überschreiten der 100.000-EUR-Grenze vorsieht. Enthalten: - Buchungserfassung nach dem Zu-/Abflussprinzip mit Storno statt Löschung - Anlagevermögen mit AfA (linear, GWG, Sammelposten); die Aktivierung entscheidet das Konto, die Methode der Betrag - Anlage EÜR, BWA im Aufbau der Form 01, Umsatzsteuer-Voranmeldung - ELSTER-Anbindung über ERiC mit validierendem Simulationsmodus - DATEV-Buchungsstapel (EXTF 700) und GoBD-Datenträgerüberlassung (Z3) - Kontenrahmen SKR03 und SKR04, je Mandant beim Anlegen wählbar - Mandantenfähigkeit mit einer SQLite-Datei je Mandant; Testmandanten sind von der Übermittlung ans Finanzamt ausgeschlossen Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
238 lines
8.1 KiB
TypeScript
238 lines
8.1 KiB
TypeScript
import { db } from '../db.js';
|
||
import type { Regime, RegimeEintrag, UstBehandlung } from './typen.js';
|
||
import { ustAusBrutto, ustAusNetto } from './geld.js';
|
||
|
||
/**
|
||
* Grenzen der Kleinunternehmerregelung (§19 UStG) ab dem Besteuerungszeitraum 2025.
|
||
* Bis einschließlich 2024 galten 22.000 EUR (Vorjahr, brutto) / 50.000 EUR (Prognose).
|
||
*/
|
||
export const KU_GRENZEN = {
|
||
bis2024: { vorjahr: 22_000_00, laufend: 50_000_00, laufendIstPrognose: true },
|
||
ab2025: { vorjahr: 25_000_00, laufend: 100_000_00, laufendIstPrognose: false },
|
||
} as const;
|
||
|
||
export function grenzenFuer(jahr: number) {
|
||
return jahr >= 2025 ? KU_GRENZEN.ab2025 : KU_GRENZEN.bis2024;
|
||
}
|
||
|
||
export function alleRegime(): RegimeEintrag[] {
|
||
return db
|
||
.prepare('SELECT * FROM steuer_regime ORDER BY gueltig_ab ASC')
|
||
.all() as RegimeEintrag[];
|
||
}
|
||
|
||
/**
|
||
* Löst das umsatzsteuerliche Regime für ein Datum auf: der jüngste Eintrag,
|
||
* dessen `gueltig_ab` nicht in der Zukunft des Datums liegt. Ohne passenden
|
||
* Eintrag gilt Kleinunternehmer (konservativer Default für Gründer).
|
||
*/
|
||
export function regimeAm(datum: string): Regime {
|
||
const row = db
|
||
.prepare(
|
||
'SELECT modus FROM steuer_regime WHERE gueltig_ab <= ? ORDER BY gueltig_ab DESC LIMIT 1',
|
||
)
|
||
.get(datum) as { modus: Regime } | undefined;
|
||
return row?.modus ?? 'kleinunternehmer';
|
||
}
|
||
|
||
/** Gibt es innerhalb des Zeitraums einen Regimewechsel? Wichtig für UStVA-Hinweise. */
|
||
export function regimeWechselIm(von: string, bis: string): RegimeEintrag[] {
|
||
return db
|
||
.prepare('SELECT * FROM steuer_regime WHERE gueltig_ab > ? AND gueltig_ab <= ? ORDER BY gueltig_ab')
|
||
.all(von, bis) as RegimeEintrag[];
|
||
}
|
||
|
||
export interface UstErgebnis {
|
||
brutto: number;
|
||
netto: number;
|
||
ust: number;
|
||
satzBp: number;
|
||
behandlung: UstBehandlung;
|
||
vorsteuerAbziehbar: boolean;
|
||
hinweise: string[];
|
||
}
|
||
|
||
export interface UstEingabe {
|
||
art: 'einnahme' | 'ausgabe';
|
||
/** Maßgeblich für das Regime: der Zeitpunkt der Leistungsausführung. */
|
||
leistungsdatum: string;
|
||
/** Genau eines von beiden angeben. */
|
||
brutto?: number;
|
||
netto?: number;
|
||
satzBp: number;
|
||
behandlung: UstBehandlung;
|
||
}
|
||
|
||
/**
|
||
* Zentrale USt-Ermittlung. Sie erzwingt die Regeln des zum Leistungsdatum
|
||
* geltenden Regimes – dadurch bleibt ein unterjähriger Wechsel automatisch
|
||
* konsistent, ohne dass Altbuchungen angefasst werden müssen.
|
||
*/
|
||
export function berechneUst(e: UstEingabe): UstErgebnis {
|
||
const regime = regimeAm(e.leistungsdatum);
|
||
const hinweise: string[] = [];
|
||
let behandlung = e.behandlung;
|
||
let satzBp = e.satzBp;
|
||
let vorsteuerAbziehbar = true;
|
||
|
||
if (regime === 'kleinunternehmer') {
|
||
if (e.art === 'einnahme') {
|
||
if (behandlung === 'regel' && satzBp > 0) {
|
||
hinweise.push(
|
||
'Zum Leistungsdatum galt die Kleinunternehmerregelung (§19 UStG): ' +
|
||
'der Umsatz ist steuerfrei, es wurde keine Umsatzsteuer angesetzt.',
|
||
);
|
||
}
|
||
behandlung = 'kleinunternehmer';
|
||
satzBp = 0;
|
||
} else {
|
||
// Vorsteuerabzug ist nach §19 Abs. 1 Satz 4 UStG ausgeschlossen ->
|
||
// die Ausgabe geht brutto in die EÜR ein.
|
||
if (satzBp > 0 && behandlung === 'regel') {
|
||
hinweise.push(
|
||
'Kein Vorsteuerabzug als Kleinunternehmer – die Betriebsausgabe wird mit dem Bruttobetrag erfasst.',
|
||
);
|
||
}
|
||
vorsteuerAbziehbar = false;
|
||
// Reverse-Charge/i.g. Erwerb bleiben auch für Kleinunternehmer steuerpflichtig.
|
||
if (behandlung !== 'reverse_charge_13b' && behandlung !== 'ig_erwerb') {
|
||
behandlung = 'kleinunternehmer';
|
||
satzBp = 0;
|
||
} else {
|
||
hinweise.push(
|
||
'Steuerschuld des Leistungsempfängers besteht auch für Kleinunternehmer – ' +
|
||
'eine USt-Voranmeldung ist für diesen Zeitraum abzugeben (§18 Abs. 4a UStG), ' +
|
||
'ein Vorsteuerabzug daraus jedoch nicht möglich.',
|
||
);
|
||
vorsteuerAbziehbar = false;
|
||
}
|
||
}
|
||
} else {
|
||
if (behandlung === 'kleinunternehmer') {
|
||
hinweise.push(
|
||
'Zum Leistungsdatum galt bereits die Regelbesteuerung – Behandlung auf "regel" gesetzt.',
|
||
);
|
||
behandlung = 'regel';
|
||
}
|
||
if (
|
||
behandlung === 'steuerfrei_ohne_vst' ||
|
||
behandlung === 'nicht_steuerbar'
|
||
) {
|
||
vorsteuerAbziehbar = behandlung === 'nicht_steuerbar';
|
||
satzBp = 0;
|
||
}
|
||
if (behandlung === 'steuerfrei_mit_vst' || behandlung === 'ig_lieferung') {
|
||
satzBp = 0;
|
||
}
|
||
}
|
||
|
||
let brutto: number;
|
||
let netto: number;
|
||
let ust: number;
|
||
|
||
if (behandlung === 'reverse_charge_13b' || behandlung === 'ig_erwerb') {
|
||
// Der Rechnungsbetrag ist netto; die Steuer schuldet der Leistungsempfänger.
|
||
netto = e.netto ?? e.brutto ?? 0;
|
||
ust = ustAusNetto(netto, satzBp);
|
||
brutto = netto; // gezahlt wird nur der Nettobetrag
|
||
} else if (e.brutto !== undefined) {
|
||
brutto = e.brutto;
|
||
ust = ustAusBrutto(brutto, satzBp);
|
||
netto = brutto - ust;
|
||
} else {
|
||
netto = e.netto ?? 0;
|
||
ust = ustAusNetto(netto, satzBp);
|
||
brutto = netto + ust;
|
||
}
|
||
|
||
return { brutto, netto, ust, satzBp, behandlung, vorsteuerAbziehbar, hinweise };
|
||
}
|
||
|
||
export interface GrenzenStatus {
|
||
jahr: number;
|
||
vorjahresumsatz: number;
|
||
laufenderUmsatz: number;
|
||
grenzeVorjahr: number;
|
||
grenzeLaufend: number;
|
||
regimeJetzt: Regime;
|
||
vorjahrUeberschritten: boolean;
|
||
laufendUeberschritten: boolean;
|
||
/** Kleinunternehmerregelung entfällt zum 1. Januar des Folgejahres */
|
||
wechselImFolgejahr: boolean;
|
||
warnungen: string[];
|
||
}
|
||
|
||
/**
|
||
* Überwacht die §19-Grenzen. Maßgeblich ist der Gesamtumsatz nach §19 Abs. 2 UStG
|
||
* (vereinnahmte Entgelte); hier näherungsweise die Summe der steuerbaren
|
||
* Betriebseinnahmen nach Zuflussprinzip.
|
||
*/
|
||
export function grenzenStatus(jahr: number): GrenzenStatus {
|
||
const umsatz = (j: number): number => {
|
||
const r = db
|
||
.prepare(
|
||
`SELECT COALESCE(SUM(betrag_brutto),0) AS s FROM buchungen
|
||
WHERE art='einnahme' AND storniert=0
|
||
AND ust_behandlung NOT IN ('nicht_steuerbar')
|
||
AND zahlungsdatum BETWEEN ? AND ?`,
|
||
)
|
||
.get(`${j}-01-01`, `${j}-12-31`) as { s: number };
|
||
return r.s;
|
||
};
|
||
|
||
const g = grenzenFuer(jahr);
|
||
const vorjahresumsatz = umsatz(jahr - 1);
|
||
const laufenderUmsatz = umsatz(jahr);
|
||
const regimeJetzt = regimeAm(`${jahr}-12-31`);
|
||
const warnungen: string[] = [];
|
||
|
||
const vorjahrUeberschritten = vorjahresumsatz > g.vorjahr;
|
||
const laufendUeberschritten = laufenderUmsatz > g.laufend;
|
||
|
||
const folgejahr = grenzenFuer(jahr + 1);
|
||
const wechselImFolgejahr =
|
||
regimeJetzt === 'kleinunternehmer' && laufenderUmsatz > folgejahr.vorjahr;
|
||
|
||
if (regimeJetzt === 'kleinunternehmer') {
|
||
if (wechselImFolgejahr) {
|
||
warnungen.push(
|
||
`Der Umsatz ${jahr} liegt mit ${(laufenderUmsatz / 100).toLocaleString('de-DE')} EUR über der ` +
|
||
`Vorjahresgrenze von ${(folgejahr.vorjahr / 100).toLocaleString('de-DE')} EUR. ` +
|
||
`Die Kleinunternehmerregelung entfällt damit ab dem 01.01.${jahr + 1} – bitte zu diesem ` +
|
||
'Datum einen Wechsel zur Regelbesteuerung anlegen und ab dann Umsatzsteuer ausweisen.',
|
||
);
|
||
}
|
||
if (vorjahrUeberschritten) {
|
||
warnungen.push(
|
||
`Der Vorjahresumsatz ${jahr - 1} liegt über ${(g.vorjahr / 100).toLocaleString('de-DE')} EUR. ` +
|
||
`Ab dem 01.01.${jahr} greift die Regelbesteuerung – bitte einen Regimewechsel zum 01.01.${jahr} anlegen.`,
|
||
);
|
||
}
|
||
if (laufendUeberschritten && jahr >= 2025) {
|
||
warnungen.push(
|
||
`Die laufende Grenze von ${(g.laufend / 100).toLocaleString('de-DE')} EUR wurde in ${jahr} überschritten. ` +
|
||
'Seit 2025 endet die Kleinunternehmerregelung sofort mit dem Umsatz, der die Grenze übersteigt ' +
|
||
'(§19 Abs. 1 Satz 3 UStG) – der Regimewechsel ist unterjährig zu diesem Datum einzutragen.',
|
||
);
|
||
}
|
||
if (!laufendUeberschritten && laufenderUmsatz > (g.laufend * 8) / 10) {
|
||
warnungen.push(
|
||
`Achtung: ${((laufenderUmsatz / g.laufend) * 100).toFixed(0)} % der laufenden Umsatzgrenze erreicht.`,
|
||
);
|
||
}
|
||
}
|
||
|
||
return {
|
||
jahr,
|
||
vorjahresumsatz,
|
||
laufenderUmsatz,
|
||
grenzeVorjahr: g.vorjahr,
|
||
grenzeLaufend: g.laufend,
|
||
regimeJetzt,
|
||
vorjahrUeberschritten,
|
||
laufendUeberschritten,
|
||
wechselImFolgejahr,
|
||
warnungen,
|
||
};
|
||
}
|