# 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 ?