21 KiB
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_fileest 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>/editGET /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/uploadGET /equipments/documents/<doc_id>/downloadPOST /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
- Trois schémas de documents et plusieurs conventions de chemin coexistent.
- Le nom affiché/téléchargé n'est pas toujours le nom original (la route
intervention stocke notamment le nom UUID comme
filename). - Une route d'upload équipement tente de renseigner
file_typeetfile_size, alors que ces colonnes ne sont pas déclarées surEquipmentDocument; les deux chemins d'upload ne sont donc pas équivalents et doivent être consolidés avant extension. - Les types métier sont riches pour les produits, mais absents pour les interventions et équipements.
- Le stockage de document produit est modélisé, mais son workflow d'upload/ download n'est pas complet.
Contract.contract_filen'est pas interrogeable comme un document.- 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.
- Une erreur après écriture physique mais avant commit DB peut laisser un fichier orphelin ; l'inverse peut laisser une ligne sans fichier.
- 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 racineUPLOAD_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: attachmentet 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 20–50 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
- Intervention, équipement et produit présents dans la liste globale.
- Recherche sur filename, titre, description et parent.
- Filtres type/source/date et filtres localisation.
- Pagination stable et conservation des paramètres d'URL.
- FDS : uniquement documents courants et accès produit autorisé.
- Lien vers chaque fiche métier et absence de lien si source interdite.
- Téléchargement autorisé, refusé et IDOR entre deux utilisateurs.
- Document DB présent mais fichier manquant : réponse contrôlée, jamais 500.
- Fichier présent sans ligne DB : diagnostic, aucune exposition publique.
- Parent supprimé/inaccessible : résultat masqué ou marqué orphelin selon politique validée.
- Aucun upload, suppression ou modification possible depuis
/documents/. - Wildcard
super_admin, rôles source et deny individuel. - Compatibilité avec noms spéciaux, extensions refusées et taille maximale.
- 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
- Le centre doit-il inclure les chemins historiques
Contract.contract_filedès la V1, ou les laisser dans les contrats ? - Le catalogue RBAC accepte-t-il deux nouvelles permissions
documents.viewetdocuments.download, ou la visibilité doit-elle être dérivée uniquement des permissions sources ? - Le téléchargement global doit-il être séparé de la consultation, ou
documents.viewsuffit-il pour la V1 ? - Quel est le type officiel des documents d'intervention et d'équipement actuellement sans classification ?
- Les documents prévention/personnel/contrats doivent-ils être inclus après une revue de confidentialité ?
- Faut-il afficher une localisation quand plusieurs emplacements stock sont possibles, et sous quel libellé ?
- Quel seuil de volume déclenche l'étude d'un index matérialisé ?
- La V1 reste-t-elle strictement consultation/recherche, sans document général non lié à un objet métier ?