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>
This commit is contained in:
@@ -0,0 +1,237 @@
|
||||
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,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user