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'; // Validation schema for opening a cash register const openRegisterSchema = z.object({ siteId: z.string().cuid('Invalid site ID'), openingAmount: z.number().nonnegative('Opening amount must be non-negative'), }); // GET /api/cash-register - Get current open register for site or list all for date export async function GET(request: NextRequest) { try { const session = await getServerSession(authOptions); if (!session?.user) { return NextResponse.json( { error: 'Unauthorized' }, { status: 401 } ); } const { searchParams } = new URL(request.url); const siteId = searchParams.get('siteId'); const date = searchParams.get('date'); const status = searchParams.get('status'); // 'open', 'closed', or 'all' // Build the where clause interface RegisterWhereClause { site: { organizationId: string; id?: string; }; closedAt?: null | { not: null } | { gte?: Date; lte?: Date }; openedAt?: { gte?: Date; lte?: Date; }; } const whereClause: RegisterWhereClause = { site: { organizationId: session.user.organizationId, }, }; // Filter by site const effectiveSiteId = siteId || session.user.siteId; if (effectiveSiteId) { // Verify site belongs to user's organization const site = await db.site.findFirst({ where: { id: effectiveSiteId, organizationId: session.user.organizationId, }, }); if (!site) { return NextResponse.json( { error: 'Site not found or does not belong to your organization' }, { status: 404 } ); } whereClause.site.id = effectiveSiteId; } // Filter by status if (status === 'open') { whereClause.closedAt = null; } else if (status === 'closed') { whereClause.closedAt = { not: null }; } // Filter by date if (date) { const dateRegex = /^\d{4}-\d{2}-\d{2}$/; if (dateRegex.test(date)) { const startOfDay = new Date(date); startOfDay.setHours(0, 0, 0, 0); const endOfDay = new Date(date); endOfDay.setHours(23, 59, 59, 999); whereClause.openedAt = { gte: startOfDay, lte: endOfDay, }; } } const registers = await db.cashRegister.findMany({ where: whereClause, include: { site: { select: { id: true, name: true, }, }, user: { select: { id: true, firstName: true, lastName: true, }, }, _count: { select: { sales: true, payments: true, }, }, }, orderBy: { openedAt: 'desc', }, }); // If looking for current open register, return just that one if (status === 'open' && !date) { const openRegister = registers[0] || null; if (openRegister) { // Get payment breakdown for the open register const payments = await db.payment.findMany({ where: { cashRegisterId: openRegister.id, }, select: { amount: true, paymentType: true, }, }); const paymentBreakdown = payments.reduce((acc, payment) => { const type = payment.paymentType; acc[type] = (acc[type] || 0) + Number(payment.amount); return acc; }, {} as Record); const totalCashSales = paymentBreakdown['CASH'] || 0; const expectedAmount = Number(openRegister.openingAmount) + totalCashSales; return NextResponse.json({ ...openRegister, paymentBreakdown, totalCashSales, expectedAmount, }); } return NextResponse.json(null); } return NextResponse.json(registers); } catch (error) { console.error('Error fetching cash registers:', error); return NextResponse.json( { error: 'Failed to fetch cash registers' }, { status: 500 } ); } } // POST /api/cash-register - Open a new cash register export async function POST(request: NextRequest) { try { const session = await getServerSession(authOptions); if (!session?.user) { return NextResponse.json( { error: 'Unauthorized' }, { status: 401 } ); } const body = await request.json(); // Validate input with Zod schema const validationResult = openRegisterSchema.safeParse(body); if (!validationResult.success) { return NextResponse.json( { error: 'Invalid register data', details: validationResult.error.flatten().fieldErrors, }, { status: 400 } ); } const { siteId, openingAmount } = validationResult.data; // Verify site belongs to user's organization const site = await db.site.findFirst({ where: { id: siteId, organizationId: session.user.organizationId, }, }); if (!site) { return NextResponse.json( { error: 'Site not found or does not belong to your organization' }, { status: 404 } ); } // If user is SITE_ADMIN, verify they have access to this site if (session.user.role === 'SITE_ADMIN' && session.user.siteId !== siteId) { return NextResponse.json( { error: 'Forbidden: You do not have access to this site' }, { status: 403 } ); } // Check if there's already an open register for this site const existingOpenRegister = await db.cashRegister.findFirst({ where: { siteId, closedAt: null, }, include: { user: { select: { firstName: true, lastName: true, }, }, }, }); if (existingOpenRegister) { return NextResponse.json( { error: `There is already an open cash register for this site, opened by ${existingOpenRegister.user.firstName} ${existingOpenRegister.user.lastName}`, existingRegisterId: existingOpenRegister.id, }, { status: 409 } ); } // Create new cash register const cashRegister = await db.cashRegister.create({ data: { siteId, userId: session.user.id, openingAmount: new Decimal(openingAmount), }, include: { site: { select: { id: true, name: true, }, }, user: { select: { id: true, firstName: true, lastName: true, }, }, }, }); return NextResponse.json(cashRegister, { status: 201 }); } catch (error) { console.error('Error opening cash register:', error); return NextResponse.json( { error: 'Failed to open cash register' }, { status: 500 } ); } }