import { inArray } from "drizzle-orm"; import { Resend } from "resend"; import { payers } from "@/db/schema"; import { db } from "@/shared/lib/db"; import { getResendFromEmail } from "@/shared/lib/email/resend"; import { formatCurrency } from "@/shared/utils/currency"; import { formatDateTime } from "@/shared/utils/date"; type ActionType = "created" | "deleted"; export type NotificationEntry = { payerId: string; name: string | null; amount: number; transactionType: string | null; paymentMethod: string | null; condition: string | null; purchaseDate: Date | null; period: string | null; note: string | null; }; export type PayerNotificationRequest = { userLabel: string; action: ActionType; entriesByPagador: Map; }; type PayerNotificationRecipient = { id: string; name: string | null; email: string | null; isAutoSend: boolean | null; }; const formatDate = (value: Date | null) => { return ( formatDateTime(value, { day: "2-digit", month: "short", year: "numeric", }) ?? "—" ); }; const buildHtmlBody = ({ userLabel, action, entries, }: { userLabel: string; action: ActionType; entries: NotificationEntry[]; }) => { const actionLabel = action === "created" ? "Novo lançamento registrado" : "Lançamento removido"; const actionDescription = action === "created" ? "Um novo lançamento foi registrado em seu nome." : "Um lançamento anteriormente registrado em seu nome foi removido."; const rows = entries .map((entry) => { const label = entry.transactionType === "Despesa" ? `${formatCurrency(Math.abs(entry.amount))} (Despesa)` : `${formatCurrency(Math.abs(entry.amount))} (Receita)`; return ` ${formatDate( entry.purchaseDate, )} ${ entry.name ?? "Sem descrição" } ${ entry.paymentMethod ?? "—" } ${ entry.condition ?? "—" } ${label} `; }) .join(""); return `

${actionLabel}

${actionDescription}

${rows}
Data Descrição Pagamento Condição Valor

Enviado automaticamente por ${userLabel} via OpenMonetis.

`; }; export async function sendPayerAutoEmails({ userLabel, action, entriesByPagador, }: PayerNotificationRequest) { "use server"; if (entriesByPagador.size === 0) { return; } const resendApiKey = process.env.RESEND_API_KEY; const resendFrom = getResendFromEmail(); if (!resendApiKey) { console.warn( "RESEND_API_KEY não configurada. Envio automático de lançamentos ignorado.", ); return; } const pagadorIds = Array.from(entriesByPagador.keys()); if (pagadorIds.length === 0) { return; } const payerRows = (await db.query.payers.findMany({ where: inArray(payers.id, pagadorIds), })) as PayerNotificationRecipient[]; if (payerRows.length === 0) { return; } const resend = new Resend(resendApiKey); const subjectPrefix = action === "created" ? "Novo lançamento" : "Lançamento removido"; const results = await Promise.allSettled( payerRows.map(async (payer: PayerNotificationRecipient) => { if (!payer.email || !payer.isAutoSend) { return; } const entries = entriesByPagador.get(payer.id); if (!entries || entries.length === 0) { return; } const html = buildHtmlBody({ userLabel, action, entries, }); await resend.emails.send({ from: resendFrom, to: payer.email, subject: `${subjectPrefix} - ${payer.name}`, html, }); }), ); // Log any failed email sends results.forEach((result: PromiseSettledResult, index: number) => { if (result.status === "rejected") { const pagador = payerRows[index]; console.error( `Failed to send email notification to ${pagador?.name} (${pagador?.email}):`, result.reason, ); } }); } export type RawNotificationRecord = { payerId: string | null; name: string | null; amount: string | number | null; transactionType: string | null; paymentMethod: string | null; condition: string | null; purchaseDate: Date | string | null; period: string | null; note: string | null; }; export const buildEntriesByPayer = ( records: RawNotificationRecord[], ): Map => { const map = new Map(); records.forEach((record) => { if (!record.payerId) { return; } const amount = typeof record.amount === "number" ? record.amount : Number(record.amount ?? 0); const purchaseDate = record.purchaseDate instanceof Date ? record.purchaseDate : record.purchaseDate ? new Date(record.purchaseDate) : null; const entry: NotificationEntry = { payerId: record.payerId, name: record.name ?? null, amount, transactionType: record.transactionType ?? null, paymentMethod: record.paymentMethod ?? null, condition: record.condition ?? null, purchaseDate, period: record.period ?? null, note: record.note ?? null, }; const list = map.get(record.payerId) ?? []; list.push(entry); map.set(record.payerId, list); }); return map; };