5.5 KiB
Export temps suivi de temps (XLSX)
Ticket : LST-41 Date : 2026-03-24 Statut : Approuvé
Contexte
Les exports de suivi de temps sont nécessaires pour constituer des dossiers CIR (Crédit Impôt Recherche) et JEI (Jeune Entreprise Innovante). Ces dossiers exigent une ventilation détaillée du temps passé par collaborateur, par projet et par mois.
Décisions
- Format : XLSX (via PhpSpreadsheet côté backend)
- Déclenchement : bouton "Exporter" sur la page time-tracking, reprenant les filtres en cours
- Récap : double tableau croisé (user × projet + user × mois)
Architecture
Frontend Backend
───────── ───────
Bouton "Exporter"
→ GET /api/time_entries/export → TimeEntryExportController
?after=2026-01-01 → Validation params + authz
&before=2026-03-31 → TimeEntryRepository (query)
&user=5 → TimeEntryExportService (XLSX)
&project=5 → BinaryFileResponse (.xlsx)
&tags[]=2
Backend
Dépendance
phpoffice/phpspreadsheet ajouté via Composer.
TimeEntryExportController
- Fichier :
src/Controller/TimeEntryExportController.php - Route :
GET /api/time_entries/exportavecpriority: 1 - Sécurité :
#[IsGranted('ROLE_USER')] - Autorisation : si l'utilisateur n'a pas
ROLE_ADMIN, le filtreuserest forcé à l'utilisateur courant (ignore toute valeur fournie). Seuls les admins peuvent exporter les données d'autres utilisateurs ou de tous les utilisateurs. - Paramètres query (IDs numériques, pas d'IRIs — c'est un controller custom, pas API Platform) :
after(obligatoire) — date YYYY-MM-DDbefore(obligatoire) — date YYYY-MM-DDuser(optionnel) — ID numérique (ex:5)project(optionnel) — ID numérique (ex:5)tags[](optionnel) — tableau d'IDs numériques (ex:tags[]=2&tags[]=3)
- Validation :
afteretbeforeobligatoires, sinon 400 Bad Request- Plage maximale : 12 mois, sinon 400 Bad Request
- Si aucune entrée trouvée : retourne un XLSX avec en-têtes uniquement (pas d'erreur)
- Construit une query Doctrine avec ces filtres
- Appelle
TimeEntryExportService::generate() - Retourne
BinaryFileResponseavec headerContent-Disposition: attachment; filename="export-temps-YYYY-MM-DD_YYYY-MM-DD.xlsx"
TimeEntryExportService
- Fichier :
src/Service/TimeEntryExportService.php - Méthode :
generate(array $timeEntries, \DateTimeImmutable $from, \DateTimeImmutable $to): string(retourne le chemin du fichier temp)
Feuille 1 — "Détail"
Toutes les entrées triées par date croissante.
| Colonne | Source | Format |
|---|---|---|
| Date | startedAt |
YYYY-MM-DD |
| Utilisateur | user.username |
texte |
| Projet | project.name |
texte (vide si null) |
| Tâche | task |
"{code}-{number} - {title}" (vide si null) |
| Titre | title |
texte |
| Tags | tags |
labels séparés par ", " |
| Début | startedAt |
HH:mm |
| Fin | stoppedAt |
HH:mm (vide si null) |
| Durée (h) | calculée | nombre décimal (ex: 3.50) |
| Description | description |
texte |
- En-têtes en gras
- Colonnes auto-dimensionnées
- Ligne de total en bas (somme de la colonne Durée)
Feuille 2 — "Récap par projet"
Tableau croisé dynamique :
- Lignes = utilisateurs (triés alphabétiquement)
- Colonnes = projets (triés alphabétiquement)
- Cellules = total heures (décimal)
- Dernière colonne = total par utilisateur
- Dernière ligne = total par projet
Feuille 3 — "Récap par mois"
Tableau croisé dynamique :
- Lignes = utilisateurs (triés alphabétiquement)
- Colonnes = mois de la période (format "Mars 2026")
- Cellules = total heures (décimal)
- Dernière colonne = total par utilisateur
- Dernière ligne = total par mois
Frontend
Page time-tracking
- Ajout d'un bouton "Exporter" dans la barre d'actions (à côté des filtres existants)
- Icône de téléchargement + label "Exporter"
- Au clic : construit l'URL
/api/time_entries/exportavec les filtres actuels (période affichée, user sélectionné, projet sélectionné, tags sélectionnés) et déclenche le téléchargement
Service time-entries.ts
Ajout d'une méthode :
function getExportUrl(params: {
after: string // YYYY-MM-DD
before: string // YYYY-MM-DD
user?: number // ID numérique
project?: number // ID numérique
tags?: number[] // tableau d'IDs
}): string
Construit l'URL complète avec query params. Le téléchargement est déclenché via un élément <a> temporaire avec attribut download (le cookie JWT est envoyé automatiquement sur une requête same-origin). En cas d'erreur, un toast est affiché.
i18n
timeEntries.export→ "Exporter" (fr)
Sécurité
- Accessible à
ROLE_USER(même niveau que la consultation des time entries) - Non-admin : export limité à ses propres données (filtre
userforcé côté serveur) - Le fichier XLSX est généré dans un fichier temporaire et supprimé après envoi
- Les filtres utilisent des IDs numériques (controller custom, pas d'IRI)
Langue
Le contenu du XLSX est toujours en français (noms de feuilles, en-têtes de colonnes, noms de mois). C'est volontaire car les documents CIR/JEI sont des dossiers destinés à l'administration française.
Hors scope
- Export PDF
- Export CSV
- Stockage des exports générés
- Planification d'exports automatiques