import { Resend } from 'resend' import type { SupabaseClient } from '@supabase/supabase-js' import { sendWhatsAppMessage } from '@/lib/notifications/whatsapp' import { createInAppNotifications } from '@/lib/notifications/in-app' import { getApiKey } from '@/lib/settings' export type EscalationThreshold = 'warning_3d' | 'due_today' | 'overdue_3d' | 'overdue_7d' export function getEscalationThreshold(dueDateIso: string): EscalationThreshold | null { const today = new Date() today.setUTCHours(0, 0, 0, 0) const due = new Date(dueDateIso) // date-only ISO strings are parsed as UTC midnight — keep due in UTC too const diffDays = Math.round((due.getTime() - today.getTime()) / 86_400_000) if (diffDays === 3) return 'warning_3d' if (diffDays === 0) return 'due_today' if (diffDays === -3) return 'overdue_3d' if (diffDays === -7) return 'overdue_7d' return null } const THRESHOLD_SUBJECT: Record = { warning_3d: '[IMS] CAPA action due in 3 days', due_today: '[IMS] CAPA action due TODAY', overdue_3d: '[IMS] CAPA action 3 days OVERDUE', overdue_7d: '[IMS] URGENT: CAPA action 7 days OVERDUE', } export async function escalateOverdueCapa( supabase: SupabaseClient ): Promise<{ notified: number }> { const { data: capas } = await supabase .from('capa_actions') .select(` id, description, due_date, incident_id, owner_user_id, incidents (reference_no, site_id), owner:users!owner_user_id (email, name, phone) `) .not('status', 'in', '(verified,closed)') if (!capas || capas.length === 0) return { notified: 0 } const resend = new Resend(process.env.RESEND_API_KEY) const from = process.env.RESEND_FROM_EMAIL ?? 'onboarding@resend.dev' const siteUrl = process.env.NEXT_PUBLIC_SITE_URL ?? 'http://localhost:3000' let notified = 0 const whatsappPhoneNumberId = await getApiKey(supabase, 'META_WHATSAPP_PHONE_NUMBER_ID') const whatsappAccessToken = await getApiKey(supabase, 'META_WHATSAPP_ACCESS_TOKEN') for (const capa of capas) { const threshold = getEscalationThreshold((capa as { due_date: string }).due_date) if (!threshold) continue const { data: alreadySent } = await supabase .from('notifications_log') .select('id') .eq('capa_id', capa.id) .eq('channel', 'email') .eq('status', threshold) .limit(1) .maybeSingle() if (alreadySent) continue const ownerEmail = (capa.owner as unknown as { email: string } | null)?.email const ownerName = (capa.owner as unknown as { name: string } | null)?.name ?? 'Owner' if (!ownerEmail) continue const incidentRef = (capa.incidents as unknown as { reference_no: string | null } | null)?.reference_no ?? capa.incident_id const capaUrl = `${siteUrl}/ims/hse/capa/${capa.id}` const subject = THRESHOLD_SUBJECT[threshold] const html = `

Hi ${ownerName},

CAPA action for incident ${incidentRef} requires attention.

Action: ${(capa as { description: string }).description}

Due date: ${(capa as { due_date: string }).due_date}

View CAPA

` const text = `CAPA ${incidentRef}: ${(capa as { description: string }).description}\nDue: ${(capa as { due_date: string }).due_date}\n${capaUrl}` const to = [ownerEmail] const siteId = (capa.incidents as unknown as { site_id: string } | null)?.site_id if (siteId && ['due_today', 'overdue_3d', 'overdue_7d'].includes(threshold)) { const { data: hseUsers } = await supabase .from('users') .select('email') .eq('site_id', siteId) .in('role', ['hse', 'supervisor', 'management']) if (hseUsers) to.push(...hseUsers.map((u: { email: string }) => u.email)) } const { error } = await resend.emails.send({ from, to: [...new Set(to)], subject, html, text }) if (error) { console.error('Escalation email error:', error) continue } // WhatsApp for urgent thresholds only (owner must have a phone number) if (['overdue_3d', 'overdue_7d'].includes(threshold)) { const ownerPhone = (capa.owner as unknown as { phone: string | null } | null)?.phone ?? '' if (ownerPhone) { try { await sendWhatsAppMessage( ownerPhone, 'ims_capa_overdue', [incidentRef, (capa as { description: string }).description, (capa as { due_date: string }).due_date], whatsappPhoneNumberId, whatsappAccessToken, ) } catch (waErr) { console.error('WhatsApp escalation error:', waErr) } } } await supabase.from('notifications_log').insert({ capa_id: capa.id, channel: 'email', recipient: to.join(','), status: threshold, }) const ownerUserId = (capa as { owner_user_id: string | null }).owner_user_id if (ownerUserId) { await createInAppNotifications(supabase, [{ userId: ownerUserId, title: `${THRESHOLD_SUBJECT[threshold].replace('[IMS] ', '')} — ${incidentRef}`, link: `/hse/capa/${capa.id}`, incidentId: capa.incident_id as string, capaId: capa.id as string, }]) } notified++ } return { notified } }