import type { Plan, RelanceTone } from "@rubis/shared"; /** * Mini interpolateur Mustache-like, miroir de * `apps/api/app/services/template.ts:renderTemplate`. Utilisé pour la * preview live dans le wizard de création de plan custom. */ export function renderTemplate(template: string, vars: Record): string { return template.replace(/\{\{\s*([\w.]+)\s*\}\}/g, (_, path: string) => { const parts = path.split("."); let val: unknown = vars; for (const p of parts) { if (val == null || typeof val !== "object") return ""; val = (val as Record)[p]; } return val == null ? "" : String(val); }); } /** * Vars de preview : utilisées par le wizard pour montrer l'email tel * qu'il sera reçu, avec un client/facture fictifs. */ export const PREVIEW_VARS = { client: { name: "Boulangerie Martin SARL", email: "compta@boulangerie-martin.fr", contactFirstName: "Marie", contactLastName: "Martin", }, user: { fullName: "Arthur Barré", companyName: "Maçonnerie Dupont", }, numero: "F-2026-0042", amount: "1 240,00 €", dueDate: "15/04/2026", issueDate: "15/03/2026", daysLate: "12", signature: "Cordialement,\nArthur Barré\nMaçonnerie Dupont", } as const; /** * Helpers de présentation des plans de relance. * Garde la conversion tonalité → label public au même endroit. */ /** Label visible utilisateur pour chaque ton (cf. wireframe 3.1, chips). */ export const TONE_LABELS: Record = { amical: "Amical", courtois: "Standard", ferme: "Ferme", mise_en_demeure: "Mise en demeure", }; /** Tonalité globale d'un plan = la dernière étape (la plus stricte). */ export function planOverallTone(plan: Pick): RelanceTone { const last = plan.steps[plan.steps.length - 1]; return last?.tone ?? "courtois"; } /** Label court d'humeur d'un plan ("Doux" / "Standard" / "Ferme" / "Strict"). */ export function planMoodLabel(plan: Pick): string { const tone = planOverallTone(plan); switch (tone) { case "amical": return "Doux"; case "courtois": return "Standard"; case "ferme": return "Ferme"; case "mise_en_demeure": return "Strict"; default: return "Standard"; } } /** * Variables de template disponibles dans les emails de relance. * Les chips dans l'éditeur viennent piocher ici. */ export type TemplateVariable = { /** Token inséré tel quel dans le body (ex. "{{numero}}"). */ token: string; /** Label affiché sur le chip cliquable. */ label: string; /** Aperçu utilisé dans l'éditeur (placeholder réaliste). */ preview: string; /** Si présent, exige qu'un champ correspondant soit rempli sur la fiche * client pour fonctionner. Sinon le token est interpolé en chaîne vide. */ requiresClientField?: "contactFirstName" | "contactLastName"; }; export const TEMPLATE_VARIABLES: TemplateVariable[] = [ // Client { token: "{{client.name}}", label: "Raison sociale", preview: "Boulangerie Martin SARL" }, { token: "{{client.contactFirstName}}", label: "Prénom contact", preview: "Marie", requiresClientField: "contactFirstName", }, { token: "{{client.contactLastName}}", label: "Nom contact", preview: "Martin", requiresClientField: "contactLastName", }, // Facture { token: "{{numero}}", label: "Numéro facture", preview: "F-2026-0042" }, { token: "{{amount}}", label: "Montant TTC", preview: "1 240,00 €" }, { token: "{{dueDate}}", label: "Échéance", preview: "15/04/2026" }, { token: "{{issueDate}}", label: "Date émission", preview: "15/03/2026" }, { token: "{{daysLate}}", label: "Jours de retard", preview: "12" }, // Expéditeur (la TPE qui envoie) { token: "{{user.fullName}}", label: "Votre nom", preview: "Arthur Barré" }, { token: "{{user.companyName}}", label: "Votre entreprise", preview: "Maçonnerie Dupont" }, { token: "{{signature}}", label: "Signature", preview: "Cordialement,\nArthur" }, ]; /** * Variables qui exigent qu'un champ correspondant soit rempli sur la fiche * client. Pour chaque token utilisé dans un template, on peut détecter * combien de clients existants n'ont pas le champ requis (warning UX). */ export type ClientRequiredField = "contactFirstName" | "contactLastName";