gmao/docs/ui-audit/DOCUMENT_CENTER_PROPOSAL.md
root 8e6a67915c
Some checks are pending
CI - Tests et Syntax / lint-and-test (push) Waiting to run
docs(audit): proposer le centre documentaire global
2026-08-22 21:33:59 +00:00

21 KiB
Raw Blame History

Proposition d'architecture — Centre documentaire global

État : étude uniquement, aucune route, table, migration ou modification du code n'est créée par cette proposition.

A. État documentaire actuel

Le dépôt ne possède pas de modèle Document générique ni de table commune. Les documents sont spécialisés par module :

Modèle Table Parent métier Métadonnées État UI / routes
InterventionDocument intervention_documents Intervention obligatoire filename, filepath, description, uploaded_at, uploaded_by_id upload, édition de description, téléchargement et suppression depuis la fiche intervention
EquipmentDocument equipment_documents Equipment obligatoire filename, filepath, description, uploaded_at, uploaded_by_id deux familles de routes et upload depuis la fiche équipement
ProductDocument cleaning_product_documents CommercialProduct obligatoire document_type, title, filename, filepath, version, published_at, uploaded_at, uploaded_by_id, is_current liste FDS/fiches techniques ; aucun upload ou téléchargement produit clairement exposé dans les routes actuelles

Le modèle Contract possède en plus un champ historique contract_file qui est un simple chemin de fichier, sans ligne documentaire ni métadonnées associées. Aucun modèle documentaire dédié aux entreprises, à la prévention ou aux contrats n'a été trouvé. Les pièces jointes Outlook/ENT sont un autre système d'intégration et restent hors périmètre de ce centre documentaire.

Le centre devra donc fonctionner par adaptateurs de sources et non par copie des objets existants.

B. Modèles et relations existants

Interventions

Intervention.documents est une relation vers InterventionDocument. Le document pointe vers l'intervention par intervention_id; l'uploader est facultatif (users.id, SET NULL côté modèle logique). Il n'existe pas de type métier normalisé pour le document.

Équipements

Equipment.documents est une relation vers EquipmentDocument. Le parent est obligatoire. La localisation doit être calculée via l'équipement, éventuellement son parent et effective_room, puis Room → Zone → Building.

Produits d'entretien

CommercialProduct.documents est une relation vers ProductDocument. Le produit commercial remonte vers le produit générique, le fabricant et le fournisseur. Une localisation éventuelle doit être calculée via les lots, balances et StockLocation → Building/Zone/Room; elle ne doit pas être recopiée dans le document.

Ce qui n'est pas encore un document global

  • Contract.contract_file est un chemin historique, pas un document normalisé.
  • Outlook/ENT ont leurs propres pièces jointes distantes et ne doivent pas être aspirés dans le centre pendant cette phase.
  • Les photos, plans, certificats ou documents de prévention n'ont pas de modèle local identifié dans le dépôt.

C. Stockage actuel

La configuration définit UPLOAD_FOLDER sous app_new/uploads. Docker monte ce répertoire dans le volume gmao_uploads (/app/app_new/uploads). La limite globale est de 50 MiB.

Les routes historiques utilisent actuellement plusieurs conventions :

  • intervention : uploads/interventions/<uuid>_<secure_filename> ;
  • équipement, blueprint documents : uploads/equipments/<uuid>_<secure_filename> ;
  • équipement, blueprint equipments_documents : uploads/documents/<equipment_id>/<uuid>.<extension> ;
  • produit : le modèle stocke un chemin, mais aucune route locale d'upload produit n'a été identifiée.

Le fichier physique est donc unique aujourd'hui, mais l'organisation n'est pas homogène. Les noms sont générés avec UUID ; secure_filename réduit le risque de traversal. Les extensions sont allowlistées (pdf, images, documents Office), mais le MIME réel n'est pas inspecté. Les modèles ne stockent généralement ni MIME, ni taille, ni hash, et le nom original n'est pas distingué proprement du nom interne dans toutes les routes.

Le volume n'est pas servi comme répertoire statique public : les téléchargements passent par send_file. Cependant, plusieurs téléchargements font confiance directement au filepath DB et certaines routes ne vérifient pas l'existence du fichier avant send_file.

État observé sur la copie locale au moment de l'étude : 2 lignes intervention_documents, 0 ligne equipment_documents et 0 ligne cleaning_product_documents. Les deux fichiers d'intervention présents dans le volume correspondent aux deux lignes DB. Ce constat est un instantané, pas une hypothèse de conception.

D. Routes actuelles

Blueprint documents (/documents)

  • POST /documents/upload/intervention/<id>
  • POST /documents/upload/equipment/<id>
  • POST /documents/intervention/delete/<doc_id>
  • POST /documents/intervention/<doc_id>/edit
  • GET /documents/intervention/download/<doc_id>
  • POST /documents/equipment/delete/<doc_id>
  • GET /documents/equipment/download/<doc_id>

Ces routes utilisent login_required et la garde globale d'autorisation, qui déduit actuellement intervention.view/manage ou patrimoine.view/manage du nom d'endpoint.

Blueprint equipments_documents (préfixé /equipments)

  • POST /equipments/<id>/documents/upload
  • GET /equipments/documents/<doc_id>/download
  • POST /equipments/documents/<doc_id>/delete

Il s'agit d'une seconde famille d'upload/téléchargement pour les équipements. Elle utilise un autre répertoire et un autre format de nom. Cette duplication de routes devra être résolue ou encapsulée avant d'exposer un index global.

Produits

GET /cleaning/documents liste les ProductDocument, mais n'offre pas de route globale, ni de téléchargement produit identifié dans le code inspecté. GET /documents/ n'est donc pas l'index attendu par UI-011 et répond 404.

E. Problèmes actuels à traiter avant ou pendant le MVP

  1. Trois schémas de documents et plusieurs conventions de chemin coexistent.
  2. Le nom affiché/téléchargé n'est pas toujours le nom original (la route intervention stocke notamment le nom UUID comme filename).
  3. Une route d'upload équipement tente de renseigner file_type et file_size, alors que ces colonnes ne sont pas déclarées sur EquipmentDocument ; les deux chemins d'upload ne sont donc pas équivalents et doivent être consolidés avant extension.
  4. Les types métier sont riches pour les produits, mais absents pour les interventions et équipements.
  5. Le stockage de document produit est modélisé, mais son workflow d'upload/ download n'est pas complet.
  6. Contract.contract_file n'est pas interrogeable comme un document.
  7. Les routes de téléchargement doivent vérifier parent, permission et fichier sous la racine autorisée afin d'éviter IDOR et chemins arbitraires.
  8. Une erreur après écriture physique mais avant commit DB peut laisser un fichier orphelin ; l'inverse peut laisser une ligne sans fichier.
  9. La validation par extension seule ne suffit pas pour les exécutables renommés et les fichiers malformés.

F. Architecture recommandée

Recommandation : option A pour la V1, avec adaptateurs de lecture

La V1 doit agréger les trois tables spécialisées sans créer une table de stockage ou recopier les fichiers. Un service de lecture produira un descripteur commun en mémoire, par exemple :

DocumentResult
  source_kind        intervention | equipment | product
  source_id          identifiant de la ligne documentaire
  parent_id          identifiant de l'objet métier
  display_name       nom lisible
  document_type      type métier normalisé à l'affichage
  description
  uploaded_at / published_at
  stored_path        utilisé seulement côté téléchargement sécurisé
  context_label      Intervention #… / Équipement … / Produit …
  context_url        URL de détail autorisée
  location           calculée par relations métier
  capabilities       view / download / edit / delete

Chaque adaptateur sait lire sa table, construire le contexte, appliquer la permission du module source et produire l'action de téléchargement vers la ligne propriétaire. Le centre est ainsi un index de recherche logique, pas un second propriétaire de fichier.

Pour une recherche SQL paginée, les adaptateurs peuvent exposer des sélections alignées combinées par UNION ALL (ou trois requêtes paginées fusionnées pour le premier incrément). Aucune modification de schéma n'est nécessaire pour la V1.

Pourquoi cette option

  • aucune migration ni backfill destructif ;
  • conservation immédiate des documents existants ;
  • aucun fichier copié ;
  • réutilisation des relations et permissions existantes ;
  • retour arrière simple : retirer la vue globale ne touche pas aux sources.

La contrepartie est une couche d'adaptation plus explicite et une pagination globale plus complexe si le volume devient très élevé.

G. Alternative étudiée : option B — index documentaire commun

Une table document_index pourrait contenir une ligne par document source, avec source_kind, source_document_id, parent_type, parent_id, type, dates et éventuellement un hash. Elle ne contiendrait pas une copie du fichier : le chemin et la suppression resteraient propriétaires de la table source.

Avantages

  • recherche et pagination uniformes ;
  • index SQL uniques et filtres simples ;
  • base future pour tags, OCR et classement ;
  • détection centralisée des documents orphelins.

Inconvénients et risques

  • migration et backfill des trois tables ;
  • synchronisation lors de chaque création, modification, suppression et changement de parent ;
  • gestion des documents historiques incomplets et de contract_file ;
  • risque de ligne d'index périmée ou d'un faux sentiment de stockage unifié ;
  • complexité supplémentaire avant d'avoir validé les besoins de recherche.

Recommandation de trajectoire

Ne pas créer cette table pour le MVP. La réévaluer si les mesures montrent que l'agrégation SQL dépasse les objectifs à 10 000+ documents ou si les tags, l'OCR et la recherche plein texte deviennent prioritaires. Dans ce cas, la table restera un index réparable, jamais le propriétaire physique du fichier.

H. Règle RBAC recommandée

Le centre ne doit jamais élargir l'accès d'un module.

Pour afficher un résultat, appliquer :

permission d'accès au Centre (documents.view)
ET
permission de lecture de la source

Exemples :

  • document d'intervention : documents.view + intervention.view ;
  • document équipement : documents.view + patrimoine.view ;
  • FDS produit : documents.view + stock.view ;
  • contrat futur : documents.view + contract.view ;
  • prévention future : documents.view + prevention.view.

Si documents.view n'est pas accordée, le menu et l'URL globale sont refusés. Si la source est refusée, le résultat est absent, même si sa ligne documentaire est connue.

Le téléchargement réévalue les deux permissions et l'existence du parent ; il ne doit jamais accepter uniquement un doc_id global. La suppression ou la modification reste dans la fiche source et utilise la permission de mutation du module (intervention.manage, patrimoine.manage, etc.). Il n'est pas recommandé d'introduire un vague documents.manage pour remplacer ces contrôles.

super_admin bénéficie de son wildcard existant. Le rôle admin et les rôles métier devront recevoir documents.view seulement après décision du catalogue RBAC ; aucune permission d'intégration ne doit être impliquée.

I. Classification métier proposée

V1 : un type principal, peu de valeurs et une valeur de repli :

  • FDS ;
  • Fiche technique ;
  • Notice / manuel ;
  • Facture ;
  • Devis ;
  • Rapport / compte rendu ;
  • Contrat ;
  • Certificat / contrôle ;
  • Photo / plan ;
  • Autre.

Pour les produits, ProductDocument.document_type est conservé et mappé vers les libellés métier. Pour les interventions et équipements, le type sera Autre tant qu'aucune donnée source ne le porte. Les tags et sous-types sont différés ; ils ne doivent pas être simulés dans description.

J. Recherche

Recherche simple visible

Un champ unique recherche : nom de fichier, titre, description, nom de l'objet parent, référence équipement, produit, intervention et entreprise accessible par relation.

SQL classique suffisant

Pour les volumes attendus, SQL/MariaDB suffit avec LIKE ciblé et des indexes sur les clés étrangères, dates, document_type, filename, uploaded_at et expiry/is_current côté produits. La recherche plein texte PDF, OCR et Elasticsearch sont explicitement hors MVP.

Résultats

Les requêtes doivent filtrer par source avant pagination et ne jamais charger en mémoire tous les fichiers. Le contenu binaire n'est jamais recherché.

K. Filtres

Visibles par défaut

  • Type de document ;
  • Source/module ;
  • période (date d'ajout ou de publication) ;
  • recherche libre.

Filtres avancés repliés

  • bâtiment, zone, salle ;
  • équipement ;
  • produit/FDS ;
  • entreprise ;
  • actif/courant pour les produits.

Les filtres non applicables à une source doivent rester neutres, pas produire un faux « aucun résultat ».

L. UI proposée pour /documents/

Titre : Centre documentaire. Sous le titre, une phrase explique que les fichiers restent attachés à leur fiche métier et sont seulement regroupés pour la recherche.

Recherche [ nom, titre, équipement, produit... ] [Rechercher]

Filtres principaux : Type | Source | Période
Filtres avancés : Bâtiment | Zone | Salle | Équipement | Produit | Entreprise

Document | Type | Lié à | Localisation | Date | Actions

Chaque ligne présente : nom lisible, icône non porteuse d'information seule, type, source (« Intervention #142 »), localisation calculée, date, bouton Télécharger si autorisé et lien Ouvrir la fiche. Aucun terme technique (source_kind, entity_type, nom de classe Python) ne doit être affiché.

La pagination est obligatoire, avec conservation des filtres dans l'URL. La V1 est consultation/recherche uniquement : aucun upload global et aucun bouton de suppression dans ce centre.

M. Vue FDS /documents/fds

Vue filtrée ou route dédiée sur les ProductDocument de type FDS et is_current=True. Colonnes : produit commercial, fabricant/fournisseur, version, date de publication, statut courant, localisation des balances de stock lorsque disponible, téléchargement et fiche produit.

Un état vide doit expliquer : « Les FDS sont ajoutées depuis la fiche du produit commercial » et proposer ce lien si stock.view le permet.

Le futur « classeur FDS » pourra réutiliser exactement le même service de sélection autorisé pour produire une liste ou un PDF ; aucune génération n'est incluse dans le MVP.

N. Documents et localisation

Ne pas dénormaliser les chemins bâtiment/zone/salle dans une nouvelle table.

  • équipement : Equipment → effective_room → Zone → Building ;
  • intervention : salle directe, sinon équipement lié ;
  • FDS produit : CommercialProduct → StockLot → StockLotBalance → StockLocation → Room/Zone/Building, en signalant « plusieurs emplacements » si nécessaire ;
  • contrat : relation équipement/lot/entreprise si le document historique est finalement inclus.

Si aucune localisation n'est calculable, afficher « Localisation non renseignée », jamais None ou un identifiant interne.

O. Sécurité

Avant développement, les contrôles suivants sont requis :

  • autoriser une liste d'extensions métier limitée et contrôler aussi le MIME réel / signatures connues ;
  • refuser exécutables, scripts et archives non prévues ;
  • conserver un nom original d'affichage séparé d'un nom interne UUID ;
  • résoudre le chemin avec Path.resolve() et vérifier qu'il reste sous la racine UPLOAD_FOLDER ;
  • vérifier parent, source et permission pour chaque download/delete ;
  • ne jamais exposer le volume par une route statique ;
  • définir explicitement Content-Disposition: attachment et un nom sûr ;
  • conserver la limite 50 MiB et afficher une erreur utilisateur claire ;
  • supprimer le fichier si l'insertion DB échoue, ou journaliser une tâche de réconciliation ;
  • fournir un diagnostic non destructif des lignes DB sans fichier et des fichiers sans ligne DB ;
  • ajouter des tests IDOR avec deux utilisateurs et deux sources.

Les documents de prévention, personnel et contrats peuvent contenir des données sensibles. Leur inclusion doit rester soumise à la permission source; aucun filtrage global par simple URL ne suffit.

P. Performance

Volume Stratégie V1 attendue
100 UNION ALL/adaptateurs, pagination 2050 lignes, eager loading des parents
1 000 mêmes principes, indexes FK/date/type, count limité aux lignes filtrées
10 000 mesurer les plans SQL ; envisager vue/index commun réparable si la fusion paginée devient lente

Indexes utiles côté sources : clés étrangères parent, uploaded_at, document_type, is_current, published_at, et éventuellement index composé parent/date. Aucun index de contenu PDF n'est proposé.

Q. Migrations éventuelles

Option A recommandée

Aucune migration nécessaire. Il faut seulement ajouter un service de lecture, des requêtes, des tests et une route protégée.

Option B différée

Une future migration créerait un index documentaire avec source_kind, source_document_id, parent_type, parent_id, type, dates et hash optionnels, puis un backfill idempotent. Elle ne déplacerait ni ne copierait les fichiers et devrait prévoir la réparation des entrées obsolètes. Elle serait déployée séparément après validation des volumes.

R. Compatibilité historique

La V1 lit les trois tables sans transformer les lignes existantes. Les documents sans type reçoivent Autre; les descriptions nulles restent vides; les noms UUID sont présentés avec une règle d'affichage documentée. Les fichiers manquants apparaissent éventuellement dans une vue d'administration diagnostique, mais ne sont jamais supprimés automatiquement.

Contract.contract_file doit faire l'objet d'une décision séparée : soit il reste exclu du MVP, soit il est adapté par un lecteur temporaire avec permission contract.view, soit il est converti plus tard en document source normalisé. Il ne faut pas le mélanger silencieusement aux trois tables.

S. Tests à prévoir avant implémentation

  1. Intervention, équipement et produit présents dans la liste globale.
  2. Recherche sur filename, titre, description et parent.
  3. Filtres type/source/date et filtres localisation.
  4. Pagination stable et conservation des paramètres d'URL.
  5. FDS : uniquement documents courants et accès produit autorisé.
  6. Lien vers chaque fiche métier et absence de lien si source interdite.
  7. Téléchargement autorisé, refusé et IDOR entre deux utilisateurs.
  8. Document DB présent mais fichier manquant : réponse contrôlée, jamais 500.
  9. Fichier présent sans ligne DB : diagnostic, aucune exposition publique.
  10. Parent supprimé/inaccessible : résultat masqué ou marqué orphelin selon politique validée.
  11. Aucun upload, suppression ou modification possible depuis /documents/.
  12. Wildcard super_admin, rôles source et deny individuel.
  13. Compatibilité avec noms spéciaux, extensions refusées et taille maximale.
  14. Non-régression des téléchargements depuis les fiches existantes.

T. MVP et évolutions

V1 obligatoire

  • GET /documents/ protégé ;
  • agrégation lecture seule des interventions, équipements et produits ;
  • recherche simple, pagination et trois filtres principaux ;
  • type/source/localisation lisibles ;
  • téléchargement sécurisé sans duplication ;
  • lien vers le parent métier ;
  • contrôle documents.view + permission source ;
  • vue FDS courante ;
  • états vides et messages d'accès compréhensibles ;
  • tests de sécurité, IDOR, fichier manquant et non-régression.

Plus tard

  • index commun matérialisé si le volume le justifie ;
  • tags métier ;
  • recherche plein texte/OCR ;
  • prévisualisation avancée ;
  • versionnement transversal ;
  • classeur FDS exportable ;
  • intégration explicite des contrats et documents généraux ;
  • diagnostic et réparation assistés des orphelins.

Questions nécessitant validation

  1. Le centre doit-il inclure les chemins historiques Contract.contract_file dès la V1, ou les laisser dans les contrats ?
  2. Le catalogue RBAC accepte-t-il deux nouvelles permissions documents.view et documents.download, ou la visibilité doit-elle être dérivée uniquement des permissions sources ?
  3. Le téléchargement global doit-il être séparé de la consultation, ou documents.view suffit-il pour la V1 ?
  4. Quel est le type officiel des documents d'intervention et d'équipement actuellement sans classification ?
  5. Les documents prévention/personnel/contrats doivent-ils être inclus après une revue de confidentialité ?
  6. Faut-il afficher une localisation quand plusieurs emplacements stock sont possibles, et sous quel libellé ?
  7. Quel seuil de volume déclenche l'étude d'un index matérialisé ?
  8. La V1 reste-t-elle strictement consultation/recherche, sans document général non lié à un objet métier ?