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,185 @@
|
||||
import { useState } from 'react';
|
||||
import { api, useDaten } from '../lib/api';
|
||||
import { useJahr } from '../lib/jahr';
|
||||
import { centAus, datum, eur } from '../lib/format';
|
||||
|
||||
interface AnlageMitAfa {
|
||||
id: number;
|
||||
bezeichnung: string;
|
||||
anschaffungsdatum: string;
|
||||
akosten_netto: number;
|
||||
akosten_brutto: number;
|
||||
nutzungsdauer: number;
|
||||
afa_methode: string;
|
||||
abgang_datum: string | null;
|
||||
notiz: string;
|
||||
afa: {
|
||||
bemessung: number;
|
||||
buchwertAnfang: number;
|
||||
afa: number;
|
||||
buchwertEnde: number;
|
||||
restbuchwertAbgang: number;
|
||||
hinweis: string;
|
||||
};
|
||||
}
|
||||
|
||||
const METHODEN: Record<string, string> = {
|
||||
linear: 'linear (§7 Abs. 1 EStG)',
|
||||
gwg_sofort: 'GWG-Sofortabschreibung',
|
||||
sammelposten: 'Sammelposten über 5 Jahre',
|
||||
keine: 'nicht abnutzbar',
|
||||
};
|
||||
|
||||
export default function Anlagen() {
|
||||
const [jahr, setJahr, jahre] = useJahr();
|
||||
const { daten, neuLaden } = useDaten<AnlageMitAfa[]>(`/api/anlagen?jahr=${jahr}`);
|
||||
const [form, setForm] = useState({
|
||||
bezeichnung: '',
|
||||
anschaffungsdatum: new Date().toISOString().slice(0, 10),
|
||||
nettoFeld: '',
|
||||
bruttoFeld: '',
|
||||
nutzungsdauer: '3',
|
||||
afa_methode: '',
|
||||
});
|
||||
const [meldung, setMeldung] = useState<string | null>(null);
|
||||
|
||||
async function anlegen(e: React.FormEvent) {
|
||||
e.preventDefault();
|
||||
try {
|
||||
const netto = centAus(form.nettoFeld);
|
||||
await api.post('/api/anlagen', {
|
||||
bezeichnung: form.bezeichnung,
|
||||
anschaffungsdatum: form.anschaffungsdatum,
|
||||
akosten_netto: netto,
|
||||
akosten_brutto: form.bruttoFeld ? centAus(form.bruttoFeld) : netto,
|
||||
nutzungsdauer: Number(form.nutzungsdauer) || 3,
|
||||
afa_methode: form.afa_methode || undefined,
|
||||
});
|
||||
setForm({ ...form, bezeichnung: '', nettoFeld: '', bruttoFeld: '' });
|
||||
setMeldung('Anlagegut angelegt.');
|
||||
neuLaden();
|
||||
} catch (err) {
|
||||
setMeldung((err as Error).message);
|
||||
}
|
||||
}
|
||||
|
||||
async function abgangEintragen(a: AnlageMitAfa) {
|
||||
const d = prompt(`Abgangsdatum für "${a.bezeichnung}" (JJJJ-MM-TT):`, `${jahr}-12-31`);
|
||||
if (!d) return;
|
||||
await api.put(`/api/anlagen/${a.id}`, { abgang_datum: d });
|
||||
neuLaden();
|
||||
}
|
||||
|
||||
const summeAfa = (daten ?? []).reduce((s, a) => s + a.afa.afa, 0);
|
||||
const summeBuchwert = (daten ?? []).reduce((s, a) => s + a.afa.buchwertEnde, 0);
|
||||
|
||||
return (
|
||||
<>
|
||||
<h1>Anlagevermögen</h1>
|
||||
<p className="untertitel">
|
||||
Wirtschaftsgüter mit mehrjähriger Nutzung sind nicht sofort abziehbar, sondern über die
|
||||
Nutzungsdauer abzuschreiben. Die AfA fließt automatisch in EÜR und BWA ein.
|
||||
</p>
|
||||
|
||||
<div className="filterreihe">
|
||||
<div className="feld">
|
||||
<label>Jahr</label>
|
||||
<select value={jahr} onChange={(e) => setJahr(Number(e.target.value))}>
|
||||
{jahre.map((j) => <option key={j} value={j}>{j}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="kachelreihe">
|
||||
<div className="kachel">
|
||||
<div className="beschriftung">Abschreibung {jahr}</div>
|
||||
<div className="wert zahl">{eur(summeAfa)}</div>
|
||||
</div>
|
||||
<div className="kachel">
|
||||
<div className="beschriftung">Buchwert 31.12.{jahr}</div>
|
||||
<div className="wert zahl">{eur(summeBuchwert)}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="karte">
|
||||
<h2>Anlagegut erfassen</h2>
|
||||
<form onSubmit={anlegen}>
|
||||
<div className="formularraster">
|
||||
<div className="feld" style={{ gridColumn: 'span 2' }}>
|
||||
<label>Bezeichnung</label>
|
||||
<input required value={form.bezeichnung} onChange={(e) => setForm({ ...form, bezeichnung: e.target.value })} />
|
||||
</div>
|
||||
<div className="feld">
|
||||
<label>Anschaffungsdatum</label>
|
||||
<input type="date" required value={form.anschaffungsdatum} onChange={(e) => setForm({ ...form, anschaffungsdatum: e.target.value })} />
|
||||
</div>
|
||||
<div className="feld">
|
||||
<label>Anschaffungskosten netto</label>
|
||||
<input required inputMode="decimal" placeholder="0,00" value={form.nettoFeld} onChange={(e) => setForm({ ...form, nettoFeld: e.target.value })} />
|
||||
</div>
|
||||
<div className="feld">
|
||||
<label>… brutto</label>
|
||||
<input inputMode="decimal" placeholder="leer = wie netto" value={form.bruttoFeld} onChange={(e) => setForm({ ...form, bruttoFeld: e.target.value })} />
|
||||
<span className="hilfe">für Kleinunternehmer maßgeblich</span>
|
||||
</div>
|
||||
<div className="feld">
|
||||
<label>Nutzungsdauer (Jahre)</label>
|
||||
<input inputMode="numeric" value={form.nutzungsdauer} onChange={(e) => setForm({ ...form, nutzungsdauer: e.target.value })} />
|
||||
</div>
|
||||
<div className="feld">
|
||||
<label>AfA-Methode</label>
|
||||
<select value={form.afa_methode} onChange={(e) => setForm({ ...form, afa_methode: e.target.value })}>
|
||||
<option value="">automatisch nach Betrag</option>
|
||||
{Object.entries(METHODEN).map(([k, v]) => <option key={k} value={k}>{v}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="knopfreihe">
|
||||
<button type="submit" className="primaer">Anlegen</button>
|
||||
</div>
|
||||
</form>
|
||||
{meldung && <div className="hinweis" style={{ marginTop: 12 }}>{meldung}</div>}
|
||||
</div>
|
||||
|
||||
<div className="karte">
|
||||
<div className="tabellenrahmen">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Wirtschaftsgut</th><th>Anschaffung</th><th>Methode</th>
|
||||
<th className="rechts">Bemessung</th><th className="rechts">Buchwert 1.1.</th>
|
||||
<th className="rechts">AfA {jahr}</th><th className="rechts">Buchwert 31.12.</th><th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{(daten ?? []).map((a) => (
|
||||
<tr key={a.id}>
|
||||
<td>
|
||||
{a.bezeichnung}
|
||||
{a.abgang_datum && <div className="abzeichen">Abgang {datum(a.abgang_datum)}</div>}
|
||||
</td>
|
||||
<td>{datum(a.anschaffungsdatum)}</td>
|
||||
<td className="gedimmt" style={{ fontSize: 12 }}>{METHODEN[a.afa_methode] ?? a.afa_methode}</td>
|
||||
<td className="rechts zahl">{eur(a.afa.bemessung)}</td>
|
||||
<td className="rechts zahl">{eur(a.afa.buchwertAnfang)}</td>
|
||||
<td className="rechts zahl">{eur(a.afa.afa)}</td>
|
||||
<td className="rechts zahl">{eur(a.afa.buchwertEnde)}</td>
|
||||
<td className="rechts">
|
||||
{!a.abgang_datum && (
|
||||
<button type="button" style={{ padding: '3px 8px', fontSize: 12 }} onClick={() => abgangEintragen(a)}>
|
||||
Abgang
|
||||
</button>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
{daten?.length === 0 && (
|
||||
<tr><td colSpan={8} className="gedimmt">Noch kein Anlagevermögen erfasst.</td></tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user