129 lines
4.3 KiB
TypeScript
129 lines
4.3 KiB
TypeScript
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 { capaActions, incidents, users } from '@/lib/db/schema'
|
|
import { aliasedTable, eq, asc, and } from 'drizzle-orm'
|
|
import { createInAppNotifications } from '@/lib/notifications/in-app'
|
|
|
|
export async function GET() {
|
|
const session = await getSession()
|
|
if (!session) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
|
|
const ownerAlias = aliasedTable(users, 'owner')
|
|
|
|
const rows = await withUser(session.sub, async tx => {
|
|
let q = tx
|
|
.select({
|
|
id: capaActions.id,
|
|
incidentId: capaActions.incidentId,
|
|
rootCauseRef: capaActions.rootCauseRef,
|
|
description: capaActions.description,
|
|
ownerUserId: capaActions.ownerUserId,
|
|
department: capaActions.department,
|
|
dueDate: capaActions.dueDate,
|
|
priority: capaActions.priority,
|
|
status: capaActions.status,
|
|
completedAt: capaActions.completedAt,
|
|
verifiedBy: capaActions.verifiedBy,
|
|
verifiedAt: capaActions.verifiedAt,
|
|
createdAt: capaActions.createdAt,
|
|
incidentReferenceNo: incidents.referenceNo,
|
|
incidentType: incidents.incidentType,
|
|
ownerName: ownerAlias.name,
|
|
ownerEmail: ownerAlias.email,
|
|
})
|
|
.from(capaActions)
|
|
.leftJoin(ownerAlias, eq(capaActions.ownerUserId, ownerAlias.id))
|
|
.leftJoin(incidents, eq(capaActions.incidentId, incidents.id))
|
|
.orderBy(asc(capaActions.dueDate))
|
|
|
|
if (session.role === 'supervisor' || session.role === 'worker') {
|
|
q = q.where(eq(capaActions.ownerUserId, session.sub)) as typeof q
|
|
}
|
|
|
|
return q
|
|
})
|
|
|
|
const today = new Date().toISOString().slice(0, 10)
|
|
const data = rows.map(r => ({
|
|
id: r.id,
|
|
incident_id: r.incidentId,
|
|
root_cause_ref: r.rootCauseRef,
|
|
description: r.description,
|
|
owner_user_id: r.ownerUserId,
|
|
department: r.department,
|
|
due_date: r.dueDate,
|
|
priority: r.priority,
|
|
status: (r.status === 'open' || r.status === 'in_progress') && r.dueDate && r.dueDate < today
|
|
? 'overdue'
|
|
: r.status,
|
|
completed_at: r.completedAt,
|
|
verified_by: r.verifiedBy,
|
|
verified_at: r.verifiedAt,
|
|
created_at: r.createdAt,
|
|
incidents: { reference_no: r.incidentReferenceNo, incident_type: r.incidentType },
|
|
owner: { name: r.ownerName, email: r.ownerEmail },
|
|
}))
|
|
|
|
return NextResponse.json(data)
|
|
}
|
|
|
|
export async function POST(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 body = await request.json()
|
|
const { incident_id, description, owner_user_id, department, due_date, priority, root_cause_ref } = body
|
|
|
|
if (!incident_id || !description || !owner_user_id || !due_date)
|
|
return NextResponse.json({ error: 'Missing required fields' }, { status: 422 })
|
|
|
|
let capaId!: string
|
|
|
|
await withUser(session.sub, async tx => {
|
|
const [capa] = await tx
|
|
.insert(capaActions)
|
|
.values({
|
|
incidentId: incident_id,
|
|
description,
|
|
ownerUserId: owner_user_id,
|
|
department: department || '',
|
|
dueDate: due_date,
|
|
priority: priority ?? 'med',
|
|
rootCauseRef: root_cause_ref ?? null,
|
|
})
|
|
.returning({ id: capaActions.id })
|
|
|
|
if (!capa) throw new Error('Insert failed')
|
|
capaId = capa.id
|
|
|
|
await writeAuditLog(tx, 'capa_actions', capa.id, 'created', {
|
|
incident_id, description, owner_user_id, department, due_date,
|
|
})
|
|
|
|
await tx
|
|
.update(incidents)
|
|
.set({ status: 'capa_pending' })
|
|
.where(and(eq(incidents.id, incident_id), eq(incidents.status, 'investigating')))
|
|
|
|
await writeAuditLog(tx, 'incidents', incident_id, 'status_changed', {
|
|
status: 'capa_pending',
|
|
})
|
|
})
|
|
|
|
await createInAppNotifications([{
|
|
userId: owner_user_id,
|
|
title: `CAPA assigned to you, due ${due_date}`,
|
|
link: `/hse/capa/${capaId}`,
|
|
incidentId: incident_id,
|
|
capaId,
|
|
}])
|
|
|
|
return NextResponse.json({ id: capaId }, { status: 201 })
|
|
}
|