Phase7b: Manual Invoice
Some checks failed
Build and Push / build (push) Failing after 54s

This commit is contained in:
2026-05-26 23:08:07 +02:00
parent ed915ec539
commit 38f4c3243e

View File

@@ -1,7 +1,7 @@
import { redirect } from "next/navigation";
import { getTranslations } from "next-intl/server";
import { getSessionUser } from "@/lib/session";
import { listAllInvoiceDrafts } from "@/lib/db";
import { getOrgBilling, listAllInvoiceDrafts } from "@/lib/db";
import { listTenants } from "@/lib/k8s";
import { BackLink } from "@/components/ui/back-link";
import { DraftList } from "@/components/admin/billing/draft-list";
@@ -14,9 +14,9 @@ import { DraftList } from "@/components/admin/billing/draft-list";
* an invoice; visible only to platform admins. From here the admin
* can resume editing or discard.
*
* Building an org-name map by reading tenant labels (same approach
* as the existing /admin/billing/orgs endpoint) so the table can
* show "Customer X" instead of a raw ZITADEL org id.
* Building an org-name map by reading tenant labels (for the set of
* known orgs) + getOrgBilling per org (for the actual company name)
* so the table can show "Customer X" instead of a raw ZITADEL org id.
*/
export default async function AdminInvoiceDraftsPage() {
const user = await getSessionUser();
@@ -29,16 +29,30 @@ export default async function AdminInvoiceDraftsPage() {
listTenants().catch(() => []),
]);
// Build org-id → company-name map from tenant labels. Same shape
// the existing /api/admin/billing/orgs uses. Falls back to the
// raw org id when we don't have a tenant label match.
// Build the set of distinct ZITADEL org ids from tenant labels,
// PLUS the set referenced by any current draft. Drafts may target
// orgs that don't have tenants yet (rare but possible), so we
// union both sources before fetching billing rows.
const orgIds = new Set<string>();
for (const tnt of tenants) {
const oid = tnt.metadata.labels?.["pieced.ch/zitadel-org-id"];
if (oid) orgIds.add(oid);
}
for (const d of drafts) {
orgIds.add(d.zitadelOrgId);
}
// Look up billing in parallel — same pattern as
// /api/admin/billing/orgs uses. Failure for any single org is
// non-fatal (falls back to the raw id in the table).
const orgNamePairs = await Promise.all(
Array.from(orgIds).map(async (oid) => {
const billing = await getOrgBilling(oid).catch(() => null);
return [oid, billing?.companyName ?? null] as const;
})
);
const orgNameMap: Record<string, string> = {};
for (const t of tenants) {
const oid = t.metadata.labels?.["pieced.ch/zitadel-org-id"];
const company = t.spec?.billing?.companyName;
if (oid && company && !orgNameMap[oid]) {
orgNameMap[oid] = company;
}
for (const [oid, name] of orgNamePairs) {
if (name) orgNameMap[oid] = name;
}
return (