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).
27 lines
1.0 KiB
TypeScript
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(),
|
|
})
|