You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
sims-hq/apps/hq/src/send-reminder.ts

70 lines
3.1 KiB
TypeScript

import type { DB } from './db'
import type { GmailDeps, SendResult } from './gmail'
import { sendReminderEmail } from './gmail'
import { getClient } from './repos-clients'
import { getClientModule } from './repos-modules'
import { getDocument } from './repos-documents'
import { getAmc } from './repos-amc'
import { getReminder, setReminderStatus } from './repos-reminders'
import { reminderEmail, type ReminderContext } from './reminder-templates'
import { documentHtml } from './templates'
/** Turn a queued/failed reminder into an actual email — used by the manual-queue
* Send button and by the scheduler's auto path. Reuses gmail.sendReminderEmail. */
export interface SendReminderDeps {
gmail: GmailDeps
renderPdf: (html: string) => Promise<Buffer>
company: () => Record<string, string>
now?: () => string
}
export async function sendReminder(
db: DB, deps: SendReminderDeps, reminderId: string, userId: string,
): Promise<SendResult> {
const reminder = getReminder(db, reminderId)
if (reminder === null) throw new Error('Reminder not found')
if (reminder.ruleKind === 'follow_up' || reminder.ruleKind === 'email_bounced') {
throw new Error(`A ${reminder.ruleKind} reminder is an internal item, not a sendable email`)
}
const client = getClient(db, reminder.clientId)
if (client === null) throw new Error('Client not found')
const company = deps.company()
const companyName = company['company.name'] ?? ''
// Build the per-rule context.
const ctx: ReminderContext = { clientName: client.name, companyName }
let attachment: { filename: string; data: Buffer } | undefined
let documentId: string | undefined
if (reminder.docId !== null) {
const doc = getDocument(db, reminder.docId)
if (doc === null) throw new Error('Reminder document not found')
documentId = doc.id
ctx.docNo = doc.docNo ?? undefined
ctx.amountPaise = doc.payablePaise
ctx.period = reminder.duePeriod
const pdf = await deps.renderPdf(documentHtml(doc, client, company))
attachment = { filename: `${(doc.docNo ?? 'draft').replaceAll('/', '-')}.pdf`, data: pdf }
} else if (reminder.ruleKind === 'renewal_due') {
const cm = getClientModule(db, reminder.subjectId)
if (cm !== null && cm.nextRenewal !== null) ctx.dueDate = cm.nextRenewal
} else if (reminder.ruleKind === 'amc_expiring') {
const amc = getAmc(db, reminder.subjectId)
if (amc !== null) { ctx.coverage = amc.coverage; ctx.dueDate = amc.periodTo }
}
const to = client.contacts.find((c) => c.email !== undefined && c.email !== '')?.email
if (to === undefined) throw new Error('No recipient: add a contact email to the client')
const mail = reminderEmail(reminder.ruleKind, ctx)
const out = await sendReminderEmail(db, deps.gmail, {
to, subject: mail.subject, bodyText: mail.bodyText,
...(attachment !== undefined ? { attachment } : {}),
...(documentId !== undefined ? { documentId } : {}),
})
const now = deps.now?.() ?? new Date().toISOString()
if (out.ok) setReminderStatus(db, userId, reminderId, 'sent', { sentAt: now, error: null })
else setReminderStatus(db, userId, reminderId, 'failed', { error: out.error })
return out
}