Files
Lesstime/docs/superpowers/specs/2026-03-24-time-entry-export-design.md
Matthieu e796741dd8 docs : add time entry export design spec (LST-41)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 15:47:33 +01:00

5.5 KiB
Raw Blame History

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/export avec priority: 1
  • Sécurité : #[IsGranted('ROLE_USER')]
  • Autorisation : si l'utilisateur n'a pas ROLE_ADMIN, le filtre user est 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-DD
    • before (obligatoire) — date YYYY-MM-DD
    • user (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 :
    • after et before obligatoires, 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 BinaryFileResponse avec header Content-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/export avec 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 user forcé 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