# RTT : Affichage en heures + Paiement RTT > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. **Goal:** Afficher les RTT en heures (plus en jours), permettre le paiement RTT via drawer avec majoration 25%/50%, stocker en BDD et afficher par mois. **Architecture:** Nouvelle entity `EmployeeRttPayment` (employee, year, month, minutes, rate). Le provider RTT agrège les paiements par mois et les soustrait du disponible. Le frontend ajoute un drawer de saisie et deux lignes par mois (25% / 50%). **Tech Stack:** Symfony + Doctrine + API Platform (backend), Nuxt 4 + Vue 3 + TypeScript + Tailwind (frontend) --- ## Contexte existant - **Entity:** `EmployeeRttBalance` dans `src/Entity/EmployeeRttBalance.php` - stocke `openingMinutes` par exercice - **Provider:** `src/State/EmployeeRttSummaryProvider.php` - calcule `availableMinutes = carry + currentYearRecovery` - **Service:** `src/Service/Rtt/RttRecoveryComputationService.php` - calcul semaine par semaine - **Frontend:** `frontend/components/employees/RttTab.vue` - affichage grille mois/semaines - **DTO backend:** `src/ApiResource/EmployeeRttSummary.php` - champs `availableMinutes`, `weeks[]` - **DTO frontend:** `frontend/services/dto/employee-rtt-summary.ts` - Les minutes sont la base de calcul. 1 jour = 7h = 420 minutes. - Exercice RTT : 1er juin N-1 -> 31 mai N (year = N) --- ### Task 1: Migration - Créer la table `employee_rtt_payments` **Files:** - Create: `migrations/Version20260309140000.php` **Step 1: Créer la migration** ```php addSql(<<<'SQL' CREATE TABLE employee_rtt_payments ( id SERIAL PRIMARY KEY, employee_id INT NOT NULL REFERENCES employees(id) ON DELETE CASCADE, year INT NOT NULL, month INT NOT NULL, minutes INT NOT NULL, rate VARCHAR(10) NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL DEFAULT NOW(), updated_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL DEFAULT NOW() ) SQL); $this->addSql('CREATE INDEX idx_rtt_payment_employee_year ON employee_rtt_payments (employee_id, year)'); $this->addSql("COMMENT ON TABLE employee_rtt_payments IS 'Paiements RTT par employe, mois et taux de majoration.'"); $this->addSql("COMMENT ON COLUMN employee_rtt_payments.rate IS 'Taux de majoration: 25 ou 50.'"); $this->addSql("COMMENT ON COLUMN employee_rtt_payments.minutes IS 'Minutes RTT payees pour ce mois et ce taux.'"); } public function down(Schema $schema): void { $this->addSql('DROP TABLE employee_rtt_payments'); } } ``` **Step 2: Exécuter la migration** Run: `make migrate` ou `docker compose exec php php bin/console doctrine:migrations:migrate --no-interaction` Expected: Migration exécutée sans erreur **Step 3: Commit** ```bash git add migrations/Version20260309140000.php git commit -m "feat(rtt): add employee_rtt_payments table" ``` --- ### Task 2: Entity + Repository `EmployeeRttPayment` **Files:** - Create: `src/Entity/EmployeeRttPayment.php` - Create: `src/Repository/EmployeeRttPaymentRepository.php` **Step 1: Créer l'entity** ```php 'Minutes RTT payees pour ce mois et ce taux.'])] private int $minutes = 0; #[ORM\Column(length: 10, options: ['comment' => 'Taux de majoration: 25 ou 50.'])] private string $rate = '25'; #[ORM\Column(type: 'datetime_immutable')] private DateTimeImmutable $createdAt; #[ORM\Column(type: 'datetime_immutable')] private DateTimeImmutable $updatedAt; public function __construct() { $now = new DateTimeImmutable(); $this->createdAt = $now; $this->updatedAt = $now; } // Getters & setters (getId, getEmployee/setEmployee, getYear/setYear, // getMonth/setMonth, getMinutes/setMinutes, getRate/setRate, touch) // Suivre le même pattern que EmployeeLeaveBalance } ``` **Step 2: Créer le repository** ```php */ final class EmployeeRttPaymentRepository extends ServiceEntityRepository { public function __construct(ManagerRegistry $registry) { parent::__construct($registry, EmployeeRttPayment::class); } /** * @return list */ public function findByEmployeeAndYear(Employee $employee, int $year): array { return $this->createQueryBuilder('p') ->andWhere('p.employee = :employee') ->andWhere('p.year = :year') ->setParameter('employee', $employee) ->setParameter('year', $year) ->orderBy('p.month', 'ASC') ->getQuery() ->getResult(); } } ``` **Step 3: Vérifier le lint** Run: `docker compose exec php php -l src/Entity/EmployeeRttPayment.php && docker compose exec php php -l src/Repository/EmployeeRttPaymentRepository.php` Expected: No syntax errors **Step 4: Commit** ```bash git add src/Entity/EmployeeRttPayment.php src/Repository/EmployeeRttPaymentRepository.php git commit -m "feat(rtt): add EmployeeRttPayment entity and repository" ``` --- ### Task 3: API Resource + Provider + Processor pour le paiement RTT **Files:** - Create: `src/ApiResource/EmployeeRttPaymentInput.php` - Create: `src/State/EmployeeRttPaymentProvider.php` - Create: `src/State/EmployeeRttPaymentProcessor.php` **Step 1: Créer l'API Resource** Endpoint: `PATCH /employees/{id}/rtt-payments` (ROLE_ADMIN) Body: `{ "month": 3, "minutes": 120, "rate": "25" }` ```php // src/ApiResource/EmployeeRttPaymentInput.php #[ApiResource( operations: [ new Patch( uriTemplate: '/employees/{id}/rtt-payments', security: "is_granted('ROLE_ADMIN')", provider: EmployeeRttPaymentProvider::class, processor: EmployeeRttPaymentProcessor::class ), ], paginationEnabled: false )] final class EmployeeRttPaymentInput { public int $month = 0; public int $minutes = 0; public string $rate = '25'; public ?int $year = null; } ``` **Step 2: Créer le Provider** (retourne un DTO vide, même pattern que `EmployeeFractionedDaysProvider`) **Step 3: Créer le Processor** Logique: - Valider `rate` in `['25', '50']`, `month` in `[1..12]`, `minutes >= 0` - Résoudre l'année (même logique exercice RTT que le provider existant) - Persister un `EmployeeRttPayment` **Step 4: Vérifier le lint** **Step 5: Commit** ```bash git add src/ApiResource/EmployeeRttPaymentInput.php src/State/EmployeeRttPaymentProvider.php src/State/EmployeeRttPaymentProcessor.php git commit -m "feat(rtt): add PATCH endpoint for RTT payment" ``` --- ### Task 4: Modifier le DTO + Provider RTT pour inclure les paiements par mois **Files:** - Modify: `src/ApiResource/EmployeeRttSummary.php` - Modify: `src/State/EmployeeRttSummaryProvider.php` - Create: `src/Dto/Rtt/RttMonthPayment.php` - Modify: `frontend/services/dto/employee-rtt-summary.ts` **Step 1: Créer le DTO mois-paiement** ```php // src/Dto/Rtt/RttMonthPayment.php final class RttMonthPayment { public function __construct( public int $month, public int $paidMinutes25 = 0, public int $paidMinutes50 = 0, ) {} } ``` **Step 2: Ajouter au summary backend** Dans `EmployeeRttSummary.php`, ajouter: ```php public int $totalPaidMinutes = 0; /** @var list */ public array $monthPayments = []; ``` Et modifier `availableMinutes` pour soustraire les paiements: ```php $summary->availableMinutes = $summary->carryFromPreviousYearMinutes + $summary->currentYearRecoveryMinutes - $summary->totalPaidMinutes; ``` **Step 3: Modifier le provider** pour charger les paiements via le repository et les agréger par mois Dans `EmployeeRttSummaryProvider::provide()`: - Charger `$payments = $this->rttPaymentRepository->findByEmployeeAndYear($employee, $year)` - Agréger par mois + rate pour construire les `RttMonthPayment` - Calculer `totalPaidMinutes = sum(minutes)` - Soustraire du `availableMinutes` **Step 4: Mettre à jour le DTO frontend** Dans `frontend/services/dto/employee-rtt-summary.ts`: ```typescript export type RttMonthPayment = { month: number paidMinutes25: number paidMinutes50: number } export type EmployeeRttSummary = { // ... champs existants ... totalPaidMinutes: number monthPayments: RttMonthPayment[] } ``` **Step 5: Commit** ```bash git commit -m "feat(rtt): include paid hours in RTT summary by month" ``` --- ### Task 5: Frontend - Afficher les RTT en heures + lignes payées **Files:** - Modify: `frontend/components/employees/RttTab.vue` **Step 1: Changer l'affichage du header de jours en heures** Ligne 4 actuelle: ```html

...: {{ formatDays(summary?.availableMinutes ?? 0) }}

``` Remplacer par: ```html

...: {{ formatMinutes(summary?.availableMinutes ?? 0) }}

``` **Step 2: Ajouter les 2 lignes de paiement par mois** Après la ligne `Heure payée` existante (ligne 33-34), remplacer par 2 lignes distinctes: ```html
Heure payée 25%
{{ formatMinutes(getMonthPaid25(month.month)) }}
Heure payée 50%
{{ formatMinutes(getMonthPaid50(month.month)) }}
``` **Step 3: Ajouter les helpers computed** ```typescript const paymentsByMonth = computed(() => { const map = new Map() for (const mp of props.summary?.monthPayments ?? []) { map.set(mp.month, { paid25: mp.paidMinutes25, paid50: mp.paidMinutes50 }) } return map }) const getMonthPaid25 = (month: number) => paymentsByMonth.value.get(month)?.paid25 ?? 0 const getMonthPaid50 = (month: number) => paymentsByMonth.value.get(month)?.paid50 ?? 0 ``` **Step 4: Commit** ```bash git commit -m "feat(rtt): display hours instead of days + paid hours per month" ``` --- ### Task 6: Frontend - Drawer de paiement RTT **Files:** - Modify: `frontend/components/employees/RttTab.vue` - Modify: `frontend/services/employee-rtt-summary.ts` - Modify: `frontend/composables/useEmployeeDetailPage.ts` - Modify: `frontend/pages/employees/[id].vue` **Step 1: Ajouter le service API** Dans `frontend/services/employee-rtt-summary.ts`: ```typescript export const createRttPayment = async ( employeeId: number, month: number, minutes: number, rate: '25' | '50', year?: number ) => { const api = useApi() const body: Record = { month, minutes, rate } if (year) body.year = year return api.patch(`/employees/${employeeId}/rtt-payments`, body) } ``` **Step 2: Ajouter au composable** Dans `useEmployeeDetailPage.ts`: - Import `createRttPayment` - Ajouter `submitRttPayment(month, minutes, rate)` qui appelle l'API puis `loadEmployee()` - Exposer dans le return **Step 3: Passer l'event dans la page** Dans `frontend/pages/employees/[id].vue`: - Destructurer `submitRttPayment` - Ajouter `@submit-rtt-payment="submitRttPayment"` sur `` **Step 4: Ajouter le drawer dans RttTab.vue** Même pattern que le drawer fractionnés dans LeaveTab: - Import `AppDrawer` - State: `isPaymentDrawerOpen`, `paymentForm: { month, minutes, rate }` - Bouton "Payer les RTT" ouvre le drawer - Formulaire avec: - Select mois (Janvier..Décembre) - Input number "Nombre d'heures" (step 0.5, converti en minutes au submit) - Select rate: "25%" / "50%" - Boutons Annuler / Enregistrer - Emit `submit-rtt-payment` au submit **Step 5: Commit** ```bash git commit -m "feat(rtt): add payment drawer with month/hours/rate fields" ``` --- ### Task 7: Documentation fonctionnelle **Files:** - Modify: `doc/functional-rules.md` **Step 1: Mettre à jour la section RTT** Ajouter après les règles RTT existantes: - Paiement RTT: saisie RH via `PATCH /employees/{id}/rtt-payments` - Stocké dans `employee_rtt_payments` (employee, year, month, minutes, rate) - Les heures payées sont soustraites du disponible RTT - Affichage: 2 lignes par mois (25% et 50%) - L'affichage global RTT est en heures (plus en jours) **Step 2: Commit** ```bash git commit -m "docs: update functional rules with RTT payment feature" ``` --- ## Résumé des fichiers | Action | Fichier | |--------|---------| | Create | `migrations/Version20260309140000.php` | | Create | `src/Entity/EmployeeRttPayment.php` | | Create | `src/Repository/EmployeeRttPaymentRepository.php` | | Create | `src/ApiResource/EmployeeRttPaymentInput.php` | | Create | `src/State/EmployeeRttPaymentProvider.php` | | Create | `src/State/EmployeeRttPaymentProcessor.php` | | Create | `src/Dto/Rtt/RttMonthPayment.php` | | Modify | `src/ApiResource/EmployeeRttSummary.php` | | Modify | `src/State/EmployeeRttSummaryProvider.php` | | Modify | `frontend/services/dto/employee-rtt-summary.ts` | | Modify | `frontend/services/employee-rtt-summary.ts` | | Modify | `frontend/components/employees/RttTab.vue` | | Modify | `frontend/composables/useEmployeeDetailPage.ts` | | Modify | `frontend/pages/employees/[id].vue` | | Modify | `doc/functional-rules.md` |