All files / services/whatsapp admin-notify.service.ts

100% Statements 24/24
75% Branches 12/16
100% Functions 2/2
100% Lines 22/22

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103                                              1x 1x                                       7x 7x 2x 7x 1x             7x 7x 1x     1x       6x 6x   2x 1x 1x   1x     4x 4x                             3x   1x       1x            
/**
 * Send the `import_result_admin` WhatsApp template to admins on terminal
 * import events.
 *
 * Template body (3 variables):
 *   "Import for {{1}} ({{2}}): {{3}}. Review at portal.interioring.com/admin/imports"
 *
 * Recipient resolution order:
 *   1. The submitting admin's `users.phone` (if set)
 *   2. `env.IMPORT_NOTIFY_WHATSAPP` fallback
 *   3. If neither — warn and no-op (never throws).
 *
 * This is best-effort: any failure logs and resolves false rather than
 * throwing, so a WhatsApp outage cannot block the email channel running
 * alongside it.
 */
 
import { IMPORT_RESULT_TEMPLATE as IMPORT_RESULT_TEMPLATE_SPEC } from "../../config/whatsapp-templates";
import {
	requireWhatsAppClient,
	WhatsAppConfigError,
} from "../../lib/whatsapp/client";
 
export const IMPORT_RESULT_TEMPLATE = IMPORT_RESULT_TEMPLATE_SPEC.name;
export const IMPORT_RESULT_TEMPLATE_LANG = IMPORT_RESULT_TEMPLATE_SPEC.language;
 
export interface AdminNotifyInput {
	proName: string;
	status: "completed" | "failed" | "cancelled";
	summary: string;
	/** Submitter's phone in E.164 (no '+'). Optional. */
	submitterPhone?: string | null;
}
 
export interface AdminNotifyEnv {
	WHATSAPP_ACCESS_TOKEN?: string;
	IMPORT_NOTIFY_WHATSAPP?: string;
	ENVIRONMENT?: string;
}
 
function resolveRecipient(
	input: AdminNotifyInput,
	env: AdminNotifyEnv,
): string | null {
	const direct = input.submitterPhone?.trim();
	if (direct) return direct.replace(/\D/g, "");
	const fallback = env.IMPORT_NOTIFY_WHATSAPP?.trim();
	if (fallback) return fallback.replace(/\D/g, "");
	return null;
}
 
export async function sendImportResultWhatsApp(
	input: AdminNotifyInput,
	env: AdminNotifyEnv,
): Promise<{ delivered: boolean; reason?: string }> {
	const recipient = resolveRecipient(input, env);
	if (!recipient) {
		console.warn(
			"[ImportNotify] No WhatsApp recipient — skipping admin notify",
		);
		return { delivered: false, reason: "no_recipient" };
	}
 
	let client: ReturnType<typeof requireWhatsAppClient>;
	try {
		client = requireWhatsAppClient(env as never);
	} catch (err) {
		if (err instanceof WhatsAppConfigError) {
			console.warn("[ImportNotify] WhatsApp not configured — skipping");
			return { delivered: false, reason: "not_configured" };
		}
		throw err;
	}
 
	try {
		await client.sendTemplate(
			recipient,
			IMPORT_RESULT_TEMPLATE,
			IMPORT_RESULT_TEMPLATE_LANG,
			[
				{
					type: "body",
					parameters: [
						{ type: "text", text: input.proName.slice(0, 60) || "(unknown)" },
						{ type: "text", text: input.status },
						{ type: "text", text: input.summary.slice(0, 200) || "—" },
					],
				},
			],
		);
		return { delivered: true };
	} catch (err) {
		console.error(
			"[ImportNotify] WhatsApp send failed:",
			err instanceof Error ? err.message : String(err),
		);
		return {
			delivered: false,
			reason: err instanceof Error ? err.message : "send_failed",
		};
	}
}