# 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 :
```typescript
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
