import { z } from 'zod'; /** Vocabulaires & schémas de la gestion (R3). */ export const PARTNER_KINDS = ['SUPPLIER', 'CLIENT'] as const; export type PartnerKind = (typeof PARTNER_KINDS)[number]; export const PARTNER_KIND_LABELS: Record = { SUPPLIER: 'Fournisseur', CLIENT: 'Client / syndic', }; export const PURCHASE_ORDER_STATUSES = ['DRAFT', 'SENT', 'RECEIVED', 'CANCELLED'] as const; export type PurchaseOrderStatus = (typeof PURCHASE_ORDER_STATUSES)[number]; export const PURCHASE_ORDER_STATUS_LABELS: Record = { DRAFT: 'Brouillon', SENT: 'Envoyé', RECEIVED: 'Reçu', CANCELLED: 'Annulé', }; export const STOCK_MOVEMENT_KINDS = ['RECEIPT', 'ENTRY', 'CONSUMPTION', 'ADJUSTMENT'] as const; export type StockMovementKind = (typeof STOCK_MOVEMENT_KINDS)[number]; export const STOCK_MOVEMENT_KIND_LABELS: Record = { RECEIPT: 'Réception', ENTRY: 'Entrée', CONSUMPTION: 'Sortie (OT)', ADJUSTMENT: 'Ajustement', }; // ————— Tiers ————— export const PartnerSchema = z.object({ id: z.uuid(), name: z.string(), kind: z.enum(PARTNER_KINDS), contactName: z.string().nullable(), phone: z.string().nullable(), email: z.string().nullable(), city: z.string().nullable(), isActive: z.boolean(), openOrders: z.number().int(), // BC non reçus/annulés (fournisseur) siteNames: z.array(z.string()), // sites gérés (client/syndic) — maquette « Rattachements » }); export type PartnerDto = z.infer; export const PartnersResponseSchema = z.object({ partners: z.array(PartnerSchema) }); export type PartnersResponse = z.infer; export const PartnerCreateSchema = z.object({ name: z.string().min(1).max(120), kind: z.enum(PARTNER_KINDS), contactName: z.string().max(120).optional(), phone: z.string().max(40).optional(), email: z.email().optional(), city: z.string().max(80).optional(), }); export type PartnerCreate = z.infer; export const PartnerUpdateSchema = PartnerCreateSchema.partial().extend({ isActive: z.boolean().optional(), }); export type PartnerUpdate = z.infer; // ————— Pièces & stock (dérivé des mouvements) ————— export const PartSchema = z.object({ id: z.uuid(), reference: z.string(), designation: z.string(), threshold: z.number().int(), lastUnitPrice: z.number().nullable(), compatible: z.string().nullable(), supplierId: z.uuid().nullable(), supplierName: z.string().nullable(), isActive: z.boolean(), stock: z.number().int(), // Σ mouvements — calculé, jamais stocké belowThreshold: z.boolean(), }); export type PartDto = z.infer; export const PartsResponseSchema = z.object({ parts: z.array(PartSchema) }); export type PartsResponse = z.infer; export const PartCreateSchema = z.object({ designation: z.string().min(1).max(200), threshold: z.number().int().min(0).optional(), supplierId: z.uuid().optional(), compatible: z.string().max(200).optional(), initialPrice: z.number().positive().optional(), // premier PU connu }); export type PartCreate = z.infer; export const PartUpdateSchema = z.object({ designation: z.string().min(1).max(200).optional(), threshold: z.number().int().min(0).optional(), supplierId: z.uuid().nullable().optional(), compatible: z.string().max(200).nullable().optional(), isActive: z.boolean().optional(), }); export type PartUpdate = z.infer; export const StockMovementSchema = z.object({ id: z.uuid(), kind: z.enum(STOCK_MOVEMENT_KINDS), quantity: z.number().int(), unitPrice: z.number().nullable(), reason: z.string().nullable(), workOrderReference: z.string().nullable(), purchaseOrderReference: z.string().nullable(), byName: z.string().nullable(), createdAt: z.iso.datetime(), }); export type StockMovementDto = z.infer; export const PartDetailSchema = PartSchema.extend({ movements: z.array(StockMovementSchema), // récents d'abord — le stock EST cette liste }); export type PartDetail = z.infer; /** Entrée manuelle (+) ou ajustement (±, motif requis). */ export const StockMovementCreateSchema = z.object({ kind: z.enum(['ENTRY', 'ADJUSTMENT']), quantity: z.number().int().refine((q) => q !== 0, 'Quantité non nulle requise'), reason: z.string().max(300).optional(), }); export type StockMovementCreate = z.infer; // ————— Bons de commande ————— export const PurchaseOrderLineSchema = z.object({ id: z.uuid(), partId: z.uuid(), partReference: z.string(), designation: z.string(), quantity: z.number().int(), unitPrice: z.number(), }); export const PurchaseOrderSchema = z.object({ id: z.uuid(), reference: z.string(), status: z.enum(PURCHASE_ORDER_STATUSES), supplierId: z.uuid(), supplierName: z.string(), lines: z.array(PurchaseOrderLineSchema), total: z.number(), sentAt: z.iso.datetime().nullable(), receivedAt: z.iso.datetime().nullable(), createdAt: z.iso.datetime(), }); export type PurchaseOrderDto = z.infer; export const PurchaseOrdersResponseSchema = z.object({ purchaseOrders: z.array(PurchaseOrderSchema), }); export type PurchaseOrdersResponse = z.infer; export const PurchaseOrderCreateSchema = z.object({ supplierId: z.uuid(), lines: z .array( z.object({ partId: z.uuid(), quantity: z.number().int().positive(), unitPrice: z.number().positive(), }), ) .min(1), }); export type PurchaseOrderCreate = z.infer; export const PurchaseOrderTransitionSchema = z.object({ to: z.enum(['SENT', 'RECEIVED', 'CANCELLED']), }); export type PurchaseOrderTransition = z.infer; // ————— Coûts sur OT ————— export const ConsumePartSchema = z.object({ partId: z.uuid(), quantity: z.number().int().positive(), }); export type ConsumePart = z.infer; export const LaborTimeCreateSchema = z.object({ minutes: z.number().int().positive().max(24 * 60), userId: z.uuid().optional(), // défaut : la personne connectée note: z.string().max(200).optional(), }); export type LaborTimeCreate = z.infer; export const WorkOrderCostsSchema = z.object({ parts: z.array( z.object({ id: z.uuid(), designation: z.string(), quantity: z.number().int(), unitPrice: z.number(), total: z.number(), }), ), labor: z.array( z.object({ id: z.uuid(), displayName: z.string(), minutes: z.number().int(), hourlyRate: z.number(), total: z.number(), note: z.string().nullable(), }), ), total: z.number(), }); export type WorkOrderCosts = z.infer;