14 KiB
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:
EmployeeRttBalancedanssrc/Entity/EmployeeRttBalance.php- stockeopeningMinutespar exercice - Provider:
src/State/EmployeeRttSummaryProvider.php- calculeavailableMinutes = 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- champsavailableMinutes,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
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20260309140000 extends AbstractMigration
{
public function getDescription(): string
{
return 'Create employee_rtt_payments table for RTT paid hours tracking.';
}
public function up(Schema $schema): void
{
$this->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
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
declare(strict_types=1);
namespace App\Entity;
use App\Repository\EmployeeRttPaymentRepository;
use DateTimeImmutable;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: EmployeeRttPaymentRepository::class)]
#[ORM\Table(name: 'employee_rtt_payments')]
#[ORM\Index(columns: ['employee_id', 'year'], name: 'idx_rtt_payment_employee_year')]
class EmployeeRttPayment
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
private ?int $id = null;
#[ORM\ManyToOne(targetEntity: Employee::class)]
#[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
private ?Employee $employee = null;
#[ORM\Column(type: 'integer')]
private int $year = 0;
#[ORM\Column(type: 'integer')]
private int $month = 0;
#[ORM\Column(type: 'integer', options: ['comment' => '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
declare(strict_types=1);
namespace App\Repository;
use App\Entity\Employee;
use App\Entity\EmployeeRttPayment;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<EmployeeRttPayment>
*/
final class EmployeeRttPaymentRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, EmployeeRttPayment::class);
}
/**
* @return list<EmployeeRttPayment>
*/
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
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" }
// 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
ratein['25', '50'],monthin[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
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
// 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:
public int $totalPaidMinutes = 0;
/** @var list<RttMonthPayment> */
public array $monthPayments = [];
Et modifier availableMinutes pour soustraire les paiements:
$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:
export type RttMonthPayment = {
month: number
paidMinutes25: number
paidMinutes50: number
}
export type EmployeeRttSummary = {
// ... champs existants ...
totalPaidMinutes: number
monthPayments: RttMonthPayment[]
}
Step 5: Commit
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:
<p>...: {{ formatDays(summary?.availableMinutes ?? 0) }}</p>
Remplacer par:
<p>...: {{ formatMinutes(summary?.availableMinutes ?? 0) }}</p>
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:
<div class="py-[6px] pl-3 border-r border-b border-primary-500">Heure payée 25%</div>
<div class="py-[6px] pl-3 border-b border-primary-500">{{ formatMinutes(getMonthPaid25(month.month)) }}</div>
<div class="py-[6px] pl-3 border-r border-primary-500">Heure payée 50%</div>
<div class="py-[6px] pl-3">{{ formatMinutes(getMonthPaid50(month.month)) }}</div>
Step 3: Ajouter les helpers computed
const paymentsByMonth = computed(() => {
const map = new Map<number, { paid25: number; paid50: number }>()
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
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:
export const createRttPayment = async (
employeeId: number,
month: number,
minutes: number,
rate: '25' | '50',
year?: number
) => {
const api = useApi()
const body: Record<string, unknown> = { 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 puisloadEmployee() - 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<EmployeesRttTab>
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-paymentau submit
Step 5: Commit
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
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 |