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>
186 lines
7.1 KiB
TypeScript
186 lines
7.1 KiB
TypeScript
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>
|
|
</>
|
|
);
|
|
}
|