export const dynamic = 'force-dynamic' import { NextRequest, NextResponse } from 'next/server' import { aliasedTable, eq } from 'drizzle-orm' import { getSession } from '@/lib/auth/get-session' import { withUser } from '@/lib/db/with-user' import { incidents, sites, zones, users, investigations, capaActions, incidentAddenda } from '@/lib/db/schema' import { buildIncidentReportPdf } from '@/lib/pdf/incident-report' export async function GET( _req: NextRequest, { params }: { params: Promise<{ id: string }> } ) { const { id } = await params 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 reporterAlias = aliasedTable(users, 'reporter') const triagedByAlias = aliasedTable(users, 'triaged_by_user') const ownerAlias = aliasedTable(users, 'capa_owner') const addendaAuthorAlias = aliasedTable(users, 'addenda_author') const [incidentRows, investigationRows, capaRows, addendaRows] = await withUser( session.sub, async tx => Promise.all([ tx.select({ id: incidents.id, referenceNo: incidents.referenceNo, incidentType: incidents.incidentType, reportedAt: incidents.reportedAt, status: incidents.status, siteName: sites.name, zoneName: zones.name, reporterName: reporterAlias.name, severity: incidents.severity, medicalStatus: incidents.medicalStatus, description: incidents.description, injuryInvolved: incidents.injuryInvolved, assetInvolved: incidents.assetInvolved, isFatality: incidents.isFatality, isSeriousBodilyInjury: incidents.isSeriousBodilyInjury, isDangerousOccurrence: incidents.isDangerousOccurrence, isOccupationalDisease: incidents.isOccupationalDisease, lostDays: incidents.lostDays, triagedByName: triagedByAlias.name, triagedAt: incidents.triagedAt, triageNotes: incidents.triageNotes, closedAt: incidents.closedAt, }) .from(incidents) .leftJoin(sites, eq(incidents.siteId, sites.id)) .leftJoin(zones, eq(incidents.zoneId, zones.id)) .leftJoin(reporterAlias, eq(incidents.reportedBy, reporterAlias.id)) .leftJoin(triagedByAlias, eq(incidents.triagedBy, triagedByAlias.id)) .where(eq(incidents.id, id)) .limit(1), tx.select({ method: investigations.method, findingsText: investigations.findingsText, rootCauseSummary: investigations.rootCauseSummary, alcoholTestResult: investigations.alcoholTestResult, urineTestResult: investigations.urineTestResult, }) .from(investigations) .where(eq(investigations.incidentId, id)) .limit(1), tx.select({ description: capaActions.description, ownerName: ownerAlias.name, department: capaActions.department, dueDate: capaActions.dueDate, priority: capaActions.priority, status: capaActions.status, completedAt: capaActions.completedAt, ownerNotes: capaActions.ownerNotes, }) .from(capaActions) .leftJoin(ownerAlias, eq(capaActions.ownerUserId, ownerAlias.id)) .where(eq(capaActions.incidentId, id)), tx.select({ body: incidentAddenda.body, createdAt: incidentAddenda.createdAt, authorName: addendaAuthorAlias.name, }) .from(incidentAddenda) .leftJoin(addendaAuthorAlias, eq(incidentAddenda.author, addendaAuthorAlias.id)) .where(eq(incidentAddenda.incidentId, id)), ]) ) const incident = incidentRows[0] if (!incident) return NextResponse.json({ error: 'Not found' }, { status: 404 }) if (incident.status !== 'closed') return NextResponse.json({ error: 'Incident is not closed' }, { status: 400 }) const inv = investigationRows[0] ?? null const pdfBytes = await buildIncidentReportPdf({ referenceNo: incident.referenceNo, incidentType: incident.incidentType, reportedAt: incident.reportedAt instanceof Date ? incident.reportedAt.toISOString() : (incident.reportedAt as string), siteName: incident.siteName ?? 'Unknown', zoneName: incident.zoneName ?? null, reporterName: incident.reporterName ?? 'Unknown', severity: incident.severity ?? null, medicalStatus: incident.medicalStatus ?? null, description: incident.description, injuryInvolved: incident.injuryInvolved, assetInvolved: incident.assetInvolved, isFatality: incident.isFatality, isSeriousBodilyInjury: incident.isSeriousBodilyInjury, isDangerousOccurrence: incident.isDangerousOccurrence, isOccupationalDisease: incident.isOccupationalDisease, lostDays: incident.lostDays ?? null, triagedBy: incident.triagedByName ?? null, triagedAt: incident.triagedAt instanceof Date ? incident.triagedAt.toISOString() : (incident.triagedAt as string | null), triageNotes: incident.triageNotes ?? null, investigation: inv ? { method: inv.method, findingsText: inv.findingsText ?? null, rootCauseSummary: inv.rootCauseSummary ?? null, alcoholTestResult: inv.alcoholTestResult ?? null, urineTestResult: inv.urineTestResult ?? null, } : null, capas: capaRows.map(c => ({ description: c.description, ownerName: c.ownerName ?? 'Unknown', department: c.department, dueDate: c.dueDate, priority: c.priority, status: c.status, completedAt: c.completedAt instanceof Date ? c.completedAt.toISOString() : (c.completedAt as string | null), ownerNotes: c.ownerNotes ?? null, })), addenda: addendaRows.map(a => ({ authorName: a.authorName ?? 'Unknown', createdAt: a.createdAt instanceof Date ? a.createdAt.toISOString() : (a.createdAt as string), body: a.body, })), closedAt: incident.closedAt instanceof Date ? incident.closedAt.toISOString() : (incident.closedAt as string | null), }) const filename = `incident-report-${incident.referenceNo ?? id}.pdf` return new NextResponse(Buffer.from(pdfBytes), { status: 200, headers: { 'Content-Type': 'application/pdf', 'Content-Disposition': `attachment; filename="${filename}"`, 'Content-Length': String(pdfBytes.byteLength), }, }) }