     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<string, { label: string; cls: string }> = {
    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<string, string> = {
    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<AccountingData>('/api/aos/accounting')
   103→  const [submitting, setSubmitting] = useState(false)
   104→  const [preparedXml, setPreparedXml] = useState<string | null>(null)
   105→
   106→  if (loading || !data) {
   107→    return (
   108→      <div className="space-y-6 p-4 md:p-6">
   109→        <Skeleton className="h-9 w-40" />
   110→        <Skeleton className="h-40" />
   111→      </div>
   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→    <div className="space-y-6 p-4 md:p-6">
   165→      <PageHeader
   166→        title="Buchhaltung"
   167→        description="Alman muhasebe uyumlu: Umsatzsteuer-Voranmeldung (ELSTER), EÜR, Bankabgleich (CAMT.053) und Mahnwesen. GoBD-konform mit Audit-Hash-Kette."
   168→      />
   169→
   170→      {/* KPIs */}
   171→      <div className="grid grid-cols-2 gap-4 sm:grid-cols-4">
   172→        <Card className="p-4">
   173→          <div className="flex items-center justify-between">
   174→            <p className="text-xs text-muted-foreground">USt-Zahllast (lfd. Monat)</p>
   175→            <Landmark className="size-3.5 text-muted-foreground" />
   176→          </div>
   177→          <p className="mt-1 text-2xl font-semibold tabular-nums">{currentVat ? formatEuroCompact(currentVat.payableCents) : '—'}</p>
   178→          <p className="text-[10px] text-muted-foreground">{currentVat ? `${MONTH_NAMES[currentVat.periodMonth - 1]} ${currentVat.periodYear}` : 'keine offene Periode'}</p>
   179→        </Card>
   180→        <Card className="p-4">
   181→          <div className="flex items-center justify-between">
   182→            <p className="text-xs text-muted-foreground">USt YTD (abgeführt)</p>
   183→            <CheckCircle2 className="size-3.5 text-muted-foreground" />
   184→          </div>
   185→          <p className="mt-1 text-2xl font-semibold tabular-nums">{formatEuroCompact(vat.totalPayableYTD)}</p>
   186→          <p className="text-[10px] text-muted-foreground">{vat.stats.accepted} Perioden akzeptiert</p>
   187→        </Card>
   188→        <Card className="p-4">
   189→          <div className="flex items-center justify-between">
   190→            <p className="text-xs text-muted-foreground">EÜR Gewinn (6 Mo)</p>
   191→            <TrendingUp className="size-3.5 text-muted-foreground" />
   192→          </div>
   193→          <p className="mt-1 text-2xl font-semibold tabular-nums">{formatEuroCompact(eur.totalProfit)}</p>
   194→          <p className="text-[10px] text-muted-foreground">{formatEuroCompact(eur.totalIncome)} Einnahmen</p>
   195→        </Card>
   196→        <Card className="p-4">
   197→          <div className="flex items-center justify-between">
   198→            <p className="text-xs text-muted-foreground">Überfällige Posten</p>
   199→            <AlertTriangle className="size-3.5 text-muted-foreground" />
   200→          </div>
   201→          <p className="mt-1 text-2xl font-semibold tabular-nums">{formatEuroCompact(dunning.stats.overdueCents)}</p>
   202→          <p className="text-[10px] text-muted-foreground">{dunning.stats.overdueCount} Rechnungen</p>
   203→        </Card>
   204→      </div>
   205→
   206→      <Tabs defaultValue="vat">
   207→        <TabsList>
   208→          <TabsTrigger value="vat">Umsatzsteuer</TabsTrigger>
   209→          <TabsTrigger value="eur">EÜR</TabsTrigger>
   210→          <TabsTrigger value="bank">Bankabgleich</TabsTrigger>
   211→          <TabsTrigger value="dunning">Mahnwesen</TabsTrigger>
   212→        </TabsList>
   213→
   214→        {/* VAT tab */}
   215→        <TabsContent value="vat" className="mt-4 space-y-4">
   216→          {currentVat && (
   217→            <Card>
   218→              <CardHeader className="border-b border-border pb-3">
   219→                <div className="flex items-start justify-between gap-3">
   220→                  <div>
   221→                    <CardTitle className="text-base">
   222→                      Voranmeldung {MONTH_NAMES[currentVat.periodMonth - 1]} {currentVat.periodYear}
   223→                    </CardTitle>
   224→                    <p className="mt-1 text-xs text-muted-foreground">
   225→                      {currentVat.periodType === 'monthly' ? 'Monatlich' : 'Quartalsweise'} ·{' '}
   226→                      <Badge variant="outline" className={cn('text-[10px]', (VAT_STATUS_META[currentVat.status] ?? VAT_STATUS_META.draft).cls)}>
   227→                        {(VAT_STATUS_META[currentVat.status] ?? VAT_STATUS_META.draft).label}
   228→                      </Badge>
   229→                    </p>
   230→                  </div>
   231→                  {(currentVat.status === 'computed' || currentVat.status === 'prepared') && (
   232→                    <Button size="sm" className="gap-1.5" onClick={() => submitVat(currentVat.id)} disabled={submitting}>
   233→                      {submitting ? <Loader2 className="size-3.5 animate-spin" /> : <Send className="size-3.5" />}
   234→                      {currentVat.status === 'prepared' ? 'UStVA-XML neu erstellen' : 'UStVA-XML erstellen'}
   235→                    </Button>
   236→                  )}
   237→                </div>
   238→              </CardHeader>
   239→              <CardContent className="p-4">
   240→                {/* Kennzahlen */}
   241→                <div className="mb-4">
   242→                  <p className="mb-2 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground">Kennzahlen (UStVA)</p>
   243→                  <div className="grid grid-cols-2 gap-2 sm:grid-cols-3 lg:grid-cols-6">
   244→                    <Kennzahl kz="81" label="19% Umsatz (netto)" value={currentVat.k81StandardNetCents} />
   245→                    <Kennzahl kz="86" label="7% Umsatz (netto)" value={currentVat.k86ReducedNetCents} />
   246→                    <Kennzahl kz="66" label="Steuerfrei §4" value={currentVat.k66TaxFreeCents} />
   247→                    <Kennzahl kz="83" label="Steuer 19%" value={currentVat.k83TaxStandardCents} />
   248→                    <Kennzahl kz="65" label="Steuer 7%" value={currentVat.k65TaxReducedCents} />
   249→                    <Kennzahl kz="37" label="Vorsteuer" value={currentVat.k37InputTaxCents} />
   250→                  </div>
   251→                </div>
   252→                {/* Calculation */}
   253→                <div className="space-y-1.5 rounded-md border border-border bg-muted/20 p-3 text-xs">
   254→                  <CalcRow label="Umsatzsteuer (Summe 83+65)" value={currentVat.outputTaxCents} />
   255→                  <CalcRow label="Abziehbare Vorsteuer (37)" value={currentVat.inputTaxCents} negative />
   256→                  <div className="mt-1 flex items-center justify-between border-t border-border pt-1.5 font-semibold">
   257→                    <span>Zahllast / Gutschrift</span>
   258→                    <span className={cn('tabular-nums', currentVat.payableCents < 0 && 'text-muted-foreground')}>
   259→                      {currentVat.payableCents < 0 ? '−' : ''}{formatEuro(Math.abs(currentVat.payableCents))}
   260→                    </span>
   261→                  </div>
   262→                </div>
   263→                {currentVat.notes && (
   264→                  <p className="mt-3 text-[11px] text-muted-foreground">{currentVat.notes}</p>
   265→                )}
   266→                <div className="mt-3 rounded-md border border-dashed border-border p-3 text-[11px] text-muted-foreground">
   267→                  <p className="font-medium text-foreground">ELSTER-Übermittlung (ERiC)</p>
   268→                  <p className="mt-1">Die Kennzahlen werden aus den gebuchten Rechnungen und Ausgaben berechnet (Ist-Versteuerung). Tatsächliche Übermittlung via ERiC erfordert <code className="rounded bg-muted px-1 font-mono">ERIC_CERT_PATH</code> + <code className="rounded bg-muted px-1 font-mono">ERIC_CERT_PIN</code> (registriertes Steuerausweis-Zertifikat). Ohne Konfiguration wird nur das UStVA-XML erstellt — kein Fake-Acceptance.</p>
   269→                </div>
   270→
   271→                {/* Prepared XML preview (real, not mock) */}
   272→                {preparedXml && (
   273→                  <div className="mt-3 rounded-md border border-foreground/30 bg-foreground/5 p-3">
   274→                    <div className="flex items-center justify-between">
   275→                      <p className="text-[10px] font-semibold uppercase tracking-wide text-muted-foreground">
   276→                        UStVA-XML erstellt ({preparedXml.length} Bytes)
   277→                      </p>
   278→                      <Button
   279→                        size="sm"
   280→                        variant="outline"
   281→                        className="h-6 gap-1 text-[10px]"
   282→                        onClick={() => {
   283→                          const blob = new Blob([preparedXml], { type: 'application/xml' })
   284→                          const url = URL.createObjectURL(blob)
   285→                          const a = document.createElement('a')
   286→                          a.href = url
   287→                          a.download = `UStVA-${currentVat.periodYear}-${String(currentVat.periodMonth).padStart(2, '0')}.xml`
   288→                          a.click()
   289→                          URL.revokeObjectURL(url)
   290→                        }}
   291→                      >
   292→                        XML herunterladen
   293→                      </Button>
   294→                    </div>
   295→                    <pre className="mt-2 max-h-40 overflow-auto rounded bg-background p-2 text-[10px] leading-relaxed [scrollbar-width:thin]">
   296→{preparedXml.slice(0, 800)}{preparedXml.length > 800 ? '\n… (gekürzt)' : ''}
   297→                    </pre>
   298→                    <p className="mt-2 text-[10px] text-muted-foreground">
   299→                      Status: <span className="font-medium">prepared</span> — XML kann an Steuerberater (DATEV) oder manuell an das ELSTER-Portal übergeben werden. Kein automatischer Versand ohne ERiC-Zertifikat.
   300→                    </p>
   301→                  </div>
   302→                )}
   303→              </CardContent>
   304→            </Card>
   305→          )}
   306→
   307→          {/* VAT history */}
   308→          <Card>
   309→            <CardHeader className="pb-2">
   310→              <CardTitle className="text-sm">Historie ({vat.history.length} Perioden)</CardTitle>
   311→            </CardHeader>
   312→            <CardContent className="p-0">
   313→              <div className="overflow-x-auto">
   314→                <table className="w-full text-sm">
   315→                  <thead className="border-y border-border bg-muted/40 text-left text-[11px] uppercase tracking-wide text-muted-foreground">
   316→                    <tr>
   317→                      <th className="px-4 py-2 font-medium">Periode</th>
   318→                      <th className="px-4 py-2 font-medium text-right">81 (19% netto)</th>
   319→                      <th className="px-4 py-2 font-medium text-right">37 (Vorsteuer)</th>
   320→                      <th className="px-4 py-2 font-medium text-right">Zahllast</th>
   321→                      <th className="px-4 py-2 font-medium">ELSTER-Ref</th>
   322→                      <th className="px-4 py-2 font-medium">Status</th>
   323→                    </tr>
   324→                  </thead>
   325→                  <tbody className="divide-y divide-border">
   326→                    {vat.history.map((v) => (
   327→                      <tr key={v.id} className="transition hover:bg-muted/40">
   328→                        <td className="px-4 py-2.5 text-xs font-medium">{MONTH_NAMES[v.periodMonth - 1]} {v.periodYear}</td>
   329→                        <td className="px-4 py-2.5 text-right text-xs tabular-nums">{formatEuro(v.k81StandardNetCents)}</td>
   330→                        <td className="px-4 py-2.5 text-right text-xs tabular-nums">{formatEuro(v.k37InputTaxCents)}</td>
   331→                        <td className="px-4 py-2.5 text-right text-xs font-semibold tabular-nums">{formatEuro(v.payableCents)}</td>
   332→                        <td className="px-4 py-2.5"><code className="text-[10px] font-mono text-muted-foreground">{v.elsterRef ?? '—'}</code></td>
   333→                        <td className="px-4 py-2.5">
   334→                          <Badge variant="outline" className={cn('text-[10px]', (VAT_STATUS_META[v.status] ?? VAT_STATUS_META.draft).cls)}>
   335→                            {(VAT_STATUS_META[v.status] ?? VAT_STATUS_META.draft).label}
   336→                          </Badge>
   337→                        </td>
   338→                      </tr>
   339→                    ))}
   340→                  </tbody>
   341→                </table>
   342→              </div>
   343→            </CardContent>
   344→          </Card>
   345→        </TabsContent>
   346→
   347→        {/* EÜR tab */}
   348→        <TabsContent value="eur" className="mt-4 space-y-4">
   349→          <div className="grid grid-cols-1 gap-4 lg:grid-cols-3">
   350→            <Card className="lg:col-span-2">
   351→              <CardHeader className="pb-2">
   352→                <CardTitle className="text-sm">Einnahmen vs. Ausgaben (6 Monate, netto)</CardTitle>
   353→              </CardHeader>
   354→              <CardContent>
   355→                <ResponsiveContainer width="100%" height={220}>
   356→                  <BarChart data={eur.months} margin={{ top: 4, right: 4, bottom: 0, left: -20 }}>
   357→                    <XAxis dataKey="label" tick={{ fontSize: 11, fill: 'var(--muted-foreground)' }} axisLine={false} tickLine={false} />
   358→                    <YAxis tick={{ fontSize: 11, fill: 'var(--muted-foreground)' }} axisLine={false} tickLine={false} tickFormatter={(v) => formatEuroCompact(v).replace(' €', '')} width={50} />
   359→                    <Tooltip
   360→                      cursor={{ fill: 'var(--muted)', opacity: 0.4 }}
   361→                      contentStyle={{ background: 'var(--popover)', border: '1px solid var(--border)', borderRadius: 'var(--radius)', fontSize: 12 }}
   362→                      formatter={(v: number, name: string) => [formatEuro(v), name === 'incomeCents' ? 'Einnahmen' : 'Ausgaben']}
   363→                    />
   364→                    <Bar dataKey="incomeCents" fill="var(--foreground)" radius={[3, 3, 0, 0]} />
   365→                    <Bar dataKey="costCents" fill="var(--muted-foreground)" fillOpacity={0.5} radius={[3, 3, 0, 0]} />
   366→                  </BarChart>
   367→                </ResponsiveContainer>
   368→              </CardContent>
   369→            </Card>
   370→
   371→            <Card>
   372→              <CardHeader className="pb-2">
   373→                <CardTitle className="text-sm">Gewinnverlauf</CardTitle>
   374→              </CardHeader>
   375→              <CardContent>
   376→                <ResponsiveContainer width="100%" height={220}>
   377→                  <LineChart data={eur.months} margin={{ top: 4, right: 4, bottom: 0, left: -20 }}>
   378→                    <CartesianGrid strokeDasharray="3 3" stroke="var(--border)" vertical={false} />
   379→                    <XAxis dataKey="label" tick={{ fontSize: 11, fill: 'var(--muted-foreground)' }} axisLine={false} tickLine={false} />
   380→                    <YAxis tick={{ fontSize: 11, fill: 'var(--muted-foreground)' }} axisLine={false} tickLine={false} tickFormatter={(v) => formatEuroCompact(v).replace(' €', '')} width={50} />
   381→                    <Tooltip
   382→                      contentStyle={{ background: 'var(--popover)', border: '1px solid var(--border)', borderRadius: 'var(--radius)', fontSize: 12 }}
   383→                      formatter={(v: number) => [formatEuro(v), 'Gewinn']}
   384→                    />
   385→                    <Line type="monotone" dataKey="profitCents" stroke="var(--foreground)" strokeWidth={2} dot={{ fill: 'var(--foreground)', r: 3 }} />
   386→                  </LineChart>
   387→                </ResponsiveContainer>
   388→              </CardContent>
   389→            </Card>
   390→          </div>
   391→
   392→          <div className="grid grid-cols-1 gap-4 lg:grid-cols-2">
   393→            <Card>
   394→              <CardHeader className="pb-2">
   395→                <CardTitle className="text-sm">EÜR-Summe (6 Monate)</CardTitle>
   396→              </CardHeader>
   397→              <CardContent className="space-y-1.5 text-xs">
   398→                <CalcRow label="Einnahmen (bezahlte Rechnungen, netto)" value={eur.totalIncome} />
   399→                <CalcRow label="Ausgaben (Aufwendungen, netto)" value={eur.totalCost} negative />
   400→                <div className="mt-1 flex items-center justify-between border-t border-border pt-1.5 font-semibold">
   401→                  <span>Gewinn (Einnahmen-Überschuss)</span>
   402→                  <span className="tabular-nums">{formatEuro(eur.totalProfit)}</span>
   403→                </div>
   404→              </CardContent>
   405→            </Card>
   406→
   407→            <Card>
   408→              <CardHeader className="pb-2">
   409→                <CardTitle className="text-sm">Ausgaben nach Kategorie</CardTitle>
   410→              </CardHeader>
   411→              <CardContent>
   412→                <div className="space-y-2">
   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→                      <div key={c.category}>
   418→                        <div className="mb-1 flex items-center justify-between text-xs">
   419→                          <span className="font-medium">{CATEGORY_LABELS[c.category] ?? c.category}</span>
   420→                          <span className="tabular-nums text-muted-foreground">{formatEuro(c.cents)}</span>
   421→                        </div>
   422→                        <div className="h-1.5 w-full overflow-hidden rounded bg-muted">
   423→                          <div className="h-full rounded bg-foreground" style={{ width: `${pct}%` }} />
   424→                        </div>
   425→                      </div>
   426→                    )
   427→                  })}
   428→                </div>
   429→              </CardContent>
   430→            </Card>
   431→          </div>
   432→
   433→          <div className="rounded-md border border-dashed border-border p-3 text-[11px] text-muted-foreground">
   434→            <p className="font-medium text-foreground">EÜR (Einnahmen-Überschuss-Rechnung)</p>
   435→            <p className="mt-1">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.</p>
   436→          </div>
   437→        </TabsContent>
   438→
   439→        {/* Bank reconciliation tab */}
   440→        <TabsContent value="bank" className="mt-4 space-y-4">
   441→          <div className="grid grid-cols-2 gap-4 sm:grid-cols-4">
   442→            <Card className="p-4">
   443→              <div className="flex items-center justify-between">
   444→                <p className="text-xs text-muted-foreground">Zugeordnet</p>
   445→                <CheckCircle2 className="size-3.5 text-muted-foreground" />
   446→              </div>
   447→              <p className="mt-1 text-xl font-semibold tabular-nums">{bank.stats.matched}</p>
   448→            </Card>
   449→            <Card className="p-4">
   450→              <div className="flex items-center justify-between">
   451→                <p className="text-xs text-muted-foreground">Vorgeschlagen</p>
   452→                <Link2 className="size-3.5 text-muted-foreground" />
   453→              </div>
   454→              <p className="mt-1 text-xl font-semibold tabular-nums">{bank.stats.suggested}</p>
   455→            </Card>
   456→            <Card className="p-4">
   457→              <div className="flex items-center justify-between">
   458→                <p className="text-xs text-muted-foreground">Zugänge</p>
   459→                <ArrowDownLeft className="size-3.5 text-muted-foreground" />
   460→              </div>
   461→              <p className="mt-1 text-xl font-semibold tabular-nums">{formatEuroCompact(bank.stats.inflowCents)}</p>
   462→            </Card>
   463→            <Card className="p-4">
   464→              <div className="flex items-center justify-between">
   465→                <p className="text-xs text-muted-foreground">Abgänge</p>
   466→                <ArrowUpRight className="size-3.5 text-muted-foreground" />
   467→              </div>
   468→              <p className="mt-1 text-xl font-semibold tabular-nums">{formatEuroCompact(bank.stats.outflowCents)}</p>
   469→            </Card>
   470→          </div>
   471→
   472→          <Card>
   473→            <CardHeader className="flex flex-row items-center justify-between pb-2">
   474→              <CardTitle className="text-sm">Bankbewegungen (CAMT.053 Import)</CardTitle>
   475→              <input
   476→                type="file"
   477→                accept=".xml,application/xml,text/xml"
   478→                onChange={async (e) => {
   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→              <Button
   503→                size="sm"
   504→                variant="outline"
   505→                className="h-7 gap-1.5 text-xs"
   506→                onClick={() => document.getElementById('camt-import')?.click()}
   507→              >
   508→                <Banknote className="size-3.5" />
   509→                CAMT.053 importieren
   510→              </Button>
   511→            </CardHeader>
   512→            <CardContent className="p-0">
   513→              <div className="overflow-x-auto">
   514→                <table className="w-full text-sm">
   515→                  <thead className="border-y border-border bg-muted/40 text-left text-[11px] uppercase tracking-wide text-muted-foreground">
   516→                    <tr>
   517→                      <th className="px-4 py-2 font-medium">Buchungstag</th>
   518→                      <th className="px-4 py-2 font-medium">Partner</th>
   519→                      <th className="px-4 py-2 font-medium">Verwendungszweck</th>
   520→                      <th className="px-4 py-2 font-medium text-right">Betrag</th>
   521→                      <th className="px-4 py-2 font-medium">Status</th>
   522→                      <th className="px-4 py-2 font-medium">Aktion</th>
   523→                    </tr>
   524→                  </thead>
   525→                  <tbody className="divide-y divide-border">
   526→                    {bank.transactions.map((tx) => {
   527→                      const isIncome = tx.amountCents > 0
   528→                      return (
   529→                        <tr key={tx.id} className="transition hover:bg-muted/40">
   530→                          <td className="px-4 py-2.5 text-xs">{formatDate(tx.bookingDate)}</td>
   531→                          <td className="px-4 py-2.5 text-xs">
   532→                            <p className="font-medium">{tx.partnerName ?? '—'}</p>
   533→                            {tx.partnerIban && <p className="text-[10px] font-mono text-muted-foreground">{tx.partnerIban}</p>}
   534→                          </td>
   535→                          <td className="px-4 py-2.5">
   536→                            <p className="truncate text-xs">{tx.purpose ?? tx.reference ?? '—'}</p>
   537→                            {tx.matchReason && <p className="text-[10px] text-muted-foreground">{tx.matchReason}</p>}
   538→                          </td>
   539→                          <td className={cn('px-4 py-2.5 text-right text-xs font-semibold tabular-nums', isIncome ? 'text-foreground' : 'text-muted-foreground')}>
   540→                            {isIncome ? '+' : ''}{formatEuro(tx.amountCents)}
   541→                          </td>
   542→                          <td className="px-4 py-2.5">
   543→                            <Badge variant="outline" className={cn(
   544→                              'text-[10px]',
   545→                              tx.matchedStatus === 'matched' && 'border-foreground/40',
   546→                              tx.matchedStatus === 'suggested' && 'border-foreground/30',
   547→                            )}>
   548→                              {tx.matchedStatus === 'matched' ? 'Zugeordnet' : tx.matchedStatus === 'suggested' ? 'Vorschlag' : 'Offen'}
   549→                            </Badge>
   550→                          </td>
   551→                          <td className="px-4 py-2.5">
   552→                            {tx.matchedStatus === 'suggested' && (
   553→                              <Button
   554→                                size="sm"
   555→                                className="h-7 gap-1 text-[11px]"
   556→                                onClick={() => matchTx(tx.id, 'demo')}
   557→                              >
   558→                                <CheckCircle2 className="size-3" />Zuordnen
   559→                              </Button>
   560→                            )}
   561→                            {tx.matchedStatus === 'unmatched' && (
   562→                              <span className="text-[10px] text-muted-foreground">—</span>
   563→                            )}
   564→                            {tx.matchedStatus === 'matched' && (
   565→                              <CheckCircle2 className="size-4 text-foreground" />
   566→                            )}
   567→                          </td>
   568→                        </tr>
   569→                      )
   570→                    })}
   571→                  </tbody>
   572→                </table>
   573→              </div>
   574→            </CardContent>
   575→          </Card>
   576→        </TabsContent>
   577→
   578→        {/* Dunning tab */}
   579→        <TabsContent value="dunning" className="mt-4 space-y-4">
   580→          <div className="grid grid-cols-1 gap-4 lg:grid-cols-2">
   581→            <Card>
   582→              <CardHeader className="pb-2">
   583→                <CardTitle className="flex items-center gap-2 text-sm">
   584→                  <AlertTriangle className="size-3.5 text-muted-foreground" />
   585→                  Überfällige Rechnungen ({dunning.overdue.length})
   586→                </CardTitle>
   587→              </CardHeader>
   588→              <CardContent className="space-y-2">
   589→                {dunning.overdue.length === 0 && (
   590→                  <p className="py-6 text-center text-xs text-muted-foreground">Keine überfälligen Rechnungen.</p>
   591→                )}
   592→                {dunning.overdue.map((inv) => (
   593→                  <div key={inv.id} className="rounded-md border border-border p-2.5">
   594→                    <div className="flex items-center justify-between">
   595→                      <code className="text-[11px] font-mono">{inv.number}</code>
   596→                      <Badge variant="outline" className="text-[10px]">{inv.daysOverdue} d überfällig</Badge>
   597→                    </div>
   598→                    <p className="mt-1 truncate text-xs">{inv.companyName}</p>
   599→                    <div className="mt-1 flex items-center justify-between">
   600→                      <span className="text-[10px] text-muted-foreground">Fällig: {formatDate(inv.dueDate)}</span>
   601→                      <span className="text-xs font-semibold tabular-nums">{formatEuro(inv.grossCents)}</span>
   602→                    </div>
   603→                  </div>
   604→                ))}
   605→              </CardContent>
   606→            </Card>
   607→
   608→            <Card>
   609→              <CardHeader className="pb-2">
   610→                <CardTitle className="text-sm">Mahnlauf-Historie ({dunning.runs.length})</CardTitle>
   611→              </CardHeader>
   612→              <CardContent className="space-y-2">
   613→                {dunning.runs.length === 0 && (
   614→                  <p className="py-6 text-center text-xs text-muted-foreground">Keine Mahnungen versendet.</p>
   615→                )}
   616→                {dunning.runs.map((run) => (
   617→                  <div key={run.id} className="rounded-md border border-border p-2.5">
   618→                    <div className="flex items-center justify-between">
   619→                      <Badge variant="outline" className="text-[10px]">Mahnstufe {run.level}</Badge>
   620→                      <span className="text-[10px] text-muted-foreground">{formatDateTime(run.sentAt)}</span>
   621→                    </div>
   622→                    <p className="mt-1 truncate text-xs">{run.invoice.company.name} · <code className="font-mono text-[10px]">{run.invoice.number}</code></p>
   623→                    {run.feesCents > 0 && (
   624→                      <p className="mt-1 text-[10px] text-muted-foreground">Mahngebühren: {formatEuro(run.feesCents)}</p>
   625→                    )}
   626→                  </div>
   627→                ))}
   628→              </CardContent>
   629→            </Card>
   630→          </div>
   631→
   632→          <div className="rounded-md border border-dashed border-border p-3 text-[11px] text-muted-foreground">
   633→            <p className="font-medium text-foreground">Mahnwesen (3 Stufen)</p>
   634→            <p className="mt-1">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).</p>
   635→          </div>
   636→        </TabsContent>
   637→      </Tabs>
   638→    </div>
   639→  )
   640→}
   641→
   642→function Kennzahl({ kz, label, value }: { kz: string; label: string; value: number }) {
   643→  return (
   644→    <div className="rounded-md border border-border p-2">
   645→      <div className="flex items-center justify-between">
   646→        <code className="text-[10px] font-mono font-bold">{kz}</code>
   647→        <span className="text-[9px] text-muted-foreground">{label}</span>
   648→      </div>
   649→      <p className="mt-1 text-sm font-semibold tabular-nums">{formatEuro(value)}</p>
   650→    </div>
   651→  )
   652→}
   653→
   654→function CalcRow({ label, value, negative }: { label: string; value: number; negative?: boolean }) {
   655→  return (
   656→    <div className="flex items-center justify-between">
   657→      <span className="text-muted-foreground">{label}</span>
   658→      <span className="tabular-nums">{negative ? '−' : ''}{formatEuro(value)}</span>
   659→    </div>
   660→  )
   661→}
   662→