/** * Source de vérité des 4 plans pré-fournis (cf. CLAUDE.md → Périmètre V1). * Dupliqués dans chaque organisation à la création (signup) — V1 mono-tenant * mais l'isolation est totale, on peut éditer le plan d'une org sans toucher * aux autres. * * Les valeurs (cadences, tons, sujets) doivent rester alignées sur le seed * MSW (apps/web/src/mocks/seed.ts → SEED_PLANS) tant que les deux coexistent. */ import type { TransactionClientContract } from '@adonisjs/lucid/types/database' import Plan from '#models/plan' import PlanStep from '#models/plan_step' type DefaultStep = { order: number offsetDays: number tone: 'amical' | 'courtois' | 'ferme' | 'mise_en_demeure' subject: string body: string requiresManualValidation: boolean } type DefaultPlan = { slug: string name: string description: string steps: DefaultStep[] /** * Email de remerciement envoyé au client final dès que l'utilisateur * confirme avoir reçu le paiement (via check-in ou mark-paid). Mêmes * variables que les steps (cf. mail_dispatcher → buildRelanceVars). */ thanksSubject: string thanksBody: string } export const DEFAULT_PLANS: DefaultPlan[] = [ { slug: 'standard-30j', name: 'Standard B2B', description: 'Cadence sobre, ton qui monte progressivement. Pour la majorité des clients sérieux.', thanksSubject: 'Merci ! Bien reçu pour la facture {{numero}}', thanksBody: "Bonjour {{client.name}},\n\nNous confirmons la bonne réception du règlement de la facture {{numero}} d'un montant de {{amount}}. Merci pour ce paiement et au plaisir de continuer à travailler ensemble.\n\n{{signature}}", steps: [ { order: 0, offsetDays: 3, tone: 'amical', subject: 'Petit rappel — facture {{numero}}', body: "Bonjour {{client.name}},\n\nNous espérons que tout va bien. Un petit rappel concernant la facture {{numero}} d'un montant de {{amount}}, échue le {{dueDate}}.\n\nMerci d'avance,\n{{signature}}", requiresManualValidation: false, }, { order: 1, offsetDays: 10, tone: 'courtois', subject: 'Relance — facture {{numero}} en retard', body: "Bonjour {{client.name}},\n\nSauf erreur de notre part, la facture {{numero}} d'un montant de {{amount}} reste impayée.\n\nMerci de procéder au règlement dans les meilleurs délais.\n\n{{signature}}", requiresManualValidation: false, }, { order: 2, offsetDays: 25, tone: 'ferme', subject: 'Mise en demeure — facture {{numero}}', body: "Bonjour {{client.name}},\n\nMalgré nos relances, la facture {{numero}} d'un montant de {{amount}} reste impayée. Nous vous mettons en demeure de régler sous 8 jours.\n\n{{signature}}", requiresManualValidation: true, }, ], }, { slug: 'rapide-15j', name: 'Rapide', description: 'Cadence resserrée pour les factures récurrentes ou les délais courts.', thanksSubject: 'Paiement bien reçu — facture {{numero}}', thanksBody: 'Bonjour {{client.name}},\n\nMerci, nous avons bien reçu le règlement de la facture {{numero}} ({{amount}}). Bonne continuation.\n\n{{signature}}', steps: [ { order: 0, offsetDays: 1, tone: 'amical', subject: 'Facture {{numero}} échue', body: 'Bonjour, petit rappel pour la facture {{numero}}.\n\n{{signature}}', requiresManualValidation: false, }, { order: 1, offsetDays: 7, tone: 'courtois', subject: 'Relance facture {{numero}}', body: 'La facture {{numero}} reste impayée à ce jour. Merci de régulariser.\n\n{{signature}}', requiresManualValidation: false, }, { order: 2, offsetDays: 15, tone: 'ferme', subject: 'Mise en demeure {{numero}}', body: 'Mise en demeure formelle de payer sous 8 jours.\n\n{{signature}}', requiresManualValidation: true, }, ], }, { slug: 'patient-60j', name: 'Patient', description: 'Pour les clients de longue date. On laisse respirer avant de relancer.', thanksSubject: 'Merci — règlement bien reçu pour {{numero}}', thanksBody: "Bonjour {{client.name}},\n\nNous accusons bonne réception du paiement de la facture {{numero}} ({{amount}}). Merci de votre confiance, à très bientôt.\n\n{{signature}}", steps: [ { order: 0, offsetDays: 15, tone: 'amical', subject: 'Facture {{numero}}', body: 'Bonjour, simple rappel.\n\n{{signature}}', requiresManualValidation: false, }, { order: 1, offsetDays: 30, tone: 'courtois', subject: 'Relance facture {{numero}}', body: 'Merci de régulariser dans les meilleurs délais.\n\n{{signature}}', requiresManualValidation: false, }, ], }, { slug: 'ferme-7j', name: 'Ferme', description: 'Cadence stricte pour les clients à risque ou les retards récurrents.', thanksSubject: 'Règlement reçu — facture {{numero}}', thanksBody: 'Bonjour {{client.name}},\n\nNous confirmons la bonne réception du paiement de la facture {{numero}} ({{amount}}). Merci.\n\n{{signature}}', steps: [ { order: 0, offsetDays: 1, tone: 'courtois', subject: 'Facture {{numero}}', body: 'Premier rappel.\n\n{{signature}}', requiresManualValidation: false, }, { order: 1, offsetDays: 5, tone: 'ferme', subject: 'Relance ferme {{numero}}', body: 'Le règlement est attendu sous 48h.\n\n{{signature}}', requiresManualValidation: false, }, { order: 2, offsetDays: 10, tone: 'mise_en_demeure', subject: 'Mise en demeure {{numero}}', body: 'Mise en demeure formelle.\n\n{{signature}}', requiresManualValidation: true, }, ], }, ] /** * Provisionne les 4 plans par défaut pour une organisation fraîchement créée. * Idempotent : si l'org a déjà un plan avec un slug, on n'écrase pas. * * À appeler dans la transaction de signup. */ export async function provisionDefaultPlans( organizationId: string, trx: TransactionClientContract ): Promise { const existing = await Plan.query({ client: trx }) .where('organization_id', organizationId) .whereIn( 'slug', DEFAULT_PLANS.map((p) => p.slug) ) .select('slug') const existingSlugs = new Set(existing.map((p) => p.slug)) const created: Plan[] = [] for (const tpl of DEFAULT_PLANS) { if (existingSlugs.has(tpl.slug)) continue const plan = await Plan.create( { organizationId, slug: tpl.slug, name: tpl.name, description: tpl.description, isDefault: true, thanksSubject: tpl.thanksSubject, thanksBody: tpl.thanksBody, }, { client: trx } ) await PlanStep.createMany( tpl.steps.map((s) => ({ planId: plan.id, order: s.order, offsetDays: s.offsetDays, tone: s.tone, subject: s.subject, body: s.body, requiresManualValidation: s.requiresManualValidation, })), { client: trx } ) created.push(plan) } return created }