import { NextRequest, NextResponse } from "next/server"; import prisma from "@/lib/db"; import { getOrganization, apiError } from "@/lib/api-helpers"; export async function GET( _request: NextRequest, { params }: { params: { customerId: string } } ) { try { const [organization, orgErr] = await getOrganization(); if (orgErr) return orgErr; const customer = await prisma.customer.findFirst({ where: { id: params.customerId, organizationId: organization.id, }, }); if (!customer) { return NextResponse.json({ error: "Customer not found" }, { status: 404 }); } // Fetch invoices and payments in parallel const [invoices, payments] = await Promise.all([ prisma.invoice.findMany({ where: { customerId: customer.id, organizationId: organization.id, }, select: { id: true, invoiceNumber: true, invoiceDate: true, dueDate: true, status: true, totalAmount: true, paidAmount: true, balanceDue: true, currencyCode: true, description: true, }, orderBy: { invoiceDate: "desc" }, take: 50, }), prisma.payment.findMany({ where: { customerId: customer.id, organizationId: organization.id, }, select: { id: true, paymentNumber: true, paymentDate: true, paymentMethod: true, paymentAmount: true, status: true, paymentReference: true, }, orderBy: { paymentDate: "desc" }, take: 20, }), ]); // Compute metrics const totalInvoiced = invoices.reduce((s, i) => s + Number(i.totalAmount), 0); const totalPaid = invoices.reduce((s, i) => s + Number(i.paidAmount), 0); const totalOutstanding = invoices.reduce((s, i) => s + Number(i.balanceDue), 0); const overdueInvoices = invoices.filter( (i) => i.status !== "PAID" && i.status !== "VOID" && i.dueDate && new Date(i.dueDate) < new Date() ); const overdueAmount = overdueInvoices.reduce((s, i) => s + Number(i.balanceDue), 0); // Aging const now = new Date(); const aging = { current: 0, days30: 0, days60: 0, days90: 0, over90: 0 }; invoices .filter((i) => Number(i.balanceDue) > 0 && i.status !== "PAID" && i.status !== "VOID") .forEach((i) => { if (!i.dueDate) return; const daysPast = Math.floor((now.getTime() - new Date(i.dueDate).getTime()) / 86400000); const amt = Number(i.balanceDue); if (daysPast <= 0) aging.current += amt; else if (daysPast <= 30) aging.days30 += amt; else if (daysPast <= 60) aging.days60 += amt; else if (daysPast <= 90) aging.days90 += amt; else aging.over90 += amt; }); // Credit utilization const creditLimit = customer.creditLimit ? Number(customer.creditLimit) : null; const creditUtilization = creditLimit ? (totalOutstanding / creditLimit) * 100 : null; return NextResponse.json({ customer: { id: customer.id, customerCode: customer.customerCode, customerName: customer.customerName, customerType: customer.customerType, contactPerson: customer.contactPerson, email: customer.email, phone: customer.phone, website: customer.website, street1: customer.street1, street2: customer.street2, city: customer.city, stateProvince: customer.stateProvince, postalCode: customer.postalCode, country: customer.country, taxId: customer.taxId, paymentTermDays: customer.paymentTermDays, creditLimit, status: customer.status, createdAt: customer.createdAt, }, metrics: { totalInvoiced, totalPaid, totalOutstanding, overdueAmount, overdueCount: overdueInvoices.length, invoiceCount: invoices.length, paymentCount: payments.length, creditLimit, creditUtilization, aging, }, invoices: invoices.map((i) => ({ ...i, totalAmount: Number(i.totalAmount), paidAmount: Number(i.paidAmount), balanceDue: Number(i.balanceDue), })), payments: payments.map((p) => ({ id: p.id, paymentNumber: p.paymentNumber, paymentDate: p.paymentDate, paymentMethod: p.paymentMethod, amount: Number(p.paymentAmount), status: p.status, referenceNumber: p.paymentReference, })), }); } catch (error) { return apiError("Failed to fetch customer details", error); } }