import Link from "next/link"; import { getLocale, getTranslations } from "next-intl/server"; import { Printer } from "lucide-react"; import { PageHead } from "@/components/mockup-ui"; import { KindLabel, PendingBadge, formatDayKey } from "@/components/billing/ui"; import { buttonCls } from "@/components/work-orders/button-cls"; import { Empty, Field, inputCls, LinkTabs } from "@/components/work-orders/ui"; import { BILLING_KINDS, BILLING_STATUSES, type BillingKind, type BillingStatus } from "@/lib/billing/schemas"; import { formatHm } from "@/lib/billing/statement"; import { formatDate } from "@/lib/work-orders/time"; import { requirePageContext } from "@/server/api/context"; import { listBillingRecords, type BillingListItem } from "@/server/services/billing/queries"; import { tenantTimezone } from "@/server/services/work-orders/_shared"; import { customerDisplayName, customerFilterOptions, teamOptions } from "@/server/services/work-orders/options"; type SP = Record; const one = (v: string | string[] | undefined) => (Array.isArray(v) ? v[0] : v) || undefined; /** * Backoffice `/billing` (L14): completed orders, confirmed milestones and approved daily reports that * the external accounting can invoice. Tabs Offen · Abgerechnet · Storniert, filters, multi-select → * print view. No prices, no export. */ export default async function BillingOverviewPage({ searchParams }: { searchParams: Promise }) { const ctx = await requirePageContext("billing"); const t = await getTranslations("billing"); if (!ctx.permissions.has("billing:read")) { return (
{t("list.noPermission")}
); } const sp = await searchParams; const statusParam = one(sp.status); const status: BillingStatus = (BILLING_STATUSES as readonly string[]).includes(statusParam ?? "") ? (statusParam as BillingStatus) : "open"; const kindParam = one(sp.kind); const kind = (BILLING_KINDS as readonly string[]).includes(kindParam ?? "") ? (kindParam as BillingKind) : undefined; const base = { kind, customerId: one(sp.customerId), teamId: one(sp.teamId), from: one(sp.from), to: one(sp.to), q: one(sp.q) }; const page = Math.max(1, Math.floor(Number(one(sp.page)) || 1)); const [list, totals, customers, teams, tz, locale] = await Promise.all([ listBillingRecords(ctx, { ...base, status, page }), Promise.all(BILLING_STATUSES.map((s) => listBillingRecords(ctx, { ...base, status: s, pageSize: 1 }).then((r) => r.total))), customerFilterOptions(ctx), teamOptions(ctx), tenantTimezone(ctx), getLocale(), ]); const qs = (extra: Record) => { const p = new URLSearchParams(); for (const [k, v] of Object.entries({ ...base, status, ...extra })) if (v !== undefined && v !== "") p.set(k, String(v)); return `/billing?${p.toString()}`; }; const period = (i: BillingListItem) => i.kind === "daily_report" && i.reportDate ? formatDayKey(i.reportDate, locale) : `${formatDate(i.periodFrom, locale, tz)} – ${formatDate(i.periodTo, locale, tz)}`; const section = (i: BillingListItem) => (i.kind === "milestone" ? i.milestoneTitle : null); const pages = Math.max(1, Math.ceil(list.total / list.pageSize)); return (
({ href: qs({ status: s, page: undefined }), label: t(`tabs.${s}`), active: s === status, count: totals[i] }))} />
{t("filter.title")}
{t("filter.reset")}
{list.items.length === 0 ? ( {t(`list.empty.${status}`)} ) : (

{t("list.selectHint")}

{list.items.map((i) => ( ))}
{t("list.select")} {t("list.order")} {t("list.customerSite")} {t("list.section")} {t("list.period")} {t("list.work")} {t("list.travel")} {t("list.trips")} {t("list.material")} {status === "open" ? t("list.readySince") : status === "billed" ? t("list.billedAt") : t("list.voidedAt")}
{i.workOrder.number}
{i.workOrder.title}
{i.customer.name}
{i.site?.name ?? "—"}
{section(i) &&
{section(i)}
} {i.summary.pendingMinutes > 0 && (
)}
{period(i)} {t("hm", { value: formatHm(i.summary.workMinutes) })} {t("hm", { value: formatHm(i.summary.travelMinutes) })} {i.summary.trips} {i.summary.materialCount} {status === "open" && formatDate(i.readySince, locale, tz)} {status === "billed" && ( <> {formatDate(i.billedAt, locale, tz)} {i.invoiceNumber &&
{t("list.invoice", { number: i.invoiceNumber })}
} )} {status === "voided" && ( <> {formatDate(i.voidedAt, locale, tz)} {i.voidReason &&
{i.voidReason}
} )}
)} {pages > 1 && ( )}
); }