export const dynamic = 'force-dynamic' import { NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth/get-session' import { withUser } from '@/lib/db/with-user' import { writeAuditLog } from '@/lib/db/audit' import { incidents, sites, zones, users, doshReports } from '@/lib/db/schema' import { buildJkkp8Rows, jkkp8Csv, type Jkkp8Incident } from '@/lib/reports/jkkp8' import { aliasedTable, eq, and, gte, lt, asc, inArray } from 'drizzle-orm' export async function GET(request: NextRequest) { const session = await getSession() if (!session) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) if (!['hse', 'admin'].includes(session.role)) return NextResponse.json({ error: 'Forbidden' }, { status: 403 }) const yearParam = request.nextUrl.searchParams.get('year') const year = Number.parseInt(yearParam ?? '', 10) || new Date().getFullYear() const reporterAlias = aliasedTable(users, 'reporter') const incidentRows = await withUser(session.sub, async tx => tx.select({ referenceNo: incidents.referenceNo, incidentType: incidents.incidentType, description: incidents.description, reportedAt: incidents.reportedAt, medicalStatus: incidents.medicalStatus, lostDays: incidents.lostDays, isFatality: incidents.isFatality, isSeriousBodilyInjury: incidents.isSeriousBodilyInjury, isDangerousOccurrence: incidents.isDangerousOccurrence, isOccupationalDisease: incidents.isOccupationalDisease, incidentId: incidents.id, siteName: sites.name, zoneName: zones.name, reporterName: reporterAlias.name, }) .from(incidents) .leftJoin(sites, eq(incidents.siteId, sites.id)) .leftJoin(zones, eq(incidents.zoneId, zones.id)) .leftJoin(reporterAlias, eq(incidents.reportedBy, reporterAlias.id)) .where(and( gte(incidents.reportedAt, new Date(`${year}-01-01T00:00:00Z`)), lt(incidents.reportedAt, new Date(`${year + 1}-01-01T00:00:00Z`)), )) .orderBy(asc(incidents.reportedAt)) ) // Fetch dosh_reports for all incidents in a single query const incidentIds = incidentRows.map(r => r.incidentId) const doshRows = incidentIds.length > 0 ? await withUser(session.sub, async tx => tx.select({ incidentId: doshReports.incidentId, formType: doshReports.formType, status: doshReports.status, submittedAt: doshReports.submittedAt, }) .from(doshReports) .where(inArray(doshReports.incidentId, incidentIds)) ) : [] // Group dosh rows by incidentId const doshByIncident = new Map>() for (const d of doshRows) { const existing = doshByIncident.get(d.incidentId) ?? [] existing.push({ form_type: d.formType, status: d.status, submitted_at: d.submittedAt ? d.submittedAt.toISOString() : null, }) doshByIncident.set(d.incidentId, existing) } // Map Drizzle camelCase rows to Jkkp8Incident snake_case shape const jkkp8Incidents: Jkkp8Incident[] = incidentRows.map(r => ({ reference_no: r.referenceNo, incident_type: r.incidentType, description: r.description ?? '', reported_at: r.reportedAt ? r.reportedAt.toISOString() : '', medical_status: r.medicalStatus, lost_days: r.lostDays, is_fatality: r.isFatality ?? false, is_serious_bodily_injury: r.isSeriousBodilyInjury ?? false, is_dangerous_occurrence: r.isDangerousOccurrence ?? false, is_occupational_disease: r.isOccupationalDisease ?? false, sites: r.siteName ? { name: r.siteName } : null, zones: r.zoneName ? { name: r.zoneName } : null, reporter: r.reporterName ? { name: r.reporterName } : null, dosh_reports: doshByIncident.get(r.incidentId) ?? [], })) const rows = buildJkkp8Rows(jkkp8Incidents) const csv = jkkp8Csv(rows) await withUser(session.sub, async tx => writeAuditLog(tx, 'incidents', session.sub, 'jkkp8_register_export', { year, row_count: rows.length }) ) return new NextResponse(csv, { status: 200, headers: { 'Content-Type': 'text/csv; charset=utf-8', 'Content-Disposition': `attachment; filename="jkkp8-register-${year}.csv"`, }, }) }