Migration clients (uuid id, organization_id FK uuid CASCADE, name, email REQUIS, phone, address, siret, notes). Index sur organization_id. Modèle Client avec belongsTo Organization. La relation hasMany Invoice est volontairement omise tant que le domaine Invoice n'est pas câblé. Validators Vine alignés sur le contrat MSW : - create : name 2-120, email requis avec format, siret 14 chiffres si fourni - update : tout optionnel - email REQUIS au create — pivot produit, pas de relance possible sans Endpoints (auth requise, scopés par organizationId du user courant) : - GET /clients?withStats=1&q= : liste filtrée + recherche, enrichissement stats optionnel, tri par actionnabilité (retards d'abord) quand withStats - GET /clients/:id : détail (id en UUID via router.matchers.uuid()) - POST /clients : 201 + détection doublon par nom case-insensitive → 409 avec payload `existing` (le SPA peut proposer "voir le client existant") - PATCH /clients/:id : merge partiel Service ClientStats avec interface bulkComputeClientStats() qui retourne EMPTY pour l'instant — sera vraiment branché quand Invoice arrive. Le contrat reste stable côté SPA, juste les compteurs à 0. Sérialisation : pour les listes avec stats per-item, on instancie le transformer manuellement (`new ClientTransformer(c).toObject()`) plutôt que de passer par BaseTransformer.transform() qui retourne un Item nested non-unwrappable hors clé directe de serialize().
35 lines
1.1 KiB
TypeScript
35 lines
1.1 KiB
TypeScript
import vine from '@vinejs/vine'
|
|
|
|
const name = () => vine.string().minLength(2).maxLength(120)
|
|
const email = () => vine.string().email().maxLength(254)
|
|
// SIRET = 14 chiffres exactement (cf. INSEE).
|
|
const siret = () => vine.string().regex(/^\d{14}$/)
|
|
const phone = () => vine.string().maxLength(40)
|
|
const address = () => vine.string().maxLength(500)
|
|
const notes = () => vine.string().maxLength(2000)
|
|
|
|
/**
|
|
* Validator pour POST /clients. Email **requis** : sans email, Rubis ne
|
|
* peut pas relancer (pivot produit, cf. CLAUDE.md → Principes).
|
|
*/
|
|
export const createClientValidator = vine.create({
|
|
name: name(),
|
|
email: email(),
|
|
phone: phone().nullable().optional(),
|
|
address: address().nullable().optional(),
|
|
siret: siret().nullable().optional(),
|
|
notes: notes().nullable().optional(),
|
|
})
|
|
|
|
/**
|
|
* Validator pour PATCH /clients/:id. Tous les champs optionnels.
|
|
*/
|
|
export const updateClientValidator = vine.create({
|
|
name: name().optional(),
|
|
email: email().optional(),
|
|
phone: phone().nullable().optional(),
|
|
address: address().nullable().optional(),
|
|
siret: siret().nullable().optional(),
|
|
notes: notes().nullable().optional(),
|
|
})
|