mirror of
https://github.com/siop-spelev/siop2.git
synced 2026-08-08 12:41:54 +00:00
feat(r2.1): socle backend exploitation — machine à états, bilan codé, demandes 1-1
- migration r2_exploitation (10 tables) : WorkOrder (référence séquentielle, horodatages), WorkOrderEvent, Request (1-1, motif de rejet), ReferenceValue, InterventionReport (6 FK), TaskTemplate/ChecklistItem, Meter/MeterReading - contrat : 15 opérations (41 total) ; la table des transitions et les champs requis du bilan vivent dans @siop/shared ; la fiche OT expose allowedTransitions + closureBlockers (messages métier) - API : machine à états stricte ; garde de clôture (bilan 3 champs requis + checklist sans tâche en attente) ; approbation → OT lié 1-1 (409 si déjà traitée) ; rejet à motif obligatoire ; scoping « voir autre » sur listes et accès directs (404 sans fuite) ; validation des valeurs de bilan par champ ; « personne bloquée » triée en tête côté API - seed : 31 valeurs de référentiels, 8 gabarits (parachute réglementaire), OT/demandes/compteurs de la maquette — idempotent - 45 tests verts (95 % stmts / 79 % branches) dont la recette officielle rejouée de bout en bout ; smoke test sur build de prod Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
209
packages/shared/src/schemas/exploitation.ts
Normal file
209
packages/shared/src/schemas/exploitation.ts
Normal file
@@ -0,0 +1,209 @@
|
||||
import { z } from 'zod';
|
||||
import {
|
||||
BILAN_FIELDS,
|
||||
CHECKLIST_STATES,
|
||||
REQUEST_STATUSES,
|
||||
WORK_ORDER_PRIORITIES,
|
||||
WORK_ORDER_STATUSES,
|
||||
WORK_ORDER_TYPES,
|
||||
} from '../exploitation';
|
||||
|
||||
const PersonneSchema = z.object({
|
||||
id: z.uuid(),
|
||||
displayName: z.string(),
|
||||
initials: z.string(),
|
||||
});
|
||||
|
||||
// ————— Ordres de travail —————
|
||||
|
||||
export const WorkOrderSummarySchema = z.object({
|
||||
id: z.uuid(),
|
||||
reference: z.string(),
|
||||
title: z.string(),
|
||||
type: z.enum(WORK_ORDER_TYPES),
|
||||
status: z.enum(WORK_ORDER_STATUSES),
|
||||
priority: z.enum(WORK_ORDER_PRIORITIES),
|
||||
assetId: z.uuid(),
|
||||
assetReference: z.string(),
|
||||
siteName: z.string(),
|
||||
dueDate: z.iso.datetime().nullable(),
|
||||
assignees: z.array(PersonneSchema),
|
||||
createdAt: z.iso.datetime(),
|
||||
});
|
||||
export type WorkOrderSummary = z.infer<typeof WorkOrderSummarySchema>;
|
||||
|
||||
export const WorkOrdersResponseSchema = z.object({
|
||||
workOrders: z.array(WorkOrderSummarySchema),
|
||||
});
|
||||
export type WorkOrdersResponse = z.infer<typeof WorkOrdersResponseSchema>;
|
||||
|
||||
export const WorkOrderEventSchema = z.object({
|
||||
id: z.uuid(),
|
||||
kind: z.string(),
|
||||
message: z.string().nullable(),
|
||||
by: PersonneSchema.nullable(),
|
||||
createdAt: z.iso.datetime(),
|
||||
});
|
||||
|
||||
export const ChecklistItemSchema = z.object({
|
||||
id: z.uuid(),
|
||||
label: z.string(),
|
||||
state: z.enum(CHECKLIST_STATES),
|
||||
doneBy: PersonneSchema.nullable(),
|
||||
doneAt: z.iso.datetime().nullable(),
|
||||
});
|
||||
export type ChecklistItemDto = z.infer<typeof ChecklistItemSchema>;
|
||||
|
||||
const BilanValueSchema = z.object({ id: z.uuid(), label: z.string() }).nullable();
|
||||
|
||||
export const InterventionReportSchema = z.object({
|
||||
note: z.string().nullable(),
|
||||
doorState: BilanValueSchema,
|
||||
cabinPosition: BilanValueSchema,
|
||||
anomaly: BilanValueSchema,
|
||||
externalCause: BilanValueSchema,
|
||||
actionTaken: BilanValueSchema,
|
||||
componentConcerned: BilanValueSchema,
|
||||
});
|
||||
export type InterventionReportDto = z.infer<typeof InterventionReportSchema>;
|
||||
|
||||
export const WorkOrderDetailSchema = WorkOrderSummarySchema.extend({
|
||||
description: z.string().nullable(),
|
||||
locationName: z.string(),
|
||||
startedAt: z.iso.datetime().nullable(),
|
||||
completedAt: z.iso.datetime().nullable(),
|
||||
cancelledAt: z.iso.datetime().nullable(),
|
||||
createdBy: PersonneSchema.nullable(),
|
||||
request: z
|
||||
.object({ id: z.uuid(), reference: z.string(), requesterLabel: z.string() })
|
||||
.nullable(),
|
||||
events: z.array(WorkOrderEventSchema),
|
||||
checklist: z.array(ChecklistItemSchema),
|
||||
report: InterventionReportSchema.nullable(),
|
||||
/** Ce que la machine à états autorise depuis l'état courant. */
|
||||
allowedTransitions: z.array(z.enum(WORK_ORDER_STATUSES)),
|
||||
/** Ce qui bloque la clôture (vide = clôturable) — messages métier. */
|
||||
closureBlockers: z.array(z.string()),
|
||||
});
|
||||
export type WorkOrderDetail = z.infer<typeof WorkOrderDetailSchema>;
|
||||
|
||||
export const WorkOrderCreateSchema = z.object({
|
||||
title: z.string().min(1).max(200),
|
||||
description: z.string().max(2000).optional(),
|
||||
type: z.enum(WORK_ORDER_TYPES),
|
||||
priority: z.enum(WORK_ORDER_PRIORITIES).optional(),
|
||||
assetId: z.uuid(),
|
||||
dueDate: z.iso.datetime().optional(),
|
||||
assigneeIds: z.array(z.uuid()).optional(),
|
||||
});
|
||||
export type WorkOrderCreate = z.infer<typeof WorkOrderCreateSchema>;
|
||||
|
||||
export const TransitionRequestSchema = z.object({
|
||||
to: z.enum(WORK_ORDER_STATUSES),
|
||||
comment: z.string().max(500).optional(),
|
||||
});
|
||||
export type TransitionRequest = z.infer<typeof TransitionRequestSchema>;
|
||||
|
||||
export const CommentCreateSchema = z.object({
|
||||
message: z.string().min(1).max(1000),
|
||||
});
|
||||
export type CommentCreate = z.infer<typeof CommentCreateSchema>;
|
||||
|
||||
export const AssigneesUpdateSchema = z.object({
|
||||
assigneeIds: z.array(z.uuid()).max(10),
|
||||
});
|
||||
export type AssigneesUpdate = z.infer<typeof AssigneesUpdateSchema>;
|
||||
|
||||
/** Upsert du bilan — null efface un champ. */
|
||||
export const ReportUpsertSchema = z.object({
|
||||
note: z.string().max(2000).nullable().optional(),
|
||||
doorStateId: z.uuid().nullable().optional(),
|
||||
cabinPositionId: z.uuid().nullable().optional(),
|
||||
anomalyId: z.uuid().nullable().optional(),
|
||||
externalCauseId: z.uuid().nullable().optional(),
|
||||
actionTakenId: z.uuid().nullable().optional(),
|
||||
componentConcernedId: z.uuid().nullable().optional(),
|
||||
});
|
||||
export type ReportUpsert = z.infer<typeof ReportUpsertSchema>;
|
||||
|
||||
export const ChecklistPatchSchema = z.object({
|
||||
state: z.enum(CHECKLIST_STATES),
|
||||
});
|
||||
export type ChecklistPatch = z.infer<typeof ChecklistPatchSchema>;
|
||||
|
||||
// ————— Demandes —————
|
||||
|
||||
export const RequestSummarySchema = z.object({
|
||||
id: z.uuid(),
|
||||
reference: z.string(),
|
||||
description: z.string(),
|
||||
isPersonTrapped: z.boolean(),
|
||||
status: z.enum(REQUEST_STATUSES),
|
||||
rejectionReason: z.string().nullable(),
|
||||
assetId: z.uuid(),
|
||||
assetReference: z.string(),
|
||||
siteName: z.string(),
|
||||
requesterLabel: z.string(),
|
||||
workOrder: z
|
||||
.object({
|
||||
id: z.uuid(),
|
||||
reference: z.string(),
|
||||
status: z.enum(WORK_ORDER_STATUSES),
|
||||
})
|
||||
.nullable(),
|
||||
createdAt: z.iso.datetime(),
|
||||
});
|
||||
export type RequestSummary = z.infer<typeof RequestSummarySchema>;
|
||||
|
||||
export const RequestsResponseSchema = z.object({
|
||||
requests: z.array(RequestSummarySchema),
|
||||
});
|
||||
export type RequestsResponse = z.infer<typeof RequestsResponseSchema>;
|
||||
|
||||
export const RequestCreateSchema = z.object({
|
||||
assetId: z.uuid(),
|
||||
description: z.string().min(1).max(2000),
|
||||
isPersonTrapped: z.boolean().optional(),
|
||||
});
|
||||
export type RequestCreate = z.infer<typeof RequestCreateSchema>;
|
||||
|
||||
export const RequestApproveSchema = z.object({
|
||||
title: z.string().min(1).max(200).optional(), // défaut : description tronquée
|
||||
priority: z.enum(WORK_ORDER_PRIORITIES).optional(),
|
||||
assigneeIds: z.array(z.uuid()).optional(),
|
||||
dueDate: z.iso.datetime().optional(),
|
||||
});
|
||||
export type RequestApprove = z.infer<typeof RequestApproveSchema>;
|
||||
|
||||
export const RequestRejectSchema = z.object({
|
||||
reason: z.string().min(3, 'Le motif est requis').max(500),
|
||||
});
|
||||
export type RequestReject = z.infer<typeof RequestRejectSchema>;
|
||||
|
||||
// ————— Référentiels du bilan codé —————
|
||||
|
||||
export const ReferenceValueSchema = z.object({
|
||||
id: z.uuid(),
|
||||
field: z.enum(BILAN_FIELDS),
|
||||
label: z.string(),
|
||||
isActive: z.boolean(),
|
||||
usageCount: z.number().int(),
|
||||
});
|
||||
export type ReferenceValueDto = z.infer<typeof ReferenceValueSchema>;
|
||||
|
||||
export const ReferenceValuesResponseSchema = z.object({
|
||||
referenceValues: z.array(ReferenceValueSchema),
|
||||
});
|
||||
export type ReferenceValuesResponse = z.infer<typeof ReferenceValuesResponseSchema>;
|
||||
|
||||
export const ReferenceValueCreateSchema = z.object({
|
||||
field: z.enum(BILAN_FIELDS),
|
||||
label: z.string().min(1).max(120),
|
||||
});
|
||||
export type ReferenceValueCreate = z.infer<typeof ReferenceValueCreateSchema>;
|
||||
|
||||
export const ReferenceValueUpdateSchema = z.object({
|
||||
label: z.string().min(1).max(120).optional(),
|
||||
isActive: z.boolean().optional(),
|
||||
});
|
||||
export type ReferenceValueUpdate = z.infer<typeof ReferenceValueUpdateSchema>;
|
||||
Reference in New Issue
Block a user