Files
beenvoice-app/lib/invoice-pdf-input.ts
T
soconnorandCursor 6762a9bff3 Move production to beenvoice.app with migrated accounts, refreshed auth and timer UX, and expanded invoice flows.
Official URL migration preserves sessions, shortcuts prefs, and last clock-in client; auth screens match web with legal links; time clock and invoice editor/send flows are updated for the new domain and UI patterns.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-26 03:40:48 -04:00

81 lines
2.5 KiB
TypeScript

import type { AppRouter } from "beenvoice/server/api/root";
import type { inferRouterInputs, inferRouterOutputs } from "@trpc/server";
export type InvoicePdfPreviewInput = inferRouterInputs<AppRouter>["invoices"]["previewPdf"];
type InvoiceDetail = NonNullable<inferRouterOutputs<AppRouter>["invoices"]["getById"]>;
export type InvoicePdfFormFields = {
invoiceNumber: string;
invoicePrefix?: string | null;
businessId?: string | null;
clientId: string;
issueDate: Date;
dueDate: Date;
status?: "draft" | "sent" | "paid";
notes?: string | null;
emailMessage?: string | null;
taxRate: number;
currency: string;
items: Array<{
date: Date;
description: string;
hours: number | string;
rate: number | string;
}>;
};
export function buildPreviewPdfInput(fields: InvoicePdfFormFields): InvoicePdfPreviewInput | null {
if (!fields.clientId.trim()) return null;
const items = fields.items.map((item) => ({
date: item.date,
description: item.description.trim() || "Service",
hours: Number(item.hours) || 0,
rate: Number(item.rate) || 0,
}));
if (items.length === 0) return null;
return {
invoiceNumber: fields.invoiceNumber.trim() || "DRAFT",
invoicePrefix: fields.invoicePrefix?.trim() || "#",
businessId: fields.businessId?.trim() || "",
clientId: fields.clientId,
issueDate: fields.issueDate,
dueDate: fields.dueDate,
status: fields.status ?? "draft",
notes: fields.notes?.trim() ?? "",
emailMessage: fields.emailMessage?.trim() ?? "",
taxRate: fields.taxRate,
currency: fields.currency,
items,
};
}
export function buildPreviewPdfInputFromInvoice(invoice: InvoiceDetail): InvoicePdfPreviewInput {
return {
invoiceNumber: invoice.invoiceNumber,
invoicePrefix: invoice.invoicePrefix ?? "#",
businessId: invoice.businessId ?? invoice.business?.id ?? "",
clientId: invoice.clientId,
issueDate: new Date(invoice.issueDate),
dueDate: new Date(invoice.dueDate),
status: invoice.status as "draft" | "sent" | "paid",
notes: invoice.notes ?? "",
emailMessage: invoice.emailMessage ?? "",
taxRate: invoice.taxRate,
currency: invoice.currency ?? "USD",
items: invoice.items.map((item) => ({
date: new Date(item.date),
description: item.description,
hours: item.hours,
rate: item.rate,
})),
};
}
export function canPreviewPdfInput(input: InvoicePdfPreviewInput | null): input is InvoicePdfPreviewInput {
if (!input?.clientId) return false;
return input.items.every((item) => item.description.trim().length > 0);
}