ordinarthur 692b514fe9 feat(api): domaine Plan + PlanStep + provisioning des 4 plans pré-fournis
Migrations :
- plans (uuid id, organization_id FK CASCADE, slug nullable, name, description, is_default). Unique (organization_id, slug) — un slug max par org.
- plan_steps (uuid id, plan_id FK CASCADE, order, offset_days, tone ENUM PG natif, subject, body, requires_manual_validation).

Schema rules : override du tone (introspection PG → 'any', on précise l'union).

Modèles Plan (belongsTo Organization, hasMany PlanStep) et PlanStep (belongsTo Plan).

Décision : plans dupliqués par organisation au signup (pas de table globale partagée). Permet l'édition isolée par org sans toucher aux templates des autres tenants. Le service `provisionDefaultPlans(orgId, trx)` est idempotent et appelé depuis NewAccountController dans la transaction de création.

Source de vérité des 4 plans (Standard B2B, Rapide, Patient, Ferme) dans app/services/default_plans.ts — alignée sur apps/web/src/mocks/seed.ts.

Endpoints :
- GET /plans : liste enrichie avec usageCount (à 0 tant qu'Invoice n'est pas câblé).
- GET /plans/:slug : détail (lookup par slug pour URL stable côté SPA).
- PATCH /plans/:slug : édition partielle. Les steps sont remplacés en bloc dans une transaction (pas de diff fin id-par-id, plus simple et prévisible).

POST plan custom = V2 (cf. backend.md §5.5).
2026-05-06 14:25:06 +02:00

27 lines
1.0 KiB
TypeScript

import vine from '@vinejs/vine'
const RELANCE_TONES = ['amical', 'courtois', 'ferme', 'mise_en_demeure'] as const
const planStep = vine.object({
// id optionnel : présent si on édite une étape existante, absent pour
// une création (le contrôleur le générera).
id: vine.string().optional(),
order: vine.number().min(0),
// Plage : -30 (rappel avant échéance) à 180 jours (gros retards).
offsetDays: vine.number().min(-30).max(180),
tone: vine.enum(RELANCE_TONES),
subject: vine.string().minLength(1).maxLength(200),
body: vine.string().minLength(1).maxLength(5000),
requiresManualValidation: vine.boolean(),
})
/**
* Validator pour PATCH /plans/:slug. Tous les champs optionnels — l'éditeur
* front peut envoyer juste `name` ou juste `steps` selon ce qu'il modifie.
*/
export const updatePlanValidator = vine.create({
name: vine.string().minLength(1).maxLength(80).optional(),
description: vine.string().maxLength(500).optional(),
steps: vine.array(planStep).minLength(1).maxLength(10).optional(),
})