Files
open-eur-buchhaltung/server/src/elster/steuernummer.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

116 lines
4.0 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.
/**
* 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);
}