Files
open-eur-buchhaltung/server/src/domain/regime.ts
T
duffyduckandClaude Opus 5 bf371c8118 Einnahmen-Überschuss-Rechnung mit ELSTER, DATEV und Mandantenfähigkeit
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>
2026-08-03 21:45:52 +02:00

238 lines
8.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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,
};
}