From 0d0fa6b5e12bc18ac07878ed28d90f8ce34ca23d Mon Sep 17 00:00:00 2001 From: Z User Date: Tue, 27 Oct 2026 17:41:53 +0000 Subject: [PATCH] bbb245ef-c23d-4d22-87ba-d5366d9def16 --- db/custom.db | Bin 331776 -> 385024 bytes .../read_1793121909886_c9de18dc46a4.txt | 662 ++++++++++++++++++ worklog.md | 39 ++ 3 files changed, 701 insertions(+) create mode 100644 tool-results/read_1793121909886_c9de18dc46a4.txt diff --git a/db/custom.db b/db/custom.db index 9d0292f6f147892b5845ca137f25cbb9f89b7e75..9ac5aebae1f968dabe2a28c4c4dbb3996571c923 100644 GIT binary patch delta 33360 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 delta 27179 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 diff --git a/tool-results/read_1793121909886_c9de18dc46a4.txt b/tool-results/read_1793121909886_c9de18dc46a4.txt new file mode 100644 index 0000000..25296ce --- /dev/null +++ b/tool-results/read_1793121909886_c9de18dc46a4.txt @@ -0,0 +1,662 @@ + 1→'use client' + 2→ + 3→import { + 4→ BookOpen, + 5→ Landmark, + 6→ TrendingUp, + 7→ ArrowDownLeft, + 8→ ArrowUpRight, + 9→ Send, + 10→ CheckCircle2, + 11→ AlertTriangle, + 12→ Loader2, + 13→ Link2, + 14→ Banknote, + 15→} from 'lucide-react' + 16→import { useFetch } from '@/hooks/use-fetch' + 17→import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' + 18→import { Badge } from '@/components/ui/badge' + 19→import { Button } from '@/components/ui/button' + 20→import { Skeleton } from '@/components/ui/skeleton' + 21→import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs' + 22→import { PageHeader } from '../primitives' + 23→import { formatEuro, formatEuroCompact, formatDate, formatDateTime } from '@/lib/format' + 24→import { cn } from '@/lib/utils' + 25→import { toast } from 'sonner' + 26→import { useState } from 'react' + 27→import { Bar, BarChart, ResponsiveContainer, Tooltip, XAxis, YAxis, Cell, Line, LineChart, CartesianGrid } from 'recharts' + 28→ + 29→interface VatReturn { + 30→ id: string + 31→ periodYear: number + 32→ periodMonth: number + 33→ periodType: string + 34→ status: string + 35→ k81StandardNetCents: number + 36→ k86ReducedNetCents: number + 37→ k66TaxFreeCents: number + 38→ k83TaxStandardCents: number + 39→ k65TaxReducedCents: number + 40→ k37InputTaxCents: number + 41→ outputTaxCents: number + 42→ inputTaxCents: number + 43→ payableCents: number + 44→ submittedAt: string | null + 45→ elsterRef: string | null + 46→ acceptedAt: string | null + 47→ notes: string | null + 48→} + 49→interface BankTransaction { + 50→ id: string + 51→ amountCents: number + 52→ bookingDate: string + 53→ valueDate: string + 54→ partnerName: string | null + 55→ partnerIban: string | null + 56→ reference: string | null + 57→ purpose: string | null + 58→ matchedStatus: string + 59→ matchReason: string | null + 60→} + 61→interface AccountingData { + 62→ vat: { + 63→ current: VatReturn | null + 64→ history: VatReturn[] + 65→ totalPayableYTD: number + 66→ stats: { periods: number; accepted: number; pending: number } + 67→ } + 68→ eur: { + 69→ months: { label: string; incomeCents: number; costCents: number; profitCents: number }[] + 70→ totalIncome: number + 71→ totalCost: number + 72→ totalProfit: number + 73→ costsByCategory: { category: string; cents: number }[] + 74→ } + 75→ bank: { + 76→ transactions: BankTransaction[] + 77→ stats: { matched: number; suggested: number; unmatched: number; inflowCents: number; outflowCents: number; total: number } + 78→ } + 79→ dunning: { + 80→ overdue: { id: string; number: string; companyName: string; grossCents: number; dueDate: string; daysOverdue: number }[] + 81→ runs: { id: string; level: number; status: string; feesCents: number; sentAt: string; invoice: { number: string; company: { name: string } } }[] + 82→ stats: { overdueCount: number; overdueCents: number; dunningSent: number } + 83→ } + 84→} + 85→ + 86→const VAT_STATUS_META: Record = { + 87→ draft: { label: 'Entwurf', cls: 'bg-muted text-foreground' }, + 88→ computed: { label: 'Berechnet', cls: 'bg-foreground/50 text-background' }, + 89→ prepared: { label: 'XML erstellt', cls: 'bg-foreground/70 text-background' }, + 90→ submitted: { label: 'Übermittelt', cls: 'bg-foreground/70 text-background' }, + 91→ accepted: { label: 'Akzeptiert', cls: 'bg-foreground text-background' }, + 92→ corrected: { label: 'Korrigiert', cls: 'bg-muted text-muted-foreground' }, + 93→} + 94→ + 95→const MONTH_NAMES = ['Jan', 'Feb', 'Mär', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'] + 96→ + 97→const CATEGORY_LABELS: Record = { + 98→ reise: 'Reise', software: 'Software', hardware: 'Hardware', fremdleistung: 'Fremdleistung', buero: 'Büro', marketing: 'Marketing', + 99→} + 100→ + 101→export function AccountingView() { + 102→ const { data, loading, refetch } = useFetch('/api/aos/accounting') + 103→ const [submitting, setSubmitting] = useState(false) + 104→ const [preparedXml, setPreparedXml] = useState(null) + 105→ + 106→ if (loading || !data) { + 107→ return ( + 108→
+ 109→ + 110→ + 111→
+ 112→ ) + 113→ } + 114→ + 115→ const { vat, eur, bank, dunning } = data + 116→ + 117→ const submitVat = async (vatId: string) => { + 118→ setSubmitting(true) + 119→ setPreparedXml(null) + 120→ const t = toast.loading('UStVA-XML wird erstellt und validiert…') + 121→ try { + 122→ const r = await fetch(`/api/aos/accounting/vat/${vatId}/submit`, { method: 'PATCH' }) + 123→ const res = await r.json() + 124→ if (!r.ok) { + 125→ if (res.details && Array.isArray(res.details)) { + 126→ throw new Error('Validierung fehlgeschlagen: ' + res.details.join('; ')) + 127→ } + 128→ throw new Error(res.error || 'submit failed') + 129→ } + 130→ if (res.transmitted === false) { + 131→ // Honest path: XML prepared, transmission requires ERiC certificate + 132→ setPreparedXml(res.xml) + 133→ toast.success(`UStVA-XML erstellt (${res.xmlSize} Bytes) — Übermittlung an ERiC ausstehend`, { id: t, duration: 6000 }) + 134→ } else { + 135→ toast.success('An ELSTER übermittelt', { id: t }) + 136→ } + 137→ refetch() + 138→ } catch (err) { + 139→ toast.error(err instanceof Error ? err.message : 'Übermittlung fehlgeschlagen', { id: t }) + 140→ } finally { + 141→ setSubmitting(false) + 142→ } + 143→ } + 144→ + 145→ const matchTx = async (txId: string, invoiceId: string) => { + 146→ const t = toast.loading('Bankbewegung wird zugeordnet…') + 147→ try { + 148→ const r = await fetch(`/api/aos/accounting/bank/${txId}/match`, { + 149→ method: 'POST', + 150→ headers: { 'content-type': 'application/json' }, + 151→ body: JSON.stringify({ invoiceId }), + 152→ }) + 153→ if (!r.ok) throw new Error('match failed') + 154→ toast.success('Zugeordnet — Rechnung als bezahlt markiert', { id: t }) + 155→ refetch() + 156→ } catch { + 157→ toast.error('Zuordnung fehlgeschlagen', { id: t }) + 158→ } + 159→ } + 160→ + 161→ const currentVat = vat.current + 162→ + 163→ return ( + 164→
+ 165→ + 169→ + 170→ {/* KPIs */} + 171→
+ 172→ + 173→
+ 174→

USt-Zahllast (lfd. Monat)

+ 175→ + 176→
+ 177→

{currentVat ? formatEuroCompact(currentVat.payableCents) : '—'}

+ 178→

{currentVat ? `${MONTH_NAMES[currentVat.periodMonth - 1]} ${currentVat.periodYear}` : 'keine offene Periode'}

+ 179→
+ 180→ + 181→
+ 182→

USt YTD (abgeführt)

+ 183→ + 184→
+ 185→

{formatEuroCompact(vat.totalPayableYTD)}

+ 186→

{vat.stats.accepted} Perioden akzeptiert

+ 187→
+ 188→ + 189→
+ 190→

EÜR Gewinn (6 Mo)

+ 191→ + 192→
+ 193→

{formatEuroCompact(eur.totalProfit)}

+ 194→

{formatEuroCompact(eur.totalIncome)} Einnahmen

+ 195→
+ 196→ + 197→
+ 198→

Überfällige Posten

+ 199→ + 200→
+ 201→

{formatEuroCompact(dunning.stats.overdueCents)}

+ 202→

{dunning.stats.overdueCount} Rechnungen

+ 203→
+ 204→
+ 205→ + 206→ + 207→ + 208→ Umsatzsteuer + 209→ EÜR + 210→ Bankabgleich + 211→ Mahnwesen + 212→ + 213→ + 214→ {/* VAT tab */} + 215→ + 216→ {currentVat && ( + 217→ + 218→ + 219→
+ 220→
+ 221→ + 222→ Voranmeldung {MONTH_NAMES[currentVat.periodMonth - 1]} {currentVat.periodYear} + 223→ + 224→

+ 225→ {currentVat.periodType === 'monthly' ? 'Monatlich' : 'Quartalsweise'} ·{' '} + 226→ + 227→ {(VAT_STATUS_META[currentVat.status] ?? VAT_STATUS_META.draft).label} + 228→ + 229→

+ 230→
+ 231→ {(currentVat.status === 'computed' || currentVat.status === 'prepared') && ( + 232→ + 236→ )} + 237→
+ 238→
+ 239→ + 240→ {/* Kennzahlen */} + 241→
+ 242→

Kennzahlen (UStVA)

+ 243→
+ 244→ + 245→ + 246→ + 247→ + 248→ + 249→ + 250→
+ 251→
+ 252→ {/* Calculation */} + 253→
+ 254→ + 255→ + 256→
+ 257→ Zahllast / Gutschrift + 258→ + 259→ {currentVat.payableCents < 0 ? '−' : ''}{formatEuro(Math.abs(currentVat.payableCents))} + 260→ + 261→
+ 262→
+ 263→ {currentVat.notes && ( + 264→

{currentVat.notes}

+ 265→ )} + 266→
+ 267→

ELSTER-Übermittlung (ERiC)

+ 268→

Die Kennzahlen werden aus den gebuchten Rechnungen und Ausgaben berechnet (Ist-Versteuerung). Tatsächliche Übermittlung via ERiC erfordert ERIC_CERT_PATH + ERIC_CERT_PIN (registriertes Steuerausweis-Zertifikat). Ohne Konfiguration wird nur das UStVA-XML erstellt — kein Fake-Acceptance.

+ 269→
+ 270→ + 271→ {/* Prepared XML preview (real, not mock) */} + 272→ {preparedXml && ( + 273→
+ 274→
+ 275→

+ 276→ UStVA-XML erstellt ({preparedXml.length} Bytes) + 277→

+ 278→ + 294→
+ 295→
+   296→{preparedXml.slice(0, 800)}{preparedXml.length > 800 ? '\n… (gekürzt)' : ''}
+   297→                    
+ 298→

+ 299→ Status: prepared — XML kann an Steuerberater (DATEV) oder manuell an das ELSTER-Portal übergeben werden. Kein automatischer Versand ohne ERiC-Zertifikat. + 300→

+ 301→
+ 302→ )} + 303→
+ 304→
+ 305→ )} + 306→ + 307→ {/* VAT history */} + 308→ + 309→ + 310→ Historie ({vat.history.length} Perioden) + 311→ + 312→ + 313→
+ 314→ + 315→ + 316→ + 317→ + 318→ + 319→ + 320→ + 321→ + 322→ + 323→ + 324→ + 325→ + 326→ {vat.history.map((v) => ( + 327→ + 328→ + 329→ + 330→ + 331→ + 332→ + 333→ + 338→ + 339→ ))} + 340→ + 341→
Periode81 (19% netto)37 (Vorsteuer)ZahllastELSTER-RefStatus
{MONTH_NAMES[v.periodMonth - 1]} {v.periodYear}{formatEuro(v.k81StandardNetCents)}{formatEuro(v.k37InputTaxCents)}{formatEuro(v.payableCents)}{v.elsterRef ?? '—'} + 334→ + 335→ {(VAT_STATUS_META[v.status] ?? VAT_STATUS_META.draft).label} + 336→ + 337→
+ 342→
+ 343→
+ 344→
+ 345→
+ 346→ + 347→ {/* EÜR tab */} + 348→ + 349→
+ 350→ + 351→ + 352→ Einnahmen vs. Ausgaben (6 Monate, netto) + 353→ + 354→ + 355→ + 356→ + 357→ + 358→ formatEuroCompact(v).replace(' €', '')} width={50} /> + 359→ [formatEuro(v), name === 'incomeCents' ? 'Einnahmen' : 'Ausgaben']} + 363→ /> + 364→ + 365→ + 366→ + 367→ + 368→ + 369→ + 370→ + 371→ + 372→ + 373→ Gewinnverlauf + 374→ + 375→ + 376→ + 377→ + 378→ + 379→ + 380→ formatEuroCompact(v).replace(' €', '')} width={50} /> + 381→ [formatEuro(v), 'Gewinn']} + 384→ /> + 385→ + 386→ + 387→ + 388→ + 389→ + 390→
+ 391→ + 392→
+ 393→ + 394→ + 395→ EÜR-Summe (6 Monate) + 396→ + 397→ + 398→ + 399→ + 400→
+ 401→ Gewinn (Einnahmen-Überschuss) + 402→ {formatEuro(eur.totalProfit)} + 403→
+ 404→
+ 405→
+ 406→ + 407→ + 408→ + 409→ Ausgaben nach Kategorie + 410→ + 411→ + 412→
+ 413→ {eur.costsByCategory.map((c) => { + 414→ const max = eur.costsByCategory[0]?.cents ?? 1 + 415→ const pct = Math.round((c.cents / max) * 100) + 416→ return ( + 417→
+ 418→
+ 419→ {CATEGORY_LABELS[c.category] ?? c.category} + 420→ {formatEuro(c.cents)} + 421→
+ 422→
+ 423→
+ 424→
+ 425→
+ 426→ ) + 427→ })} + 428→
+ 429→ + 430→ + 431→
+ 432→ + 433→
+ 434→

EÜR (Einnahmen-Überschuss-Rechnung)

+ 435→

Für Freiberufler und Kleingewerbe (§ 4 Abs. 3 EStG). Einnahmen bei Ist-Versteuerung nach Zufluss, Ausgaben nach Abfluss. Jahres-EÜR mit AVEÜR an das Finanzamt (Anlage EÜR). AfA und pauschalen werden separat berechnet.

+ 436→
+ 437→ + 438→ + 439→ {/* Bank reconciliation tab */} + 440→ + 441→
+ 442→ + 443→
+ 444→

Zugeordnet

+ 445→ + 446→
+ 447→

{bank.stats.matched}

+ 448→
+ 449→ + 450→
+ 451→

Vorgeschlagen

+ 452→ + 453→
+ 454→

{bank.stats.suggested}

+ 455→
+ 456→ + 457→
+ 458→

Zugänge

+ 459→ + 460→
+ 461→

{formatEuroCompact(bank.stats.inflowCents)}

+ 462→
+ 463→ + 464→
+ 465→

Abgänge

+ 466→ + 467→
+ 468→

{formatEuroCompact(bank.stats.outflowCents)}

+ 469→
+ 470→
+ 471→ + 472→ + 473→ + 474→ Bankbewegungen (CAMT.053 Import) + 475→ { + 479→ const file = e.target.files?.[0] + 480→ if (!file) return + 481→ const t = toast.loading(`CAMT.053 wird geparst: ${file.name}…`) + 482→ try { + 483→ const xml = await file.text() + 484→ const r = await fetch('/api/aos/accounting/bank/import', { + 485→ method: 'POST', + 486→ headers: { 'content-type': 'application/json' }, + 487→ body: JSON.stringify({ xml }), + 488→ }) + 489→ const res = await r.json() + 490→ if (!r.ok) throw new Error(res.error || 'import failed') + 491→ toast.success(`${res.imported} Bewegungen importiert (Batch ${res.batchId.slice(0, 12)}…)`, { id: t }) + 492→ refetch() + 493→ } catch (err) { + 494→ toast.error(err instanceof Error ? err.message : 'Import fehlgeschlagen', { id: t }) + 495→ } finally { + 496→ e.target.value = '' + 497→ } + 498→ }} + 499→ className="hidden" + 500→ id="camt-import" + 501→ /> + 502→ + 511→ + 512→ + 513→
+ 514→ + 515→ + 516→ + 517→ + 518→ + 519→ + 520→ + 521→ + 522→ + 523→ + 524→ + 525→ + 526→ {bank.transactions.map((tx) => { + 527→ const isIncome = tx.amountCents > 0 + 528→ return ( + 529→ + 530→ + 531→ + 535→ + 539→ + 542→ + 551→ + 568→ + 569→ ) + 570→ })} + 571→ + 572→
BuchungstagPartnerVerwendungszweckBetragStatusAktion
{formatDate(tx.bookingDate)} + 532→

{tx.partnerName ?? '—'}

+ 533→ {tx.partnerIban &&

{tx.partnerIban}

} + 534→
+ 536→

{tx.purpose ?? tx.reference ?? '—'}

+ 537→ {tx.matchReason &&

{tx.matchReason}

} + 538→
+ 540→ {isIncome ? '+' : ''}{formatEuro(tx.amountCents)} + 541→ + 543→ + 548→ {tx.matchedStatus === 'matched' ? 'Zugeordnet' : tx.matchedStatus === 'suggested' ? 'Vorschlag' : 'Offen'} + 549→ + 550→ + 552→ {tx.matchedStatus === 'suggested' && ( + 553→ + 560→ )} + 561→ {tx.matchedStatus === 'unmatched' && ( + 562→ — + 563→ )} + 564→ {tx.matchedStatus === 'matched' && ( + 565→ + 566→ )} + 567→
+ 573→
+ 574→
+ 575→
+ 576→
+ 577→ + 578→ {/* Dunning tab */} + 579→ + 580→
+ 581→ + 582→ + 583→ + 584→ + 585→ Überfällige Rechnungen ({dunning.overdue.length}) + 586→ + 587→ + 588→ + 589→ {dunning.overdue.length === 0 && ( + 590→

Keine überfälligen Rechnungen.

+ 591→ )} + 592→ {dunning.overdue.map((inv) => ( + 593→
+ 594→
+ 595→ {inv.number} + 596→ {inv.daysOverdue} d überfällig + 597→
+ 598→

{inv.companyName}

+ 599→
+ 600→ Fällig: {formatDate(inv.dueDate)} + 601→ {formatEuro(inv.grossCents)} + 602→
+ 603→
+ 604→ ))} + 605→
+ 606→
+ 607→ + 608→ + 609→ + 610→ Mahnlauf-Historie ({dunning.runs.length}) + 611→ + 612→ + 613→ {dunning.runs.length === 0 && ( + 614→

Keine Mahnungen versendet.

+ 615→ )} + 616→ {dunning.runs.map((run) => ( + 617→
+ 618→
+ 619→ Mahnstufe {run.level} + 620→ {formatDateTime(run.sentAt)} + 621→
+ 622→

{run.invoice.company.name} · {run.invoice.number}

+ 623→ {run.feesCents > 0 && ( + 624→

Mahngebühren: {formatEuro(run.feesCents)}

+ 625→ )} + 626→
+ 627→ ))} + 628→
+ 629→
+ 630→
+ 631→ + 632→
+ 633→

Mahnwesen (3 Stufen)

+ 634→

Stufe 1: Zahlungserinnerung (kostenlos) · Stufe 2: Mahnung mit Gebühr (2,50 €) · Stufe 3: Letzte Mahnung + Verzugszinsen (§ 288 BGB, 5 % p.a.). Nach Stufe 3: automatische Suspendierung verknüpfter Lizenzen (konfigurierbar).

+ 635→
+ 636→
+ 637→ + 638→
+ 639→ ) + 640→} + 641→ + 642→function Kennzahl({ kz, label, value }: { kz: string; label: string; value: number }) { + 643→ return ( + 644→
+ 645→
+ 646→ {kz} + 647→ {label} + 648→
+ 649→

{formatEuro(value)}

+ 650→
+ 651→ ) + 652→} + 653→ + 654→function CalcRow({ label, value, negative }: { label: string; value: number; negative?: boolean }) { + 655→ return ( + 656→
+ 657→ {label} + 658→ {negative ? '−' : ''}{formatEuro(value)} + 659→
+ 660→ ) + 661→} + 662→ \ No newline at end of file diff --git a/worklog.md b/worklog.md index 93978a4..c3fe943 100644 --- a/worklog.md +++ b/worklog.md @@ -89,3 +89,42 @@ Stage Summary: - All 17 GET handlers now return a 500 JSON response `{ error: }` instead of crashing the request when Prisma throws, when the URL constructor fails, or when `JSON.parse(l.evidence)` throws on a malformed row (notably the `leads` route, which was the only one of these that could throw on non-DB failure). - Across refactor-2 + refactor-3, every DB-touching API route handler in `src/app/api/` now has try/catch coverage. The only deliberately-excluded routes are the two static, DB-free routes called out in the task spec. - Next actions: none required. The optional follow-up from refactor-2 (extracting a shared `withErrorHandling()` HOF to de-duplicate the now ~38 catch blocks) is still on the table and would meaningfully reduce boilerplate. + +--- +Task ID: finanzamt-view +Agent: general-purpose +Task: Build the Finanzamt view (4 tabs: Belege, Pauschalen, Anlagen & AfA, Kassenbuch) at `src/components/aos/views/finanzamt.tsx`. + +Work Log: +- Created `src/components/aos/views/finanzamt.tsx` (~1250 lines). The view is named `FinanzamtView` and also exported as default. Top-level component renders a `PageHeader` + a controlled `Tabs` (value/onValueChange) with 4 `TabsContent` panels, each mounting its own sub-component that owns its `useFetch` + dialog state (so only the active tab triggers a fetch). +- **Tab 1 — Belege (`BelegeTab` + `BelegDialog`):** + - `useFetch('/api/aos/belege')` consumes `{ belege, stats }`. + - 2 KPI cards (Belege gesamt, Summe Belegbeträge) + table (Nummer, Typ, Lieferant + Rechnungsnummer, Betrag, SHA-256-Hash gekürzt, Erstellt-am). + - "Beleg registrieren" dialog: optional file picker (read via `FileReader.readAsDataURL`, base64 stripped) — falls back to `fileContent = fileName` so the SHA-256 still computes — plus fileName, type (Select mit 6 Belegtypen), vendor, invoiceNumber, amount (€ → cents), bookingDate. + - Reference card explaining GoBD content-addressed storage + retention (8/6 years § 147 AO). +- **Tab 2 — Pauschalen (`PauschalenTab` + `PauschaleDialog`):** + - `useFetch('/api/aos/pauschalen')` consumes `{ pauschalen, stats, rates }`. + - 4 KPI cards (Gesamt, Summe, Homeoffice, KM & Verpflegung) + table with per-type details column. + - "Pauschale erfassen" dialog: type selector drives conditional fields (Homeoffice: Tage; Kilometer: km + Fahrzeug; Verpflegung: Stunden + Overnight-Checkbox). Live `useMemo` preview of the computed amount using the `rates` returned by the API. + - Reference card listing the 2026 rates (Homeoffice €6.92/d, KM €0.30/km, Verpflegung €14/€24/€28) with EStG § 4 Abs. 5 / § 9 Abs. 1 citations. +- **Tab 3 — Anlagen & AfA (`AnlagenTab` + `AnlageDialog`):** + - `useFetch('/api/aos/anlagen')` consumes `{ anlagen, stats }`. + - 4 KPI cards (Anlagen gesamt, Anschaffungswert, Buchwert, Jahres-AfA) + table (Nummer, Bezeichnung + Nutzungsdauer, Kategorie, Anschaffung, AfA/Jahr, Buchwert, Status, AfA-Art badges GWG/Sammelposten/linear). + - "Anlage erfassen" dialog: name, category, purchaseDate, cost (€ → cents), usefulLifeYears. Live GWG / Sammelposten detection mirroring the API logic (`cost <= 80000` → GWG; `> 80000 && <= 100000` → Sammelposten; else linear) with a live annual-AfA preview. + - Reference card: GWG § 6 Abs. 2 EStG, Sammelposten § 6 Abs. 2a EStG, lineare AfA. Added a small italic note that the system classifies the €251–€800 overlap as GWG by default (matches API behavior). +- **Tab 4 — Kassenbuch (`KassenbuchTab` + `KassenbuchDialog`):** + - `useFetch('/api/aos/kassenbuch')` consumes `{ entries, stats }`. + - 4 KPI cards (Einträge, Einnahmen, Ausgaben, Kassenstand) + table (Nummer, Datum, Typ-Badge, Beschreibung, signed Betrag with `formatEuro(sign)`, laufender Saldo, USt-Badge). + - "Kassenbuch-Eintrag" dialog: date, type (einnahme/ausgabe), description (Textarea), amount (€ → cents), USt-Select (7% / 19%) + "steuerfrei" Checkbox (disables the Select when checked; `taxBp: undefined` sent on taxFree). + - Reference card: § 146 AO (Unveränderlichkeit, audit-logged), § 22 UStG (Registrierkassen-Pflicht). +- Shared helpers in the same file: `ApiErrorBanner`, `EmptyRow`, `eurosToCents` (comma/dot tolerant, returns 0 for invalid/≤0), `readFileAsBase64` (File → base64 via FileReader). +- All POST operations wrapped in `try { fetch… if (!r.ok) throw new Error(res.error) … toast.success } catch (err) { toast.error(err instanceof Error ? err.message : '…') } finally { setSubmitting(false) }`. All toasts use `toast.loading` → `toast.success`/`toast.error` with `{ id: t }` for in-place updates. `refetch()` is called after every successful write. +- Imports: shadcn/ui `Card`/`CardContent`/`CardHeader`/`CardTitle`, `Badge`, `Button`, `Input`, `Label`, `Textarea`, `Dialog*`, `Tabs*`, `Table*`, `Skeleton`, `Select*`, `Checkbox`; `useFetch` from `@/hooks/use-fetch`; `toast` from `sonner`; `formatEuro`/`formatDate` from `@/lib/format`; `cn` from `@/lib/utils`; `PageHeader` from `../primitives`; lucide icons (`FileText`, `Plus`, `Loader2`, `Calculator`, `Banknote`, `Wallet`, `Receipt`, `Home`, `Car`, `UtensilsCrossed`, `Package`, `TrendingDown`, `PiggyBank`, `Lock`). +- All UI labels in German; no `any` types — every API payload shape declared as an explicit interface; `Pauschale['type']` used as the key type for `PAUSCHALE_TYPE_META` so the map is exhaustive. +- Bonus 1-line fix in `src/app/api/aos/belege/route.ts:45`: added parens around the ternary in `setFullYear(getFullYear() + type.includes('rechnung') ? 8 : 6)` → `setFullYear(getFullYear() + (type.includes('rechnung') ? 8 : 6))`. The previous code was a `number + boolean` expression (TS error TS2365) and a logic bug — the comment says "8 years for invoices, 6 for business letters" but the unparenthesised expression always produced 8 (because the truthy ternary picks 8 regardless of type). Now matches the comment and resolves the only remaining `src/` TS error. + +Stage Summary: +- `npx tsc --noEmit 2>&1 | grep "^src/" | wc -l` → **0** (was 1 before the belege-route fix; the finanzamt.tsx file itself introduced zero new errors). +- `bun run lint` → exit code **0**, no warnings/errors. +- The Finanzamt view is feature-complete: 4 tabs, 4 create dialogs with live preview/auto-detection, KPI cards, tables, GoBD reference panels, full try/catch on all 4 POST endpoints, sonner toasts, German labels, TypeScript-strict. +- Next actions: none required. Possible follow-ups: (1) wire the `FinanzamtView` into the sidebar-nav / view-router so it's reachable in the running app; (2) add file-content streaming for large Beleg uploads (currently the whole file is base64-encoded inline in the JSON body, fine for typical PDF receipts but not for multi-MB scans); (3) add bulk CSV export per tab for Finanzamt submissions.