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:
@@ -37,6 +37,26 @@ import {
|
||||
UsersResponseSchema,
|
||||
UserUpdateSchema,
|
||||
} from './schemas/users-admin';
|
||||
import {
|
||||
AssigneesUpdateSchema,
|
||||
ChecklistItemSchema,
|
||||
ChecklistPatchSchema,
|
||||
CommentCreateSchema,
|
||||
ReferenceValueCreateSchema,
|
||||
ReferenceValueSchema,
|
||||
ReferenceValuesResponseSchema,
|
||||
ReferenceValueUpdateSchema,
|
||||
ReportUpsertSchema,
|
||||
RequestApproveSchema,
|
||||
RequestCreateSchema,
|
||||
RequestRejectSchema,
|
||||
RequestsResponseSchema,
|
||||
RequestSummarySchema,
|
||||
TransitionRequestSchema,
|
||||
WorkOrderCreateSchema,
|
||||
WorkOrderDetailSchema,
|
||||
WorkOrdersResponseSchema,
|
||||
} from './schemas/exploitation';
|
||||
|
||||
/**
|
||||
* Contrat d'API R0 — source unique de vérité (règle d'or ADR-001).
|
||||
@@ -367,6 +387,189 @@ export const API_CONTRACT: ApiOperation[] = [
|
||||
404: { description: 'Inconnue' },
|
||||
},
|
||||
},
|
||||
// ————— R2 · Exploitation —————
|
||||
{
|
||||
operationId: 'listWorkOrders',
|
||||
method: 'get',
|
||||
path: '/work-orders',
|
||||
summary: 'Ordres de travail (sans « voir autre » : seulement les siens)',
|
||||
tags: ['work-orders'],
|
||||
responses: {
|
||||
200: { description: 'Liste', name: 'WorkOrdersResponse', schema: WorkOrdersResponseSchema },
|
||||
},
|
||||
},
|
||||
{
|
||||
operationId: 'getWorkOrder',
|
||||
method: 'get',
|
||||
path: '/work-orders/{id}',
|
||||
summary: 'Fiche OT (activité, checklist, bilan, transitions autorisées, blocages de clôture)',
|
||||
tags: ['work-orders'],
|
||||
pathParams: ['id'],
|
||||
responses: {
|
||||
200: { description: 'Fiche', name: 'WorkOrderDetail', schema: WorkOrderDetailSchema },
|
||||
404: { description: 'Inconnu (ou hors de son périmètre)' },
|
||||
},
|
||||
},
|
||||
{
|
||||
operationId: 'createWorkOrder',
|
||||
method: 'post',
|
||||
path: '/work-orders',
|
||||
summary: 'Créer un OT (statut Ouvert)',
|
||||
tags: ['work-orders'],
|
||||
request: { name: 'WorkOrderCreate', schema: WorkOrderCreateSchema },
|
||||
responses: {
|
||||
201: { description: 'Créé', name: 'WorkOrderDetail', schema: WorkOrderDetailSchema },
|
||||
},
|
||||
},
|
||||
{
|
||||
operationId: 'transitionWorkOrder',
|
||||
method: 'post',
|
||||
path: '/work-orders/{id}/transition',
|
||||
summary: 'Changer l’état (machine à états stricte ; DONE exige bilan + checklist)',
|
||||
tags: ['work-orders'],
|
||||
pathParams: ['id'],
|
||||
request: { name: 'TransitionRequest', schema: TransitionRequestSchema },
|
||||
responses: {
|
||||
200: { description: 'État changé', name: 'WorkOrderDetail', schema: WorkOrderDetailSchema },
|
||||
409: { description: 'Transition interdite ou clôture bloquée (garde)' },
|
||||
},
|
||||
},
|
||||
{
|
||||
operationId: 'commentWorkOrder',
|
||||
method: 'post',
|
||||
path: '/work-orders/{id}/comments',
|
||||
summary: 'Commenter (activité chronologique)',
|
||||
tags: ['work-orders'],
|
||||
pathParams: ['id'],
|
||||
request: { name: 'CommentCreate', schema: CommentCreateSchema },
|
||||
responses: {
|
||||
201: { description: 'Commentaire ajouté', name: 'WorkOrderDetail', schema: WorkOrderDetailSchema },
|
||||
},
|
||||
},
|
||||
{
|
||||
operationId: 'setWorkOrderAssignees',
|
||||
method: 'put',
|
||||
path: '/work-orders/{id}/assignees',
|
||||
summary: 'Assigner (remplace la liste)',
|
||||
tags: ['work-orders'],
|
||||
pathParams: ['id'],
|
||||
request: { name: 'AssigneesUpdate', schema: AssigneesUpdateSchema },
|
||||
responses: {
|
||||
200: { description: 'Assignés', name: 'WorkOrderDetail', schema: WorkOrderDetailSchema },
|
||||
},
|
||||
},
|
||||
{
|
||||
operationId: 'upsertWorkOrderReport',
|
||||
method: 'put',
|
||||
path: '/work-orders/{id}/report',
|
||||
summary: 'Renseigner le bilan codé (null efface un champ)',
|
||||
tags: ['work-orders'],
|
||||
pathParams: ['id'],
|
||||
request: { name: 'ReportUpsert', schema: ReportUpsertSchema },
|
||||
responses: {
|
||||
200: { description: 'Bilan enregistré', name: 'WorkOrderDetail', schema: WorkOrderDetailSchema },
|
||||
400: { description: 'Valeur hors référentiel du champ' },
|
||||
},
|
||||
},
|
||||
{
|
||||
operationId: 'patchChecklistItem',
|
||||
method: 'patch',
|
||||
path: '/work-orders/{id}/checklist/{itemId}',
|
||||
summary: 'Régler une tâche de la grille (Fait / N-A / à faire)',
|
||||
tags: ['work-orders'],
|
||||
pathParams: ['id', 'itemId'],
|
||||
request: { name: 'ChecklistPatch', schema: ChecklistPatchSchema },
|
||||
responses: {
|
||||
200: { description: 'Tâche réglée', name: 'ChecklistItem', schema: ChecklistItemSchema },
|
||||
404: { description: 'Tâche inconnue' },
|
||||
},
|
||||
},
|
||||
{
|
||||
operationId: 'listRequests',
|
||||
method: 'get',
|
||||
path: '/requests',
|
||||
summary: 'Demandes (sans « voir autre » : seulement les siennes)',
|
||||
tags: ['requests'],
|
||||
responses: {
|
||||
200: { description: 'Liste', name: 'RequestsResponse', schema: RequestsResponseSchema },
|
||||
},
|
||||
},
|
||||
{
|
||||
operationId: 'createRequest',
|
||||
method: 'post',
|
||||
path: '/requests',
|
||||
summary: 'Signaler (interne — le portail public QR arrive en R2.4)',
|
||||
tags: ['requests'],
|
||||
request: { name: 'RequestCreate', schema: RequestCreateSchema },
|
||||
responses: {
|
||||
201: { description: 'Demande créée', name: 'RequestSummary', schema: RequestSummarySchema },
|
||||
},
|
||||
},
|
||||
{
|
||||
operationId: 'approveRequest',
|
||||
method: 'post',
|
||||
path: '/requests/{id}/approve',
|
||||
summary: 'Approuver → crée l’OT lié (1-1, jamais de doublon)',
|
||||
tags: ['requests'],
|
||||
pathParams: ['id'],
|
||||
request: { name: 'RequestApprove', schema: RequestApproveSchema },
|
||||
responses: {
|
||||
201: { description: 'OT créé et lié', name: 'WorkOrderDetail', schema: WorkOrderDetailSchema },
|
||||
409: { description: 'Demande déjà traitée' },
|
||||
},
|
||||
},
|
||||
{
|
||||
operationId: 'rejectRequest',
|
||||
method: 'post',
|
||||
path: '/requests/{id}/reject',
|
||||
summary: 'Rejeter — motif obligatoire, lisible côté demandeur',
|
||||
tags: ['requests'],
|
||||
pathParams: ['id'],
|
||||
request: { name: 'RequestReject', schema: RequestRejectSchema },
|
||||
responses: {
|
||||
200: { description: 'Rejetée', name: 'RequestSummary', schema: RequestSummarySchema },
|
||||
409: { description: 'Demande déjà traitée' },
|
||||
},
|
||||
},
|
||||
{
|
||||
operationId: 'listReferenceValues',
|
||||
method: 'get',
|
||||
path: '/reference-values',
|
||||
summary: 'Référentiels du bilan codé (6 champs)',
|
||||
tags: ['reference-values'],
|
||||
responses: {
|
||||
200: {
|
||||
description: 'Liste',
|
||||
name: 'ReferenceValuesResponse',
|
||||
schema: ReferenceValuesResponseSchema,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
operationId: 'createReferenceValue',
|
||||
method: 'post',
|
||||
path: '/reference-values',
|
||||
summary: 'Ajouter une valeur de référentiel',
|
||||
tags: ['reference-values'],
|
||||
request: { name: 'ReferenceValueCreate', schema: ReferenceValueCreateSchema },
|
||||
responses: {
|
||||
201: { description: 'Créée', name: 'ReferenceValue', schema: ReferenceValueSchema },
|
||||
409: { description: 'Libellé déjà présent pour ce champ' },
|
||||
},
|
||||
},
|
||||
{
|
||||
operationId: 'updateReferenceValue',
|
||||
method: 'patch',
|
||||
path: '/reference-values/{id}',
|
||||
summary: 'Renommer ou (dés)activer — jamais de suppression si utilisée',
|
||||
tags: ['reference-values'],
|
||||
pathParams: ['id'],
|
||||
request: { name: 'ReferenceValueUpdate', schema: ReferenceValueUpdateSchema },
|
||||
responses: {
|
||||
200: { description: 'Mise à jour', name: 'ReferenceValue', schema: ReferenceValueSchema },
|
||||
404: { description: 'Inconnue' },
|
||||
},
|
||||
},
|
||||
{
|
||||
operationId: 'getHealth',
|
||||
method: 'get',
|
||||
|
||||
93
packages/shared/src/exploitation.ts
Normal file
93
packages/shared/src/exploitation.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
/** Vocabulaires de l'exploitation (R2) — partagés API / web / seed. */
|
||||
|
||||
export const WORK_ORDER_TYPES = ['CORRECTIVE', 'PREVENTIVE', 'WORKS'] as const;
|
||||
export type WorkOrderType = (typeof WORK_ORDER_TYPES)[number];
|
||||
export const WORK_ORDER_TYPE_LABELS: Record<WorkOrderType, string> = {
|
||||
CORRECTIVE: 'Dépannage',
|
||||
PREVENTIVE: 'Maintenance',
|
||||
WORKS: 'Travaux',
|
||||
};
|
||||
|
||||
export const WORK_ORDER_STATUSES = [
|
||||
'OPEN',
|
||||
'IN_PROGRESS',
|
||||
'ON_HOLD',
|
||||
'DONE',
|
||||
'CANCELLED',
|
||||
] as const;
|
||||
export type WorkOrderStatus = (typeof WORK_ORDER_STATUSES)[number];
|
||||
export const WORK_ORDER_STATUS_LABELS: Record<WorkOrderStatus, string> = {
|
||||
OPEN: 'Ouvert',
|
||||
IN_PROGRESS: 'En cours',
|
||||
ON_HOLD: 'En attente',
|
||||
DONE: 'Terminé',
|
||||
CANCELLED: 'Annulé',
|
||||
};
|
||||
|
||||
/** Machine à états STRICTE — la table est la loi, côté API comme côté UI. */
|
||||
export const WORK_ORDER_TRANSITIONS: Record<WorkOrderStatus, WorkOrderStatus[]> = {
|
||||
OPEN: ['IN_PROGRESS', 'CANCELLED'],
|
||||
IN_PROGRESS: ['ON_HOLD', 'DONE', 'CANCELLED'],
|
||||
ON_HOLD: ['IN_PROGRESS', 'CANCELLED'],
|
||||
DONE: [],
|
||||
CANCELLED: [],
|
||||
};
|
||||
|
||||
export const WORK_ORDER_PRIORITIES = [
|
||||
'NONE',
|
||||
'LOW',
|
||||
'MEDIUM',
|
||||
'HIGH',
|
||||
'PERSON_TRAPPED',
|
||||
] as const;
|
||||
export type WorkOrderPriority = (typeof WORK_ORDER_PRIORITIES)[number];
|
||||
export const WORK_ORDER_PRIORITY_LABELS: Record<WorkOrderPriority, string> = {
|
||||
NONE: 'Aucune',
|
||||
LOW: 'Basse',
|
||||
MEDIUM: 'Moyenne',
|
||||
HIGH: 'Haute',
|
||||
PERSON_TRAPPED: 'Personne bloquée',
|
||||
};
|
||||
|
||||
export const REQUEST_STATUSES = ['RECEIVED', 'APPROVED', 'REJECTED'] as const;
|
||||
export type RequestStatus = (typeof REQUEST_STATUSES)[number];
|
||||
export const REQUEST_STATUS_LABELS: Record<RequestStatus, string> = {
|
||||
RECEIVED: 'Reçue',
|
||||
APPROVED: 'Approuvée',
|
||||
REJECTED: 'Rejetée',
|
||||
};
|
||||
|
||||
export const CHECKLIST_STATES = ['PENDING', 'DONE', 'NA'] as const;
|
||||
export type ChecklistState = (typeof CHECKLIST_STATES)[number];
|
||||
|
||||
export const BILAN_FIELDS = [
|
||||
'DOOR_STATE',
|
||||
'CABIN_POSITION',
|
||||
'ANOMALY',
|
||||
'EXTERNAL_CAUSE',
|
||||
'ACTION_TAKEN',
|
||||
'COMPONENT_CONCERNED',
|
||||
] as const;
|
||||
export type BilanField = (typeof BILAN_FIELDS)[number];
|
||||
export const BILAN_FIELD_LABELS: Record<BilanField, string> = {
|
||||
DOOR_STATE: 'État des portes',
|
||||
CABIN_POSITION: 'Position cabine',
|
||||
ANOMALY: 'Anomalie constatée',
|
||||
EXTERNAL_CAUSE: 'Cause extérieure',
|
||||
ACTION_TAKEN: 'Action réalisée',
|
||||
COMPONENT_CONCERNED: 'Élément concerné',
|
||||
};
|
||||
|
||||
/** Champs du bilan REQUIS pour clôturer (maquette fiche OT validée R0). */
|
||||
export const REQUIRED_BILAN_FIELDS: readonly BilanField[] = [
|
||||
'DOOR_STATE',
|
||||
'ACTION_TAKEN',
|
||||
'COMPONENT_CONCERNED',
|
||||
];
|
||||
|
||||
export const METER_KINDS = ['RUNNING_HOURS', 'STARTS'] as const;
|
||||
export type MeterKind = (typeof METER_KINDS)[number];
|
||||
export const METER_KIND_LABELS: Record<MeterKind, string> = {
|
||||
RUNNING_HOURS: 'Heures de marche',
|
||||
STARTS: 'Démarrages',
|
||||
};
|
||||
@@ -1,5 +1,7 @@
|
||||
export * from './permissions';
|
||||
export * from './referentiel';
|
||||
export * from './exploitation';
|
||||
export * from './schemas/exploitation';
|
||||
export * from './schemas/auth';
|
||||
export * from './schemas/users';
|
||||
export * from './schemas/users-admin';
|
||||
|
||||
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