import { NotificationLog } from '../models/NotificationLog.js'; import { brandedEmail, sendMail } from './mail.js'; export async function notify({ kind, to, subject, text, html }) { const recipient = String(to || '').trim(); if (!recipient) return { ok: false, skipped: true }; try { await sendMail({ to: recipient, subject, text, html }); await NotificationLog.create({ kind, to: recipient, subject, status: 'sent' }); return { ok: true }; } catch (err) { const error = err?.message || 'Could not send email'; await NotificationLog.create({ kind, to: recipient, subject, status: 'failed', error }); console.error(`[notify] ${kind} failed for ${recipient}: ${error}`); return { ok: false, error }; } } export function personName(user) { return [user?.firstName, user?.lastName].filter(Boolean).join(' '); } export function staffGreeting(user) { const name = personName(user); return name ? `Dear ${name},` : 'Hello,'; } function prtLabel(user) { const name = personName(user); return name || 'your Virtual PRT'; } function cohortDetails(cohort) { return [ { label: 'Cohort', value: cohort?.name || '' }, { label: 'Start date', value: String(cohort?.startDate || '').slice(0, 10) }, { label: 'Slot', value: cohort?.slot || '' }, { label: 'Language', value: cohort?.language || '' }, { label: 'Virtual PRT', value: prtLabel(cohort?.virtualPrt) }, ]; } export async function notifyPaymentReview({ toUser, patientName, status, reason }) { if (!toUser?.email) return; const approved = status === 'confirmed'; const subject = approved ? `Patient confirmed: ${patientName}` : `Patient not confirmed: ${patientName}`; const { text, html } = brandedEmail({ greeting: staffGreeting(toUser), intro: approved ? `${patientName} has been confirmed and can now be matched to a cohort.` : `${patientName} was not confirmed.`, details: [ { label: 'Patient', value: patientName }, { label: 'Decision', value: approved ? 'Confirmed' : 'Rejected' }, { label: 'Reason', value: !approved ? reason : '' }, ], note: approved ? 'No further action is needed unless you were asked to update the patient record.' : 'Please review the reason and update the patient record if a correction is needed.', }); await notify({ kind: approved ? 'payment_confirmed' : 'payment_rejected', to: toUser.email, subject, text, html, }); } export async function notifyCohortConfirmed({ prt, fieldNotices = [], cohort }) { const subject = `Cohort confirmed: ${cohort.name}`; if (prt?.email) { const { text, html } = brandedEmail({ greeting: staffGreeting(prt), intro: `You have been assigned ${cohort.name}. The cohort is confirmed and its sessions are on your calendar.`, details: cohortDetails(cohort), note: 'Please review the start date and slot, and connect Google Calendar on your profile if Meet links are not yet created.', }); await notify({ kind: 'cohort_confirmed', to: prt.email, subject, text, html, }); } for (const notice of fieldNotices) { const field = notice.user || notice; if (!field?.email) continue; const names = (notice.names || []).filter(Boolean); const { text, html } = brandedEmail({ greeting: staffGreeting(field), intro: names.length ? `Patients you registered have been placed in ${cohort.name}.` : `Patients you registered were assigned to ${cohort.name}.`, details: [ ...cohortDetails(cohort), { label: 'Your patients', value: names.join(', ') }, ], }); await notify({ kind: 'cohort_confirmed_field', to: field.email, subject, text, html, }); } } export async function notifySessionReminder({ prt, session, cohort }) { if (!prt?.email) return; const subject = `Session reminder: ${cohort?.name || 'Cohort'} at ${session.slot || ''}`; const { text, html } = brandedEmail({ greeting: staffGreeting(prt), intro: 'Your cohort session starts in about 2 hours.', details: [ { label: 'Cohort', value: cohort?.name || '' }, { label: 'Date', value: session.date || '' }, { label: 'Time', value: session.slot || '' }, ], note: session.meetUrl ? 'Use the button below to join the meeting.' : 'A Meet link is not on this session yet.', action: session.meetUrl ? { href: session.meetUrl, label: 'Join Meet' } : null, }); await notify({ kind: 'session_reminder', to: prt.email, subject, text, html, }); } export async function notifySessionPushed({ prt, admins = [], cohort, fromDate, toDate, reason }) { const subject = `Session moved: ${cohort?.name || 'Cohort'}`; const content = { intro: `The ${fromDate} session for ${cohort?.name || 'a cohort'} was moved.`, details: [ { label: 'Cohort', value: cohort?.name || '' }, { label: 'Original date', value: fromDate }, { label: 'New date', value: toDate }, { label: 'Reason', value: reason || 'Rescheduled' }, ], note: 'The replacement session keeps the same slot. Please update anyone who was expecting the original date.', }; if (prt?.email) { const { text, html } = brandedEmail({ greeting: staffGreeting(prt), ...content }); await notify({ kind: 'session_pushed', to: prt.email, subject, text, html, }); } for (const admin of admins) { if (!admin?.email) continue; const { text, html } = brandedEmail({ greeting: staffGreeting(admin), ...content }); await notify({ kind: 'session_cancelled_admin', to: admin.email, subject, text, html, }); } } export async function notifyLeaveDecision({ prt, status, startDate, endDate }) { if (!prt?.email) return; const approved = status === 'approved'; const subject = `Leave ${approved ? 'approved' : status}: ${startDate} to ${endDate}`; const { text, html } = brandedEmail({ greeting: staffGreeting(prt), intro: approved ? 'Your leave request has been approved.' : `Your leave request was ${status}.`, details: [ { label: 'From', value: startDate }, { label: 'To', value: endDate }, { label: 'Decision', value: approved ? 'Approved' : status }, ], note: approved ? 'Sessions in this period will be covered or moved according to the leave decision.' : 'Your sessions in this period stay on the original dates.', }); await notify({ kind: `leave_${status}`, to: prt.email, subject, text, html, }); } export async function notifyReassignment({ prt, cohort, reason }) { if (!prt?.email) return; const { text, html } = brandedEmail({ greeting: staffGreeting(prt), intro: `You have been assigned to ${cohort?.name || 'a cohort'}.`, details: [ ...cohortDetails(cohort), { label: 'Reason', value: reason || 'The previous Virtual PRT is no longer assigned.' }, ], note: 'Please check the cohort schedule and make sure your Google Calendar is connected.', }); await notify({ kind: 'reassignment', to: prt.email, subject: `Cohort assigned: ${cohort?.name || 'Cohort'}`, text, html, }); }