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:
duffyduck
2026-08-03 21:45:52 +02:00
co-authored by Claude Opus 5
commit bf371c8118
59 changed files with 12913 additions and 0 deletions
+115
View File
@@ -0,0 +1,115 @@
/**
* Umrechnung der auf dem Steuerbescheid gedruckten Landes-Steuernummer in das
* 13-stellige bundeseinheitliche Format, das ELSTER erwartet.
*
* Aufbau: <2- bis 3-stelliges Landespräfix><Finanzamtsnummer><0><Bezirk><Unterscheidungsnr.><Prüfziffer>
*/
export type Bundesland =
| 'BW' | 'BY' | 'BE' | 'BB' | 'HB' | 'HH' | 'HE' | 'MV'
| 'NI' | 'NW' | 'RP' | 'SL' | 'SN' | 'ST' | 'SH' | 'TH';
export const BUNDESLAENDER: { code: Bundesland; name: string }[] = [
{ code: 'BW', name: 'Baden-Württemberg' },
{ code: 'BY', name: 'Bayern' },
{ code: 'BE', name: 'Berlin' },
{ code: 'BB', name: 'Brandenburg' },
{ code: 'HB', name: 'Bremen' },
{ code: 'HH', name: 'Hamburg' },
{ code: 'HE', name: 'Hessen' },
{ code: 'MV', name: 'Mecklenburg-Vorpommern' },
{ code: 'NI', name: 'Niedersachsen' },
{ code: 'NW', name: 'Nordrhein-Westfalen' },
{ code: 'RP', name: 'Rheinland-Pfalz' },
{ code: 'SL', name: 'Saarland' },
{ code: 'SN', name: 'Sachsen' },
{ code: 'ST', name: 'Sachsen-Anhalt' },
{ code: 'SH', name: 'Schleswig-Holstein' },
{ code: 'TH', name: 'Thüringen' },
];
type Regel =
/** 10-stellige Landesnummer FFBBBUUUUP -> <praefix>FF0BBBUUUUP */
| { typ: 'kurz'; praefix: string }
/** 11-stellige Landesnummer mit führender Füllziffer -> wie 'kurz' nach Abschneiden */
| { typ: 'fuellziffer'; praefix: string }
/** Bayern: FFFBBBUUUUP -> 9FFF0BBBUUUUP */
| { typ: 'bayern' }
/** NRW: FFFBBBBUUUP -> 5FFF0BBBBUUUP */
| { typ: 'nrw' };
const REGELN: Record<Bundesland, Regel> = {
BW: { typ: 'kurz', praefix: '28' },
BY: { typ: 'bayern' },
BE: { typ: 'kurz', praefix: '11' },
BB: { typ: 'fuellziffer', praefix: '30' },
HB: { typ: 'kurz', praefix: '24' },
HH: { typ: 'kurz', praefix: '22' },
HE: { typ: 'fuellziffer', praefix: '26' },
MV: { typ: 'fuellziffer', praefix: '40' },
NI: { typ: 'kurz', praefix: '23' },
NW: { typ: 'nrw' },
RP: { typ: 'kurz', praefix: '27' },
SL: { typ: 'kurz', praefix: '10' },
SN: { typ: 'fuellziffer', praefix: '32' },
ST: { typ: 'fuellziffer', praefix: '31' },
SH: { typ: 'kurz', praefix: '21' },
TH: { typ: 'fuellziffer', praefix: '41' },
};
export class SteuernummerFehler extends Error {}
export function nurZiffern(s: string): string {
return s.replace(/\D/g, '');
}
export function bundeseinheitlich(steuernummer: string, land: Bundesland): string {
const z = nurZiffern(steuernummer);
if (!z) throw new SteuernummerFehler('Es ist keine Steuernummer hinterlegt.');
const regel = REGELN[land];
let ergebnis: string;
if (regel.typ === 'bayern') {
if (z.length !== 11) {
throw new SteuernummerFehler(
`Bayerische Steuernummern haben 11 Ziffern (Format 123/456/78901), erhalten: ${z.length}.`,
);
}
ergebnis = `9${z.slice(0, 3)}0${z.slice(3)}`;
} else if (regel.typ === 'nrw') {
if (z.length !== 11) {
throw new SteuernummerFehler(
`Nordrhein-westfälische Steuernummern haben 11 Ziffern (Format 123/4567/8901), erhalten: ${z.length}.`,
);
}
ergebnis = `5${z.slice(0, 3)}0${z.slice(3)}`;
} else {
let kurz = z;
if (regel.typ === 'fuellziffer') {
if (z.length === 11) kurz = z.slice(1);
else if (z.length !== 10) {
throw new SteuernummerFehler(
`Steuernummer für ${land} muss 10 oder 11 Ziffern haben, erhalten: ${z.length}.`,
);
}
} else if (z.length !== 10) {
throw new SteuernummerFehler(
`Steuernummer für ${land} muss 10 Ziffern haben (Format 12/345/67890), erhalten: ${z.length}.`,
);
}
ergebnis = `${regel.praefix}${kurz.slice(0, 2)}0${kurz.slice(2)}`;
}
if (ergebnis.length !== 13) {
throw new SteuernummerFehler(
`Umrechnung ergab ${ergebnis.length} statt 13 Stellen – bitte Bundesland und Steuernummer prüfen.`,
);
}
return ergebnis;
}
/** Die ersten vier Stellen sind die Bundesfinanzamtsnummer – ELSTER-Empfänger-ID. */
export function finanzamtNummer(bundesSteuernummer: string): string {
return bundesSteuernummer.slice(0, 4);
}