import vine from '@vinejs/vine' const INVOICE_STATUSES = [ 'pending', 'awaiting_user_confirmation', 'in_relance', 'paid', 'litigation', 'cancelled', ] as const /** * Filtres GET /invoices?status=&q=&clientId=&page= */ export const listInvoicesValidator = vine.create({ status: vine.enum([...INVOICE_STATUSES, 'all'] as const).optional(), q: vine.string().maxLength(120).optional(), clientId: vine.string().uuid().optional(), page: vine.number().min(1).optional(), }) /** * POST /invoices — saisie manuelle. * * Le SPA peut envoyer : * - clientId d'un client existant (combobox a sélectionné une fiche), OU * - clientName seul → on tente de matcher par nom, sinon création à la * volée mais alors clientEmail est REQUIS (pivot produit, cf. Client). * * On ne peut pas exprimer "email requis si pas de match" en Vine pur, donc * c'est le contrôleur qui retourne 422 `client_email_required` si besoin. */ export const createInvoiceValidator = vine.create({ clientId: vine.string().uuid().optional(), clientName: vine.string().minLength(2).maxLength(120), clientEmail: vine.string().email().nullable().optional(), numero: vine.string().minLength(1).maxLength(50), amountTtcCents: vine.number().min(1), issueDate: vine.string().regex(/^\d{4}-\d{2}-\d{2}T/), dueDate: vine.string().regex(/^\d{4}-\d{2}-\d{2}T/), planId: vine.string().uuid().nullable().optional(), }) const HEX_RE = /^#[0-9a-fA-F]{6}$/u /** Une ligne de l'éditeur de facture native. */ const invoiceLineObject = vine.object({ id: vine.string().minLength(1).maxLength(64), description: vine.string().minLength(1).maxLength(500), quantity: vine.number().positive(), unitPriceCents: vine.number().min(0).max(100_000_000), tvaRate: vine.number().in([0, 2.1, 5.5, 10, 20]), }) /** * POST /invoices/native — création depuis l'éditeur natif. * * - pas de `numero` : alloué par le serveur (séquence strict) * - pas de `amountTtcCents` : recalculé depuis lines * - `lines` requis avec au moins 1 entrée * - `themeSlug` + `accentColor` snapshotés sur la facture * - `clientId` obligatoire (créer le client en amont si neuf) * - `draft: true` → ne consomme pas la séquence (brouillon) */ export const createNativeInvoiceValidator = vine.compile( vine.object({ clientId: vine.string().uuid(), issueDate: vine.string().regex(/^\d{4}-\d{2}-\d{2}T/), dueDate: vine.string().regex(/^\d{4}-\d{2}-\d{2}T/), paymentTermsDays: vine.number().min(0).max(365), planId: vine.string().uuid().nullable().optional(), themeSlug: vine.enum(['classique', 'moderne', 'minimal', 'elegant'] as const), accentColor: vine.string().regex(HEX_RE), lines: vine.array(invoiceLineObject).minLength(1), footerNotes: vine.string().maxLength(1000).nullable().optional(), draft: vine.boolean().optional(), }) ) /** * POST /invoices/preview-pdf — mêmes champs que la création, sans persister. * Le serveur recalcule les totaux et stream le PDF. */ export const previewInvoiceValidator = vine.compile( vine.object({ clientId: vine.string().uuid(), issueDate: vine.string().regex(/^\d{4}-\d{2}-\d{2}T/), dueDate: vine.string().regex(/^\d{4}-\d{2}-\d{2}T/), paymentTermsDays: vine.number().min(0).max(365), themeSlug: vine.enum(['classique', 'moderne', 'minimal', 'elegant'] as const), accentColor: vine.string().regex(HEX_RE), lines: vine.array(invoiceLineObject).minLength(1), footerNotes: vine.string().maxLength(1000).nullable().optional(), }) )