'use client' import { useState } from 'react' import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Separator } from '@/components/ui/separator' import { Loader2, FileCheck2, Lock, ShieldCheck } from 'lucide-react' import { formatEuro, formatDate, daysUntil } from '@/lib/format' import { toast } from 'sonner' import { cn } from '@/lib/utils' export interface Invoice { id: string number: string status: string type: string issueDate: string dueDate: string paidDate: string | null netCents: number taxBp: number grossCents: number zugferd: boolean xrechnung: boolean datevExported: boolean company: { id: string; name: string } lines: { id: string; description: string; quantity: number; unitCents: number; totalCents: number }[] } const STATUS_META: Record = { draft: { label: 'Entwurf', cls: 'bg-muted text-foreground' }, sent: { label: 'Versendet', cls: 'bg-foreground/70 text-background' }, paid: { label: 'Bezahlt', cls: 'bg-foreground text-background' }, overdue: { label: 'Überfällig', cls: 'bg-foreground text-background' }, cancelled: { label: 'Storniert', cls: 'bg-muted text-muted-foreground' }, storno: { label: 'Storno', cls: 'bg-muted text-muted-foreground' }, } const TYPE_LABELS: Record = { final: 'Schlussrechnung', partial: 'Abschlagszahlung', recurring: 'Wiederkehrend', credit: 'Gutschrift', } interface Props { invoice: Invoice | null open: boolean onOpenChange: (o: boolean) => void onFinalized: () => void } export function InvoiceDetailDialog({ invoice, open, onOpenChange, onFinalized }: Props) { const [finalizing, setFinalizing] = useState(false) const [wantXrechnung, setWantXrechnung] = useState(false) if (!invoice) return null const st = STATUS_META[invoice.status] ?? STATUS_META.draft const dueDays = daysUntil(invoice.dueDate) const isDraft = invoice.status === 'draft' const finalize = async () => { setFinalizing(true) const t = toast.loading('Rechnung wird finalisiert (E-Rechnung generiert)…') try { const r = await fetch(`/api/aos/finance/${invoice.id}/finalize`, { method: 'PATCH', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ zugferd: true, xrechnung: wantXrechnung }), }) if (!r.ok) throw new Error('finalize failed') toast.success('Rechnung finalisiert und versendet', { id: t }) onOpenChange(false) onFinalized() } catch { toast.error('Finalisierung fehlgeschlagen', { id: t }) } finally { setFinalizing(false) } } return (
{invoice.number} {invoice.company.name} · {TYPE_LABELS[invoice.type] ?? invoice.type}
{st.label}
{/* Meta */}
{/* Lines */}
Rechnungspositionen ({invoice.lines.length})
{invoice.lines.map((l) => (

{l.description}

{l.quantity} × {formatEuro(l.unitCents)}

{formatEuro(l.totalCents)}

))}
{/* E-Rechnung status */}

E-Rechnung-Status

ZUGFeRD {invoice.zugferd ? '✓' : '—'} XRechnung {invoice.xrechnung ? '✓' : '—'} DATEV {invoice.datevExported ? '✓' : '—'}

EN 16931 validiert via @e-invoice-eu/core. GoBD: Finalisierung sperrt nachträgliches Editieren (Storno via neue Zeile).

{/* Dunning */} {invoice.status === 'overdue' && (

Mahnstufe 1 fällig

Überfällig seit {Math.abs(dueDays)} Tagen. Automatisches Mahnwesen konfigurierbar (3 Stufen → Suspendierung verknüpfter Lizenz).

)}
{isDraft ? ( <> ) : ( <>
Finalisiert · lückenlos nummeriert
)}
) } function Meta({ label, value, sub }: { label: string; value: string; sub?: string }) { return (

{label}

{value}

{sub &&

{sub}

}
) } function Row({ label, value, strong }: { label: string; value: string; strong?: boolean }) { return (
{label} {value}
) }