mirror of
https://github.com/siop-spelev/siop2.git
synced 2026-08-08 20:51:53 +00:00
Arbitrage du référent sur la revue pixel : tout corriger, activer la recherche. - Stock : filtre fournisseur, sous-seuil en tête, « Entrée de stock » depuis la liste ; fiche pièce : fournisseur → lien Tiers. - Statistiques : période 3/6/12 mois (paramètre months au contrat). - Tiers : rattachements syndic→site (migration r3_recette_fixes, Location.partnerId gardé CLIENT), éditable sur la fiche site, seedé. - Bibliothèque : filtre « Rattaché à » + glisser-déposer (modale préremplie, rattachement toujours requis). - Recherche globale : GET /search (73 opérations) — familles OT/ ascenseurs/sites filtrées par la matrice, « voir autre » respecté ; topbar ⌘K, debounce, résultats groupés, navigation clavier. 74 tests API (8 nouveaux sur le scoping de la recherche), 14/14 Playwright dont un parcours « recette corrigée », 18/18 contrôles en navigateur réel, zéro erreur console. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
215 lines
6.9 KiB
TypeScript
215 lines
6.9 KiB
TypeScript
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<PartnerKind, string> = {
|
|
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<PurchaseOrderStatus, string> = {
|
|
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<StockMovementKind, string> = {
|
|
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<typeof PartnerSchema>;
|
|
|
|
export const PartnersResponseSchema = z.object({ partners: z.array(PartnerSchema) });
|
|
export type PartnersResponse = z.infer<typeof PartnersResponseSchema>;
|
|
|
|
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<typeof PartnerCreateSchema>;
|
|
|
|
export const PartnerUpdateSchema = PartnerCreateSchema.partial().extend({
|
|
isActive: z.boolean().optional(),
|
|
});
|
|
export type PartnerUpdate = z.infer<typeof PartnerUpdateSchema>;
|
|
|
|
// ————— 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<typeof PartSchema>;
|
|
|
|
export const PartsResponseSchema = z.object({ parts: z.array(PartSchema) });
|
|
export type PartsResponse = z.infer<typeof PartsResponseSchema>;
|
|
|
|
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<typeof PartCreateSchema>;
|
|
|
|
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<typeof PartUpdateSchema>;
|
|
|
|
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<typeof StockMovementSchema>;
|
|
|
|
export const PartDetailSchema = PartSchema.extend({
|
|
movements: z.array(StockMovementSchema), // récents d'abord — le stock EST cette liste
|
|
});
|
|
export type PartDetail = z.infer<typeof PartDetailSchema>;
|
|
|
|
/** 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<typeof StockMovementCreateSchema>;
|
|
|
|
// ————— 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<typeof PurchaseOrderSchema>;
|
|
|
|
export const PurchaseOrdersResponseSchema = z.object({
|
|
purchaseOrders: z.array(PurchaseOrderSchema),
|
|
});
|
|
export type PurchaseOrdersResponse = z.infer<typeof PurchaseOrdersResponseSchema>;
|
|
|
|
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<typeof PurchaseOrderCreateSchema>;
|
|
|
|
export const PurchaseOrderTransitionSchema = z.object({
|
|
to: z.enum(['SENT', 'RECEIVED', 'CANCELLED']),
|
|
});
|
|
export type PurchaseOrderTransition = z.infer<typeof PurchaseOrderTransitionSchema>;
|
|
|
|
// ————— Coûts sur OT —————
|
|
|
|
export const ConsumePartSchema = z.object({
|
|
partId: z.uuid(),
|
|
quantity: z.number().int().positive(),
|
|
});
|
|
export type ConsumePart = z.infer<typeof ConsumePartSchema>;
|
|
|
|
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<typeof LaborTimeCreateSchema>;
|
|
|
|
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<typeof WorkOrderCostsSchema>;
|