since3-portal/modules/shared/actions/invoices.ts
2026-09-29 13:46:50 +02:00

95 lines
3.3 KiB
TypeScript

"use server";
import * as z from "zod";
import { revalidatePath } from "next/cache";
import { createClient } from "@/lib/supabase/server";
import { verifySession } from "@/lib/supabase/dal";
import { getClientId } from "@/modules/shared/queries/profile";
import {
INVOICE_STATUSES,
InvoiceDraftSchema,
firstIssueMessage,
type InvoiceDraft,
type InvoiceStatus,
} from "@/modules/shared/schemas/invoice";
import { toIsoDate } from "@/modules/shared/lib/invoice-math";
export type InvoiceActionState = { error: string } | { success: true };
function revalidateBilling(profileId: string) {
revalidatePath("/client/billing");
revalidatePath(`/admin/clients/${profileId}/billing`);
}
export async function createInvoice(draft: InvoiceDraft): Promise<InvoiceActionState> {
const session = await verifySession();
if (!session.isAdmin) return { error: "Keine Berechtigung." };
const validated = InvoiceDraftSchema.safeParse(draft);
if (!validated.success) return { error: firstIssueMessage(validated.error) };
const data = validated.data;
const clientId = await getClientId(data.profileId);
if (!clientId) return { error: "Client hat noch keine Stammdaten hinterlegt." };
const supabase = await createClient();
const { error } = await supabase.rpc("create_invoice", {
p_client_id: clientId,
p_invoice_date: data.invoiceDate,
p_due_date: data.dueDate,
p_company_name: data.companyName,
p_billing_street: data.billingStreet,
p_billing_postal_code: data.billingPostalCode,
p_billing_city: data.billingCity,
p_billing_country: data.billingCountry.toUpperCase(),
p_items: data.items.map((item) => ({
product_template_id: item.productTemplateId,
description: item.description,
quantity: item.quantity,
unit_price: item.unitPrice,
tax_rate: item.taxRate,
})),
});
if (error) return { error: "Rechnung konnte nicht erstellt werden." };
revalidateBilling(data.profileId);
return { success: true };
}
const StatusSchema = z.object({
profileId: z.string().uuid(),
invoiceId: z.string().uuid(),
status: z.enum(INVOICE_STATUSES),
});
export async function updateInvoiceStatus(
profileId: string,
invoiceId: string,
status: InvoiceStatus
): Promise<InvoiceActionState> {
const session = await verifySession();
if (!session.isAdmin) return { error: "Keine Berechtigung." };
const validated = StatusSchema.safeParse({ profileId, invoiceId, status });
if (!validated.success) return { error: "Ungültiger Status." };
const clientId = await getClientId(validated.data.profileId);
if (!clientId) return { error: "Status konnte nicht geändert werden." };
const supabase = await createClient();
const { data, error } = await supabase
.from("invoices")
.update({
status: validated.data.status,
paid_at: validated.data.status === "paid" ? toIsoDate(new Date()) : null,
})
.eq("id", validated.data.invoiceId)
.eq("client_id", clientId)
.select("id");
if (error || !data?.length) return { error: "Status konnte nicht geändert werden." };
revalidateBilling(validated.data.profileId);
return { success: true };
}