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 manual transaction const manualTransactionSchema = z.object({ type: z.enum(['DEPOSIT', 'WITHDRAWAL']), amount: z.number().positive('Amount must be positive'), reason: z.string().min(1, 'Reason is required').max(500), }); // GET /api/cash-register/[id]/transactions - List all transactions for this register 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; // Verify the cash register exists and belongs to user's organization const cashRegister = await db.cashRegister.findFirst({ where: { id, site: { organizationId: session.user.organizationId, }, }, }); if (!cashRegister) { return NextResponse.json( { error: 'Cash register not found' }, { status: 404 } ); } // Get all payments (transactions) for this register const transactions = await db.payment.findMany({ where: { cashRegisterId: id, }, include: { booking: { select: { id: true, startTime: true, endTime: true, court: { select: { name: true, }, }, client: { select: { firstName: true, lastName: true, }, }, }, }, sale: { select: { id: true, total: true, items: { select: { product: { select: { name: true, }, }, quantity: true, }, }, }, }, client: { select: { id: true, firstName: true, lastName: true, }, }, }, orderBy: { createdAt: 'desc', }, }); // Categorize transactions const categorizedTransactions = transactions.map(transaction => { let category: string; let description: string; if (transaction.booking) { category = 'BOOKING'; const client = transaction.booking.client; description = `Booking payment${client ? ` - ${client.firstName} ${client.lastName}` : ''} - ${transaction.booking.court?.name || 'Unknown court'}`; } else if (transaction.sale) { category = 'SALE'; const itemCount = transaction.sale.items.reduce((sum, item) => sum + item.quantity, 0); description = `Sale payment - ${itemCount} item(s)`; } else if (transaction.notes?.startsWith('DEPOSIT:') || transaction.notes?.startsWith('WITHDRAWAL:')) { category = transaction.notes.startsWith('DEPOSIT:') ? 'DEPOSIT' : 'WITHDRAWAL'; description = transaction.notes.substring(transaction.notes.indexOf(':') + 1).trim(); } else { category = 'OTHER'; description = transaction.notes || 'Unknown transaction'; } return { id: transaction.id, amount: transaction.amount, paymentType: transaction.paymentType, reference: transaction.reference, category, description, createdAt: transaction.createdAt, bookingId: transaction.booking?.id || null, saleId: transaction.sale?.id || null, client: transaction.client, }; }); // Calculate summary const summary = { totalDeposits: categorizedTransactions .filter(t => t.category === 'DEPOSIT') .reduce((sum, t) => sum + Number(t.amount), 0), totalWithdrawals: categorizedTransactions .filter(t => t.category === 'WITHDRAWAL') .reduce((sum, t) => sum + Number(t.amount), 0), totalBookingPayments: categorizedTransactions .filter(t => t.category === 'BOOKING') .reduce((sum, t) => sum + Number(t.amount), 0), totalSalePayments: categorizedTransactions .filter(t => t.category === 'SALE') .reduce((sum, t) => sum + Number(t.amount), 0), totalOther: categorizedTransactions .filter(t => t.category === 'OTHER') .reduce((sum, t) => sum + Number(t.amount), 0), transactionCount: categorizedTransactions.length, }; return NextResponse.json({ transactions: categorizedTransactions, summary, }); } catch (error) { console.error('Error fetching transactions:', error); return NextResponse.json( { error: 'Failed to fetch transactions' }, { status: 500 } ); } } // POST /api/cash-register/[id]/transactions - Add manual transaction (deposit/withdrawal) export async function POST( request: NextRequest, context: RouteContext ) { try { const session = await getServerSession(authOptions); if (!session?.user) { return NextResponse.json( { error: 'Unauthorized' }, { status: 401 } ); } // Check if user has appropriate role const allowedRoles = ['SUPER_ADMIN', 'ORG_ADMIN', 'SITE_ADMIN', 'RECEPTIONIST']; if (!allowedRoles.includes(session.user.role)) { return NextResponse.json( { error: 'Forbidden: Insufficient permissions' }, { status: 403 } ); } const { id } = await context.params; // Verify the cash register exists and belongs to user's organization const cashRegister = await db.cashRegister.findFirst({ where: { id, site: { organizationId: session.user.organizationId, }, }, }); if (!cashRegister) { return NextResponse.json( { error: 'Cash register not found' }, { status: 404 } ); } // Check if register is already closed if (cashRegister.closedAt) { return NextResponse.json( { error: 'Cannot add transactions to a closed cash register' }, { status: 400 } ); } // If user is SITE_ADMIN or RECEPTIONIST, verify they have access to this site if (['SITE_ADMIN', 'RECEPTIONIST'].includes(session.user.role) && session.user.siteId !== cashRegister.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 = manualTransactionSchema.safeParse(body); if (!validationResult.success) { return NextResponse.json( { error: 'Invalid transaction data', details: validationResult.error.flatten().fieldErrors, }, { status: 400 } ); } const { type, amount, reason } = validationResult.data; // For withdrawals, verify there's enough cash in the register if (type === 'WITHDRAWAL') { // Get current cash balance 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 currentCashBalance = Number(cashRegister.openingAmount) + totalCashPayments; if (amount > currentCashBalance) { return NextResponse.json( { error: `Insufficient cash balance. Available: ${currentCashBalance.toFixed(2)}, Requested: ${amount.toFixed(2)}` }, { status: 400 } ); } } // Create the payment record // For deposits, amount is positive; for withdrawals, amount is negative in the payment record const paymentAmount = type === 'WITHDRAWAL' ? -amount : amount; const transaction = await db.payment.create({ data: { amount: new Decimal(paymentAmount), paymentType: 'CASH', notes: `${type}: ${reason}`, cashRegisterId: id, }, }); return NextResponse.json({ id: transaction.id, type, amount: Math.abs(Number(transaction.amount)), reason, createdAt: transaction.createdAt, message: `${type.toLowerCase()} of ${amount.toFixed(2)} recorded successfully`, }, { status: 201 }); } catch (error) { console.error('Error creating transaction:', error); return NextResponse.json( { error: 'Failed to create transaction' }, { status: 500 } ); } }