import { NextResponse } from "next/server"; import { getSupabaseAdmin } from "@/lib/admin"; import { requireAdmin } from "@/lib/apiAuth"; import { sendEmail } from "@/lib/email"; import { invoiceEmail } from "@/lib/email-templates"; import type { Invoice } from "@/types/billing"; export async function GET(request: Request) { const authResult = await requireAdmin(request); if (authResult instanceof NextResponse) return authResult; const supabase = getSupabaseAdmin(); const { searchParams } = new URL(request.url); const page = parseInt(searchParams.get("page") || "1"); const limit = parseInt(searchParams.get("limit") || "20"); const orgId = searchParams.get("orgId"); const status = searchParams.get("status"); const offset = (page - 1) * limit; try { let query = supabase .from("invoices") .select( "*, organization:organizations(id, name, billing_email), creator:users!invoices_created_by_fkey(id, email, name)", { count: "exact" } ) .order("created_at", { ascending: false }) .range(offset, offset + limit - 1); if (orgId) query = query.eq("organization_id", orgId); if (status) query = query.eq("status", status); const { data, count, error } = await query; if (error) throw error; const invoices = (data || []) as unknown as Invoice[]; return NextResponse.json({ invoices, pagination: { page, limit, total: count || 0, pages: Math.ceil((count || 0) / limit) }, }); } catch (error) { console.error("Error fetching invoices:", error); return NextResponse.json({ error: "Failed to fetch invoices" }, { status: 500 }); } } export async function POST(request: Request) { const authResult = await requireAdmin(request); if (authResult instanceof NextResponse) return authResult; const supabase = getSupabaseAdmin(); try { const body = await request.json(); const { organization_id, amount, currency, items, due_date, notes, status: invoiceStatus } = body; if (!organization_id || amount === undefined || !items?.length) { return NextResponse.json( { error: "organization_id, amount, and items are required" }, { status: 400 } ); } // Generate invoice number: INV-YYYYMM-XXXX const now = new Date(); const prefix = `INV-${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, "0")}`; const { count } = await supabase .from("invoices") .select("id", { count: "exact", head: true }) .like("invoice_number", `${prefix}%`); const seq = String((count || 0) + 1).padStart(4, "0"); const { data, error } = await supabase .from("invoices") .insert({ invoice_number: `${prefix}-${seq}`, organization_id, amount: Math.round(amount), currency: currency || "EUR", status: invoiceStatus || "draft", items, due_date: due_date || null, notes, created_by: authResult.userId, }) .select("*, organization:organizations(id, name, billing_email)") .single(); if (error) throw error; const invoice = data as unknown as Invoice; return NextResponse.json({ invoice }); } catch (error) { console.error("Error creating invoice:", error); return NextResponse.json({ error: "Failed to create invoice" }, { status: 500 }); } } export async function PATCH(request: Request) { const authResult = await requireAdmin(request); if (authResult instanceof NextResponse) return authResult; const supabase = getSupabaseAdmin(); try { const body = await request.json(); const { id, action, ...updates } = body; if (!id) { return NextResponse.json({ error: "Invoice id is required" }, { status: 400 }); } // Send invoice via email if (action === "send") { const { data } = await supabase .from("invoices") .select("*, organization:organizations(id, name, billing_email)") .eq("id", id) .single(); const invoice = data as unknown as Invoice | null; if (!invoice) { return NextResponse.json({ error: "Invoice not found" }, { status: 404 }); } const org = invoice.organization; const recipientEmail = org?.billing_email; if (!recipientEmail) { return NextResponse.json( { error: "Organization has no billing email configured" }, { status: 400 } ); } const appUrl = process.env.NEXT_PUBLIC_APP_URL || "http://localhost:3000"; const template = invoiceEmail({ organizationName: org?.name || "Unknown", invoiceNumber: invoice.invoice_number, amount: invoice.amount, currency: invoice.currency, dueDate: invoice.due_date, items: (invoice.items) || [], dashboardUrl: `${appUrl}/dashboard/settings`, }); const sent = await sendEmail({ to: recipientEmail, subject: template.subject, html: template.html, text: template.text, }); if (!sent) { return NextResponse.json({ error: "Failed to send email" }, { status: 500 }); } await supabase .from("invoices") .update({ status: "sent", updated_at: new Date().toISOString() }) .eq("id", id); return NextResponse.json({ success: true, message: "Invoice sent" }); } // Mark as paid if (action === "mark_paid") { const { data, error } = await supabase .from("invoices") .update({ status: "paid", paid_at: new Date().toISOString(), updated_at: new Date().toISOString() }) .eq("id", id) .select() .single(); if (error) throw error; const invoice = data as unknown as Invoice; return NextResponse.json({ invoice }); } // Generic update const allowedFields = ["status", "amount", "items", "due_date", "notes"]; const safeUpdates: Record = { updated_at: new Date().toISOString() }; for (const key of allowedFields) { if (key in updates) safeUpdates[key] = updates[key]; } const { data, error } = await supabase .from("invoices") .update(safeUpdates) .eq("id", id) .select() .single(); if (error) throw error; const invoice = data as unknown as Invoice; return NextResponse.json({ invoice }); } catch (error) { console.error("Error updating invoice:", error); return NextResponse.json({ error: "Failed to update invoice" }, { status: 500 }); } }