import { useState } from "react"; import { Link, useLoaderData, useRevalidator } from "react-router"; import { requireUser } from "@/session.server"; import prisma from "@/lib/prisma.server"; import { Card, CardContent } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Dialog, DialogContent, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { ChevronLeft, Plus, Pencil, Trash2, Loader2 } from "lucide-react"; import { formatCurrency } from "@/lib/tax"; import { afaFuerJahr, buchwert, assetStatus, type AnlagegutRaw } from "@/lib/afa"; export const handle = { breadcrumbs: (data: { companyId: string; companyName: string }) => [ { label: "Mandanten", href: "/companies" }, { label: data.companyName, href: `/companies/${data.companyId}` }, { label: "Anlagevermögen" }, ], }; interface Asset { id: string; bezeichnung: string; beschreibung: string | null; anschaffungsdatum: string; anschaffungskosten: number; nutzungsdauerJahre: number; restwert: number; aktiv: boolean; } interface AssetWithAfa extends Asset { afaJahr: number; buchwertJahr: number; statusLabel: string; } function enrichAsset(a: Asset, year: number): AssetWithAfa { const raw: AnlagegutRaw = { anschaffungskosten: a.anschaffungskosten, nutzungsdauerJahre: a.nutzungsdauerJahre, restwert: a.restwert, anschaffungsdatum: a.anschaffungsdatum, aktiv: a.aktiv, }; return { ...a, afaJahr: afaFuerJahr(raw, year), buchwertJahr: buchwert(raw, year), statusLabel: assetStatus(raw, year), }; } const STATUS_VARIANTS: Record = { aktiv: "success", "vollständig abgeschrieben": "secondary", inaktiv: "outline", }; const emptyForm = { bezeichnung: "", anschaffungsdatum: "", anschaffungskosten: "", nutzungsdauerJahre: "", restwert: "0", beschreibung: "", aktiv: true, }; export async function loader({ request, params }: { request: Request; params: { id: string } }) { const user = await requireUser(request); const company = await prisma.company.findFirst({ where: { id: params.id, userId: user.id }, select: { id: true, name: true }, }); if (!company) throw new Response("Not Found", { status: 404 }); const assets = await prisma.anlagegut.findMany({ where: { companyId: params.id }, orderBy: { anschaffungsdatum: "asc" }, }); return { companyId: company.id, companyName: company.name, initialYear: new Date().getFullYear(), assets: assets.map((a) => ({ id: a.id, bezeichnung: a.bezeichnung, beschreibung: a.beschreibung, anschaffungsdatum: a.anschaffungsdatum.toISOString(), anschaffungskosten: Number(a.anschaffungskosten), nutzungsdauerJahre: a.nutzungsdauerJahre, restwert: Number(a.restwert), aktiv: a.aktiv, })), }; } export default function AnlagevermoegenPage() { const { assets: initialAssets, companyId, companyName, initialYear } = useLoaderData(); const { revalidate } = useRevalidator(); const [year, setYear] = useState(initialYear); const [assets, setAssets] = useState(initialAssets); const [loadingYear, setLoadingYear] = useState(false); const [saving, setSaving] = useState(false); const [deleting, setDeleting] = useState(null); const [dialogOpen, setDialogOpen] = useState(false); const [editingAsset, setEditingAsset] = useState(null); const [form, setForm] = useState(emptyForm); const years = Array.from({ length: 10 }, (_, i) => new Date().getFullYear() + 2 - i); async function loadYear(y: number) { setYear(y); setLoadingYear(true); const res = await fetch(`/api/anlagevermoegen?companyId=${companyId}&year=${y}`); const data = await res.json(); // eslint-disable-next-line @typescript-eslint/no-explicit-any setAssets(data.assets.map((a: any) => ({ id: a.id, bezeichnung: a.bezeichnung, beschreibung: a.beschreibung, anschaffungsdatum: a.anschaffungsdatum, anschaffungskosten: a.anschaffungskosten, nutzungsdauerJahre: a.nutzungsdauerJahre, restwert: a.restwert, aktiv: a.aktiv, }))); setLoadingYear(false); } function openCreate() { setEditingAsset(null); setForm(emptyForm); setDialogOpen(true); } function openEdit(asset: Asset) { setEditingAsset(asset); setForm({ bezeichnung: asset.bezeichnung, anschaffungsdatum: asset.anschaffungsdatum.slice(0, 10), anschaffungskosten: String(asset.anschaffungskosten), nutzungsdauerJahre: String(asset.nutzungsdauerJahre), restwert: String(asset.restwert), beschreibung: asset.beschreibung ?? "", aktiv: asset.aktiv, }); setDialogOpen(true); } async function handleSave() { setSaving(true); const payload = { bezeichnung: form.bezeichnung, anschaffungsdatum: form.anschaffungsdatum, anschaffungskosten: parseFloat(form.anschaffungskosten), nutzungsdauerJahre: parseInt(form.nutzungsdauerJahre), restwert: parseFloat(form.restwert) || 0, beschreibung: form.beschreibung || undefined, aktiv: form.aktiv, }; try { if (editingAsset) { await fetch(`/api/anlagevermoegen/${editingAsset.id}`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), }); } else { await fetch("/api/anlagevermoegen", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ ...payload, companyId }), }); } setDialogOpen(false); await loadYear(year); revalidate(); } finally { setSaving(false); } } async function handleDelete(id: string) { if (!confirm("Anlagegut wirklich löschen?")) return; setDeleting(id); await fetch(`/api/anlagevermoegen/${id}`, { method: "DELETE" }); setDeleting(null); await loadYear(year); revalidate(); } const enriched = assets.map((a) => enrichAsset(a, year)); const aktiveAnlagen = enriched.filter((a) => a.aktiv && a.statusLabel === "aktiv").length; const gesamtAfa = enriched.reduce((s, a) => s + a.afaJahr, 0); const gesamtBuchwert = enriched.reduce((s, a) => s + a.buchwertJahr, 0); const formValid = form.bezeichnung.trim().length > 0 && form.anschaffungsdatum.length > 0 && parseFloat(form.anschaffungskosten) > 0 && parseInt(form.nutzungsdauerJahre) >= 1; return (
Zurück zum Mandanten

Anlagevermögen

{companyName} · {year}

{/* Zusammenfassung */}

Aktive Anlagen

{aktiveAnlagen}

AfA gesamt {year}

{formatCurrency(gesamtAfa)}

Gesamter Buchwert

{formatCurrency(gesamtBuchwert)}

{/* Tabelle */} {loadingYear ? (
Lade Anlagen...
) : enriched.length === 0 ? (

Noch keine Anlagegüter erfasst.

) : (
{enriched.map((asset) => ( ))}
Bezeichnung Anschaffung AK ND (J) AfA {year} Buchwert 31.12.{year} Status

{asset.bezeichnung}

{asset.beschreibung && (

{asset.beschreibung}

)}
{new Date(asset.anschaffungsdatum).toLocaleDateString("de-DE")} {formatCurrency(asset.anschaffungskosten)} {asset.nutzungsdauerJahre} {asset.afaJahr > 0 ? ( {formatCurrency(asset.afaJahr)} ) : ( )} {formatCurrency(asset.buchwertJahr)} {asset.statusLabel}
Gesamt {formatCurrency(gesamtAfa)} {formatCurrency(gesamtBuchwert)}
AfA: lineare Abschreibung nach §7 EStG · Buchwert zum 31.12. des gewählten Jahres
)} {/* Dialog: Anlegen / Bearbeiten */} {editingAsset ? "Anlagegut bearbeiten" : "Neues Anlagegut"}
setForm((f) => ({ ...f, bezeichnung: e.target.value }))} placeholder="z.B. Laptop, Firmenwagen, Maschine" className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500" />
setForm((f) => ({ ...f, anschaffungsdatum: e.target.value }))} className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500" />
setForm((f) => ({ ...f, nutzungsdauerJahre: e.target.value })) } placeholder="z.B. 3" className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500" />
setForm((f) => ({ ...f, anschaffungskosten: e.target.value })) } placeholder="0,00" className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500" />
setForm((f) => ({ ...f, restwert: e.target.value }))} placeholder="0,00" className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500" />