feat(r3.2): bibliothèque de documents (FileStorage réel) + analytics

- FileStorage : putObject/getObjectStream/removeObject, bucket créé au
  démarrage — MinIO confiné à son implémentation (règle ESLint intacte)
- documents : upload multipart (PDF/JPG/PNG, 20 Mo max, rattachement
  appareil OU OT requis, permission d'édition sur la CIBLE), liste
  filtrable, téléchargement STREAMÉ par l'API (MinIO jamais exposé),
  suppression ; e2e : octets téléchargés identiques aux octets envoyés
- analytics : GET /analytics/summary dérivé du réel — coûts/mois
  (mouvements + main-d'œuvre figés), pannes par organe (bilans codés),
  taux de préventif, durée moyenne de résolution, top équipements
- générateur OpenAPI : query params, multipart, réponse binaire (71 ops)
- CI : service MinIO (bitnami) sur les jobs api et e2e
- test de régression du tri « Interventions récentes » rendu déterministe
  (positions absolues instables sous 12 suites parallèles) ; 58 tests,
  6 runs complets consécutifs verts

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
pr-daaif
2026-07-16 21:24:04 +01:00
parent 32eb20b5a0
commit 2ffdd13cf4
23 changed files with 1652 additions and 30 deletions

View File

@@ -372,6 +372,75 @@ export interface paths {
patch: operations["updateUser"];
trace?: never;
};
"/documents": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/** Bibliothèque (filtrable par appareil ou OT) */
get: operations["listDocuments"];
put?: never;
/** Téléverser (PDF/JPG/PNG, 20 Mo max, rattachement appareil OU OT requis) */
post: operations["uploadDocument"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/documents/{id}/download": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/** Télécharger — streamé par lAPI (MinIO jamais exposé) */
get: operations["downloadDocument"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/documents/{id}": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
post?: never;
/** Supprimer (permission dédition sur la cible) */
delete: operations["deleteDocument"];
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/analytics/summary": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/** Le tableau de la direction : coûts, pannes par organe (bilans), préventif, top équipements */
get: operations["getAnalyticsSummary"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/partners": {
parameters: {
query?: never;
@@ -1297,6 +1366,62 @@ export interface components {
isActive?: boolean;
hourlyRate?: number | null;
};
DocumentsResponse: {
documents: {
/** Format: uuid */
id: string;
/** @enum {string} */
kind: "NOTICE" | "CERTIFICATE" | "PHOTO" | "OTHER";
fileName: string;
size: number;
contentType: string;
assetReference: string | null;
workOrderReference: string | null;
uploadedByName: string | null;
/** Format: date-time */
createdAt: string;
}[];
};
Document: {
/** Format: uuid */
id: string;
/** @enum {string} */
kind: "NOTICE" | "CERTIFICATE" | "PHOTO" | "OTHER";
fileName: string;
size: number;
contentType: string;
assetReference: string | null;
workOrderReference: string | null;
uploadedByName: string | null;
/** Format: date-time */
createdAt: string;
};
AnalyticsSummary: {
monthCost: number;
previousMonthCost: number;
closed12m: {
total: number;
preventive: number;
};
preventiveRate: number | null;
avgResolutionDays: number | null;
failuresByComponent: {
label: string;
count: number;
}[];
costsByMonth: {
month: string;
total: number;
}[];
topAssets: {
reference: string;
siteName: string;
correctives: number;
partsCost: number;
laborCost: number;
total: number;
}[];
};
PartnersResponse: {
partners: {
/** Format: uuid */
@@ -2647,6 +2772,141 @@ export interface operations {
};
};
};
listDocuments: {
parameters: {
query?: {
assetId?: string;
workOrderId?: string;
kind?: string;
};
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Liste */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["DocumentsResponse"];
};
};
};
};
uploadDocument: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody: {
content: {
"multipart/form-data": {
/** Format: binary */
file: string;
kind?: string;
assetId?: string;
workOrderId?: string;
};
};
};
responses: {
/** @description Document rangé */
201: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["Document"];
};
};
/** @description Type/taille refusé ou rattachement manquant */
400: {
headers: {
[name: string]: unknown;
};
content?: never;
};
};
};
downloadDocument: {
parameters: {
query?: never;
header?: never;
path: {
id: string;
};
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Fichier */
200: {
headers: {
[name: string]: unknown;
};
content?: never;
};
/** @description Inconnu */
404: {
headers: {
[name: string]: unknown;
};
content?: never;
};
};
};
deleteDocument: {
parameters: {
query?: never;
header?: never;
path: {
id: string;
};
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Supprimé */
204: {
headers: {
[name: string]: unknown;
};
content?: never;
};
/** @description Inconnu */
404: {
headers: {
[name: string]: unknown;
};
content?: never;
};
};
};
getAnalyticsSummary: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Synthèse */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["AnalyticsSummary"];
};
};
};
};
listPartners: {
parameters: {
query?: never;