since3-portal/modules/shared/queries/invoices.ts

93 lines
3.6 KiB
TypeScript

import "server-only"
import { createClient } from "@/lib/supabase/server"
import type { InvoiceListItem, InvoicePdfData, ProductTemplate } from "@/modules/shared/schemas/invoice"
export async function getInvoices(clientId: string): Promise<InvoiceListItem[]> {
const supabase = await createClient()
// Versandinfo direkt aus invoices: die View expandiert i.* nur zum Anlegezeitpunkt und kennt die Spalten nicht.
const [{ data, error }, { data: mailRows, error: mailError }] = await Promise.all([
supabase
.from("invoices_overview")
.select("id, invoice_number, status, display_status, gross_total, due_date, invoice_date")
.eq("client_id", clientId)
.order("invoice_date", { ascending: false })
.order("invoice_number", { ascending: false }),
supabase.from("invoices").select("id, email_sent_at, email_sent_to").eq("client_id", clientId),
])
if (error || mailError) throw new Error("Rechnungen konnten nicht geladen werden.")
const mailInfo = new Map(mailRows.map((row) => [row.id, row]))
return data.map((row) => ({
id: row.id,
invoiceNumber: row.invoice_number,
status: row.status,
displayStatus: row.display_status,
grossTotal: Number(row.gross_total),
dueDate: row.due_date,
invoiceDate: row.invoice_date,
emailSentAt: mailInfo.get(row.id)?.email_sent_at ?? null,
emailSentTo: mailInfo.get(row.id)?.email_sent_to ?? null,
}))
}
export async function getProductTemplates(): Promise<ProductTemplate[]> {
const supabase = await createClient()
const { data, error } = await supabase
.from("product_templates")
.select("id, name, description, unit_price, tax_rate")
.eq("is_active", true)
.order("name")
if (error) throw new Error("Produktvorlagen konnten nicht geladen werden.")
return data.map((row) => ({
id: row.id,
name: row.name,
description: row.description,
unitPrice: Number(row.unit_price),
taxRate: Number(row.tax_rate),
}))
}
export async function peekNextInvoiceNumber(): Promise<string | null> {
const supabase = await createClient()
const { data, error } = await supabase.rpc("peek_next_invoice_number")
if (error || typeof data !== "string") return null
return data
}
export async function getInvoicesForPdf(invoiceIds: string[]): Promise<InvoicePdfData[]> {
const supabase = await createClient()
const { data, error } = await supabase
.from("invoices")
.select(`
invoice_number, invoice_date, due_date, company_name,
billing_street, billing_postal_code, billing_city, billing_country,
invoice_items ( position, description, quantity, unit_price, tax_rate )
`)
.in("id", invoiceIds)
.order("invoice_number")
if (error) throw new Error("Rechnungen konnten nicht geladen werden.")
return data.map((row) => ({
invoiceNumber: row.invoice_number,
invoiceDate: row.invoice_date,
dueDate: row.due_date,
companyName: row.company_name,
billingStreet: row.billing_street,
billingPostalCode: row.billing_postal_code,
billingCity: row.billing_city,
billingCountry: row.billing_country,
items: [...row.invoice_items]
.sort((a, b) => a.position - b.position)
.map((item) => ({
description: item.description,
quantity: Number(item.quantity),
unitPrice: Number(item.unit_price),
taxRate: Number(item.tax_rate),
})),
}))
}