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>
74 lines
2.1 KiB
TypeScript
74 lines
2.1 KiB
TypeScript
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 };
|
|
}
|