From 8e6a67915c1cd2363fefa91f84cfb02c586124f3 Mon Sep 17 00:00:00 2001 From: root Date: Sat, 22 Aug 2026 21:33:59 +0000 Subject: [PATCH] docs(audit): proposer le centre documentaire global --- docs/ui-audit/DOCUMENT_CENTER_PROPOSAL.md | 493 ++++++++++++++++++++++ docs/ui-audit/PROGRESS.md | 9 + 2 files changed, 502 insertions(+) create mode 100644 docs/ui-audit/DOCUMENT_CENTER_PROPOSAL.md diff --git a/docs/ui-audit/DOCUMENT_CENTER_PROPOSAL.md b/docs/ui-audit/DOCUMENT_CENTER_PROPOSAL.md new file mode 100644 index 0000000..736686e --- /dev/null +++ b/docs/ui-audit/DOCUMENT_CENTER_PROPOSAL.md @@ -0,0 +1,493 @@ +# 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/_` ; +- équipement, blueprint `documents` : `uploads/equipments/_` ; +- équipement, blueprint `equipments_documents` : + `uploads/documents//.` ; +- 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/` +- `POST /documents/upload/equipment/` +- `POST /documents/intervention/delete/` +- `POST /documents/intervention//edit` +- `GET /documents/intervention/download/` +- `POST /documents/equipment/delete/` +- `GET /documents/equipment/download/` + +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//documents/upload` +- `GET /equipments/documents//download` +- `POST /equipments/documents//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 : + +```text +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 : + +```text +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. + +```text +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 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 + +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 ? diff --git a/docs/ui-audit/PROGRESS.md b/docs/ui-audit/PROGRESS.md index 739fdbc..fe7e795 100644 --- a/docs/ui-audit/PROGRESS.md +++ b/docs/ui-audit/PROGRESS.md @@ -31,6 +31,15 @@ implémenté. Contrôle post-rebuild effectué : `/health/` répond 200 avec la version `0.1.0-dev.4`, le commit `b6fbfe6` et une base MariaDB connectée. +## Étude Centre documentaire global — 22 août 2026 + +Étude d'architecture terminée dans `DOCUMENT_CENTER_PROPOSAL.md`. Le dépôt +possède trois modèles documentaires spécialisés, plusieurs conventions de +stockage et aucune table commune. La recommandation est une agrégation de +lecture par adaptateurs pour la V1, sans migration ni copie de fichier, avec +contrôle conjoint `documents.view` et permission du module source. Aucune +modification applicative, route, migration ou donnée de test n'a été créée. + Couverture estimée : **environ 85 %**. Les principaux workflows patrimoine, interventions, stock, prévention et RBAC ont été testés localement ; restent partiels la recherche documentaire globale, certaines variantes de planning,