docs(audit): proposer le centre documentaire global
Some checks are pending
CI - Tests et Syntax / lint-and-test (push) Waiting to run

This commit is contained in:
root 2026-08-22 21:33:59 +00:00
parent c58bcbd5fb
commit 8e6a67915c
2 changed files with 502 additions and 0 deletions

View file

@ -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/<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 :
```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 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 ?

View file

@ -31,6 +31,15 @@ implémenté.
Contrôle post-rebuild effectué : `/health/` répond 200 avec la version 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. `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, Couverture estimée : **environ 85 %**. Les principaux workflows patrimoine,
interventions, stock, prévention et RBAC ont été testés localement ; restent interventions, stock, prévention et RBAC ont été testés localement ; restent
partiels la recherche documentaire globale, certaines variantes de planning, partiels la recherche documentaire globale, certaines variantes de planning,