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,73 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { aktiverMandant } from './mandant';
|
||||
|
||||
export class ApiFehler extends Error {
|
||||
constructor(
|
||||
message: string,
|
||||
public status: number,
|
||||
) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
async function anfrage<T>(pfad: string, init?: RequestInit): Promise<T> {
|
||||
// Jeder Aufruf trägt den aktiven Mandanten; ohne Angabe wählt der Server.
|
||||
const slug = aktiverMandant();
|
||||
const antwort = await fetch(pfad, {
|
||||
...init,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...(slug ? { 'X-Mandant': slug } : {}),
|
||||
...(init?.headers ?? {}),
|
||||
},
|
||||
});
|
||||
const text = await antwort.text();
|
||||
const daten = text ? JSON.parse(text) : null;
|
||||
if (!antwort.ok) {
|
||||
throw new ApiFehler(daten?.fehler ?? `Fehler ${antwort.status}`, antwort.status);
|
||||
}
|
||||
return daten as T;
|
||||
}
|
||||
|
||||
export const api = {
|
||||
get: <T,>(pfad: string) => anfrage<T>(pfad),
|
||||
post: <T,>(pfad: string, body?: unknown) =>
|
||||
anfrage<T>(pfad, { method: 'POST', body: JSON.stringify(body ?? {}) }),
|
||||
put: <T,>(pfad: string, body: unknown) =>
|
||||
anfrage<T>(pfad, { method: 'PUT', body: JSON.stringify(body) }),
|
||||
del: <T,>(pfad: string) => anfrage<T>(pfad, { method: 'DELETE' }),
|
||||
};
|
||||
|
||||
export function useDaten<T>(pfad: string | null): {
|
||||
daten: T | null;
|
||||
fehler: string | null;
|
||||
laedt: boolean;
|
||||
neuLaden: () => void;
|
||||
} {
|
||||
const [daten, setDaten] = useState<T | null>(null);
|
||||
const [fehler, setFehler] = useState<string | null>(null);
|
||||
const [laedt, setLaedt] = useState(true);
|
||||
const [zaehler, setZaehler] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
if (!pfad) return;
|
||||
let abgebrochen = false;
|
||||
setLaedt(true);
|
||||
api
|
||||
.get<T>(pfad)
|
||||
.then((d) => {
|
||||
if (!abgebrochen) {
|
||||
setDaten(d);
|
||||
setFehler(null);
|
||||
}
|
||||
})
|
||||
.catch((e: Error) => !abgebrochen && setFehler(e.message))
|
||||
.finally(() => !abgebrochen && setLaedt(false));
|
||||
return () => {
|
||||
abgebrochen = true;
|
||||
};
|
||||
}, [pfad, zaehler]);
|
||||
|
||||
const neuLaden = useCallback(() => setZaehler((z) => z + 1), []);
|
||||
return { daten, fehler, laedt, neuLaden };
|
||||
}
|
||||
Reference in New Issue
Block a user