import { NextRequest, NextResponse } from 'next/server'; import { db, dbReady } from '@/db'; import { employees, timeEntries, companies } from '@/db/schema'; import { eq, and, gte, lte } from 'drizzle-orm'; import { calculateDayMinutes, formatHours } from '@/lib/calculations'; import { format, addDays } from 'date-fns'; import { ok, err } from '@/lib/api-response'; import { parseId } from '@/lib/params'; export async function GET(request: NextRequest) { const { searchParams } = new URL(request.url); const companyId = parseId(searchParams.get('companyId')); const startDateStr = searchParams.get('startDate'); const endDateStr = searchParams.get('endDate'); if (companyId == null) { return NextResponse.json(err('valid companyId is required'), { status: 400 }); } if (!startDateStr || !endDateStr) { return NextResponse.json(err('startDate and endDate are required'), { status: 400 }); } const startDate = new Date(startDateStr + 'T00:00:00'); const endDate = new Date(endDateStr + 'T23:59:59'); if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) { return NextResponse.json(err('Invalid date format'), { status: 400 }); } const startUnix = Math.floor(startDate.getTime() / 1000); const endUnix = Math.floor(endDate.getTime() / 1000); const contextStartUnix = startUnix - 36 * 3600; const contextEndUnix = endUnix + 36 * 3600; await dbReady; const [company, companyEmployees, allEntries] = await Promise.all([ db.select().from(companies).where(eq(companies.id, companyId)).get(), db .select() .from(employees) .where(and(eq(employees.companyId, companyId), eq(employees.isActive, true))) .all(), db .select({ id: timeEntries.id, employeeId: timeEntries.employeeId, type: timeEntries.type, timestamp: timeEntries.timestamp, isDeleted: timeEntries.isDeleted, }) .from(timeEntries) .where( and( eq(timeEntries.companyId, companyId), eq(timeEntries.isDeleted, false), gte(timeEntries.timestamp, contextStartUnix), lte(timeEntries.timestamp, contextEndUnix), ), ) .orderBy(timeEntries.timestamp) .all(), ]); if (!company) { return NextResponse.json(err('Company not found'), { status: 404 }); } const days: string[] = []; let dayDate = startDate; while (dayDate <= endDate) { days.push(format(dayDate, 'yyyy-MM-dd')); dayDate = addDays(dayDate, 1); } const resultEmployees = companyEmployees.map((emp) => { const empEntries = allEntries.filter((e) => e.employeeId === emp.id); const empDays = days.map((dayStr) => { const dayStart = new Date(dayStr + 'T00:00:00').getTime() / 1000; const dayEnd = new Date(dayStr + 'T23:59:59').getTime() / 1000; const dayEntries = empEntries.filter((e) => e.timestamp >= dayStart && e.timestamp <= dayEnd); // Single call , errors are returned directly const { clockMinutes, breakMinutes, workedMinutes, hasErrors, errors } = calculateDayMinutes(empEntries, dayStr); const inEntry = dayEntries.find((e) => e.type === 'IN' && !e.isDeleted); const outEntry = dayEntries.find((e) => e.type === 'OUT' && !e.isDeleted); return { date: dayStr, inTime: inEntry ? format(new Date(inEntry.timestamp * 1000), 'h:mm a') : '', outTime: outEntry ? format(new Date(outEntry.timestamp * 1000), 'h:mm a') : '', breakMinutes: Math.round(breakMinutes), clockMinutes: Math.round(clockMinutes), workedMinutes: Math.round(workedMinutes), hasErrors, errors, }; }); const totals = empDays.reduce( (acc, day) => ({ clockMinutes: acc.clockMinutes + day.clockMinutes, breakMinutes: acc.breakMinutes + day.breakMinutes, workedMinutes: acc.workedMinutes + day.workedMinutes, }), { clockMinutes: 0, breakMinutes: 0, workedMinutes: 0 }, ); return { id: emp.id, name: emp.name, days: empDays, totals, }; }); let summary = 'Employee\tDate\tIn\tOut\tBreak\tNet\tNotes\n'; for (const emp of resultEmployees) { for (const day of emp.days) { const hasErrors = day.errors.length > 0; summary += `${emp.name}\t${day.date}\t${day.inTime}\t${day.outTime}\t${formatHours(day.breakMinutes)}\t${formatHours(day.workedMinutes)}\t${hasErrors ? '[WARN] ' + day.errors.join('; ') : ''}\n`; } summary += `${emp.name}\tTOTAL\t\t\t${formatHours(emp.totals.breakMinutes)}\t${formatHours(emp.totals.workedMinutes)}\t\n`; } return NextResponse.json( ok({ company: company.name, period: { startDate: startDateStr, endDate: endDateStr }, employees: resultEmployees, summary, }), ); }