# Task Documents — Design Spec ## Overview Ajout d'un système de documents attachés aux tickets (tasks). Les utilisateurs peuvent uploader des fichiers via drag & drop ou sélection, les visualiser (images, PDF) dans une modale plein écran, et les télécharger. ## Contraintes - **Taille max par fichier** : 50 Mo - **Types acceptés** : tous types de fichiers - **Nombre par ticket** : illimité - **Stockage** : filesystem local (`var/uploads/documents/`) - **Permissions** : ROLE_ADMIN pour créer/supprimer, ROLE_USER pour lire ## Backend ### Entité `TaskDocument` | Champ | Type | Description | |-------|------|-------------| | `id` | int (auto) | Clé primaire | | `task` | ManyToOne → Task | Ticket parent (CASCADE on delete) | | `originalName` | string (255) | Nom original du fichier uploadé | | `fileName` | string (255) | Nom unique sur disque (`{uuid}.{extension}`) | | `mimeType` | string (100) | Type MIME (ex: `image/png`, `application/pdf`) | | `size` | int | Taille en octets | | `createdAt` | DateTimeImmutable | Date d'upload | | `uploadedBy` | ManyToOne → User | Utilisateur ayant uploadé (SET NULL on delete) | ### Relation inverse sur Task - `Task.documents` : OneToMany → TaskDocument - Sérialisé dans le groupe `task:read` pour charger les documents avec le ticket ### Stockage filesystem - Répertoire : `var/uploads/documents/` - Nommage : `{uuid}.{extension}` — évite les collisions et les caractères spéciaux - Volume Docker dédié pour persister les uploads - Servir les fichiers via Nginx (`/uploads/{fileName}`) ou un endpoint Symfony dédié pour contrôle d'accès ### API Endpoints | Méthode | Route | Description | Accès | |---------|-------|-------------|-------| | `POST` | `/api/task_documents` | Upload multipart/form-data | ROLE_ADMIN | | `GET` | `/api/task_documents?task=/api/tasks/{id}` | Liste documents d'un ticket | ROLE_USER | | `GET` | `/api/task_documents/{id}` | Métadonnées d'un document | ROLE_USER | | `DELETE` | `/api/task_documents/{id}` | Supprime document + fichier | ROLE_ADMIN | ### State Processor — POST (`TaskDocumentProcessor`) 1. Reçoit le fichier via multipart/form-data + IRI de la task 2. Valide : fichier non vide, taille ≤ 50 Mo 3. Génère un UUID v4, extrait l'extension du nom original 4. Déplace le fichier uploadé dans `var/uploads/documents/{uuid}.{ext}` 5. Crée et persiste l'entité `TaskDocument` avec toutes les métadonnées 6. Set `uploadedBy` depuis le token JWT courant ### State Processor — DELETE 1. Récupère le `fileName` de l'entité 2. Supprime le fichier du filesystem (`var/uploads/documents/{fileName}`) 3. Supprime l'entité de la base de données ### Validation - Contrainte sur `originalName` : NotBlank - Contrainte sur `task` : NotNull - Validation dans le Processor : taille fichier ≤ 50 Mo, fichier présent dans la requête - PHP `upload_max_filesize` et `post_max_size` à configurer ≥ 50 Mo ### Configuration PHP/Nginx - `php.ini` : `upload_max_filesize = 50M`, `post_max_size = 55M` - Nginx : `client_max_body_size 55m;` ## Frontend ### Placement dans l'UI La zone de documents est placée **sous la description** dans le `TaskModal`, visible en mode édition. ### Composants à créer Tous dans `frontend/components/task/` : #### `TaskDocumentUpload.vue` - Zone drag & drop avec bordure pointillée - Texte : "Glisser des fichiers ici ou cliquer pour sélectionner" - Input file caché (`multiple`, `accept="*"`) - Événements : `dragover`, `dragleave`, `drop` pour le feedback visuel - Barre de progression par fichier pendant l'upload - Upload séquentiel ou parallèle (un POST multipart par fichier) - Émet un événement quand l'upload est terminé pour rafraîchir la liste #### `TaskDocumentList.vue` - Grille de cartes compactes pour chaque document - **Images** (`image/*`) : miniature 64x64 en `object-fit: cover`, chargée depuis l'URL du fichier - **Autres fichiers** : icône selon le type MIME : - PDF → icône PDF - Word/Excel → icônes Office - Archives → icône archive - Défaut → icône fichier générique - Informations affichées : nom original (tronqué si > ~30 chars), taille formatée (Ko/Mo) - Clic sur un document → ouvre `TaskDocumentPreview` - Bouton supprimer (visible uniquement pour ROLE_ADMIN, avec confirmation) #### `TaskDocumentPreview.vue` - Modale plein écran (overlay sombre semi-transparent) - Contenu selon le type : - **Images** (`image/*`) : `` centré, taille adaptative - **PDF** (`application/pdf`) : `