import { NextRequest, NextResponse } from 'next/server'; import { getServerSession } from 'next-auth'; import { authOptions } from '@/lib/auth'; import { db } from '@/lib/db'; import { z } from 'zod'; import { Decimal } from '@prisma/client/runtime/library'; interface RouteContext { params: Promise<{ id: string }>; } // Validation schema for closing a cash register const closeRegisterSchema = z.object({ closingAmount: z.number().nonnegative('Closing amount must be non-negative'), notes: z.string().max(500).optional(), }); // GET /api/cash-register/[id] - Get register details with transactions and summary export async function GET( request: NextRequest, context: RouteContext ) { try { const session = await getServerSession(authOptions); if (!session?.user) { return NextResponse.json( { error: 'Unauthorized' }, { status: 401 } ); } const { id } = await context.params; const cashRegister = await db.cashRegister.findFirst({ where: { id, site: { organizationId: session.user.organizationId, }, }, include: { site: { select: { id: true, name: true, address: true, }, }, user: { select: { id: true, firstName: true, lastName: true, email: true, }, }, sales: { include: { items: { include: { product: { select: { id: true, name: true, }, }, }, }, client: { select: { id: true, firstName: true, lastName: true, }, }, }, orderBy: { createdAt: 'desc', }, }, payments: { select: { id: true, amount: true, paymentType: true, reference: true, notes: true, createdAt: true, booking: { select: { id: true, }, }, sale: { select: { id: true, }, }, }, orderBy: { createdAt: 'desc', }, }, }, }); if (!cashRegister) { return NextResponse.json( { error: 'Cash register not found' }, { status: 404 } ); } // Calculate payment breakdown by method const paymentBreakdown = cashRegister.payments.reduce((acc, payment) => { const type = payment.paymentType; acc[type] = (acc[type] || 0) + Number(payment.amount); return acc; }, {} as Record); // Calculate totals const totalCashSales = paymentBreakdown['CASH'] || 0; const totalCardSales = paymentBreakdown['CARD'] || 0; const totalTransferSales = paymentBreakdown['TRANSFER'] || 0; const totalMembershipSales = paymentBreakdown['MEMBERSHIP'] || 0; const totalFreeSales = paymentBreakdown['FREE'] || 0; const totalAllPayments = Object.values(paymentBreakdown).reduce((sum, val) => sum + val, 0); const expectedCashAmount = Number(cashRegister.openingAmount) + totalCashSales; // Calculate difference if register is closed let actualDifference = null; if (cashRegister.closedAt && cashRegister.closingAmount !== null) { actualDifference = Number(cashRegister.closingAmount) - expectedCashAmount; } return NextResponse.json({ ...cashRegister, summary: { paymentBreakdown, totalCashSales, totalCardSales, totalTransferSales, totalMembershipSales, totalFreeSales, totalAllPayments, openingAmount: Number(cashRegister.openingAmount), expectedCashAmount, closingAmount: cashRegister.closingAmount ? Number(cashRegister.closingAmount) : null, difference: actualDifference, salesCount: cashRegister.sales.length, transactionsCount: cashRegister.payments.length, }, }); } catch (error) { console.error('Error fetching cash register:', error); return NextResponse.json( { error: 'Failed to fetch cash register' }, { status: 500 } ); } } // PUT /api/cash-register/[id] - Close a cash register export async function PUT( request: NextRequest, context: RouteContext ) { try { const session = await getServerSession(authOptions); if (!session?.user) { return NextResponse.json( { error: 'Unauthorized' }, { status: 401 } ); } const { id } = await context.params; // Fetch the cash register const existingRegister = await db.cashRegister.findFirst({ where: { id, site: { organizationId: session.user.organizationId, }, }, }); if (!existingRegister) { return NextResponse.json( { error: 'Cash register not found' }, { status: 404 } ); } // Check if already closed if (existingRegister.closedAt) { return NextResponse.json( { error: 'Cash register is already closed' }, { status: 400 } ); } // If user is SITE_ADMIN, verify they have access to this site if (session.user.role === 'SITE_ADMIN' && session.user.siteId !== existingRegister.siteId) { return NextResponse.json( { error: 'Forbidden: You do not have access to this cash register' }, { status: 403 } ); } const body = await request.json(); // Validate input with Zod schema const validationResult = closeRegisterSchema.safeParse(body); if (!validationResult.success) { return NextResponse.json( { error: 'Invalid close register data', details: validationResult.error.flatten().fieldErrors, }, { status: 400 } ); } const { closingAmount, notes } = validationResult.data; // Calculate expected amount from cash payments const cashPayments = await db.payment.aggregate({ where: { cashRegisterId: id, paymentType: 'CASH', }, _sum: { amount: true, }, }); const totalCashPayments = cashPayments._sum.amount ? Number(cashPayments._sum.amount) : 0; const expectedAmount = Number(existingRegister.openingAmount) + totalCashPayments; const difference = closingAmount - expectedAmount; // Close the cash register const cashRegister = await db.cashRegister.update({ where: { id }, data: { closedAt: new Date(), closingAmount: new Decimal(closingAmount), expectedAmount: new Decimal(expectedAmount), difference: new Decimal(difference), notes: notes || null, }, include: { site: { select: { id: true, name: true, }, }, user: { select: { id: true, firstName: true, lastName: true, }, }, }, }); // Get payment breakdown for the response const allPayments = await db.payment.findMany({ where: { cashRegisterId: id, }, select: { amount: true, paymentType: true, }, }); const paymentBreakdown = allPayments.reduce((acc, payment) => { const type = payment.paymentType; acc[type] = (acc[type] || 0) + Number(payment.amount); return acc; }, {} as Record); return NextResponse.json({ ...cashRegister, summary: { paymentBreakdown, openingAmount: Number(existingRegister.openingAmount), expectedAmount, closingAmount, difference, status: difference === 0 ? 'balanced' : difference > 0 ? 'over' : 'short', }, }); } catch (error) { console.error('Error closing cash register:', error); return NextResponse.json( { error: 'Failed to close cash register' }, { status: 500 } ); } }