Compare commits

...

2 Commits

Author SHA1 Message Date
gitea-actions 49ad6306ea chore: bump version to v0.1.113
Auto Tag Develop / tag (push) Successful in 8s
Build & Push Docker Image / build (push) Successful in 1m9s
2026-06-11 08:46:19 +00:00
tristan 9d2e70f81e feat(recap-conges) : cutoff S-1 au lieu de S-2
Auto Tag Develop / tag (push) Successful in 13s
Le récap des congés se fige désormais à la fin de la semaine précédente
(S-1) au lieu de l'avant-dernière (S-2), incluant ainsi une semaine de
plus. Demande métier.

- LeaveRecapCutoff : -14j -> -7j
- Test unitaire figeant la règle S-1
- Doc fonctionnelle, doc in-app et CLAUDE.md mis à jour

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 10:45:33 +02:00
7 changed files with 62 additions and 12 deletions
+1 -1
View File
@@ -126,7 +126,7 @@
## Récap. congés (écran) ## Récap. congés (écran)
- Accès via sidebar `Récap. congés`, conditionné au flag `User.hasLeaveRecapAccess` (défaut `false`) — activé au create/edit user. Le flag s'applique à tous les profils, y compris admin. - Accès via sidebar `Récap. congés`, conditionné au flag `User.hasLeaveRecapAccess` (défaut `false`) — activé au create/edit user. Le flag s'applique à tous les profils, y compris admin.
- Scope : `ROLE_ADMIN` → tous les employés, `ROLE_USER` (chef de site) → employés de ses sites, `ROLE_SELF` → sa ligne - Scope : `ROLE_ADMIN` → tous les employés, `ROLE_USER` (chef de site) → employés de ses sites, `ROLE_SELF` → sa ligne
- Cutoff temporel : fin de la semaine S-2 (dimanche 23:59:59). Formule : `dimanche(lundi_semaine_courante 14j)`. Pas de gate `isValid`. - Cutoff temporel : fin de la semaine S-1 (dimanche 23:59:59). Formule : `dimanche(lundi_semaine_courante 7j)`. Pas de gate `isValid`.
- Helper : `App\Util\LeaveRecapCutoff::resolveCutoff()` - Helper : `App\Util\LeaveRecapCutoff::resolveCutoff()`
- Colonnes : Nom, Prénom, Contrat, CP N-1 restant, CP N, Samedis, RTT — identiques au PDF - Colonnes : Nom, Prénom, Contrat, CP N-1 restant, CP N, Samedis, RTT — identiques au PDF
- Service partagé : `LeaveRecapRowBuilder` consommé par `LeaveRecapPrintProvider` (as-of today) et `EmployeeLeaveRecapProvider` (as-of cutoff) - Service partagé : `LeaveRecapRowBuilder` consommé par `LeaveRecapPrintProvider` (as-of today) et `EmployeeLeaveRecapProvider` (as-of cutoff)
+1 -1
View File
@@ -1,2 +1,2 @@
parameters: parameters:
app.version: '0.1.112' app.version: '0.1.113'
+3 -3
View File
@@ -374,9 +374,9 @@ Seuls les employés dont au moins une période de contrat intersecte la période
- `ROLE_ADMIN` : tous les employés - `ROLE_ADMIN` : tous les employés
- `ROLE_USER` (chef de site) : employés des sites autorisés (`UserSiteRole`) - `ROLE_USER` (chef de site) : employés des sites autorisés (`UserSiteRole`)
- `ROLE_SELF` : uniquement son employé lié - `ROLE_SELF` : uniquement son employé lié
- **Cutoff temporel** : le récap est figé à la fin de la semaine S-2 (dimanche 23:59:59) - **Cutoff temporel** : le récap est figé à la fin de la semaine S-1 (dimanche 23:59:59)
- Formule : `cutoffDate = dimanche(lundi_semaine_courante 14 jours)` - Formule : `cutoffDate = dimanche(lundi_semaine_courante 7 jours)`
- Exemple : mardi 14/04/2026 (S16) → dimanche 05/04/2026 (fin S14) - Exemple : mardi 14/04/2026 (S16) → dimanche 12/04/2026 (fin S15)
- `isValid` n'entre PAS en compte : cutoff purement temporel - `isValid` n'entre PAS en compte : cutoff purement temporel
- Les heures et absences postérieures au cutoff sont ignorées dans les calculs - Les heures et absences postérieures au cutoff sont ignorées dans les calculs
- Colonnes identiques au PDF (voir §10) - Colonnes identiques au PDF (voir §10)
+3 -3
View File
@@ -2,14 +2,14 @@
## Objet ## Objet
Vue tableau des soldes de congés par employé, figée à un cutoff temporel (fin de semaine S-2). Vue tableau des soldes de congés par employé, figée à un cutoff temporel (fin de semaine S-1).
Complémentaire à l'export PDF admin : mêmes colonnes, accès étendu aux employés et chefs de site. Complémentaire à l'export PDF admin : mêmes colonnes, accès étendu aux employés et chefs de site.
## Cutoff ## Cutoff
La formule est : `cutoffDate = dimanche de (lundi de la semaine courante 14 jours)`. La formule est : `cutoffDate = dimanche de (lundi de la semaine courante 7 jours)`.
Exemple : mardi 14/04/2026 (S16) → **dimanche 05/04/2026 23:59:59** (fin S14). Exemple : mardi 14/04/2026 (S16) → **dimanche 12/04/2026 23:59:59** (fin S15).
Le cutoff est purement temporel : l'état `isValid` des heures n'entre pas en compte. Les heures Le cutoff est purement temporel : l'état `isValid` des heures n'entre pas en compte. Les heures
et absences postérieures au cutoff sont ignorées dans le calcul des soldes. et absences postérieures au cutoff sont ignorées dans le calcul des soldes.
+1 -1
View File
@@ -505,7 +505,7 @@ export const documentationSections: DocSection[] = [
blocks: [ blocks: [
{ type: 'paragraph', content: 'L\'écran "Récap. congés" affiche un tableau figé des soldes de congés et RTT par employé. Il est accessible via la sidebar lorsque l\'accès a été activé sur le compte utilisateur.' }, { type: 'paragraph', content: 'L\'écran "Récap. congés" affiche un tableau figé des soldes de congés et RTT par employé. Il est accessible via la sidebar lorsque l\'accès a été activé sur le compte utilisateur.' },
{ type: 'list', content: 'Employé : voit uniquement sa propre ligne\nChef de site : voit les employés des sites qui lui sont rattachés\nAdmin : voit tous les employés, groupés par site' }, { type: 'list', content: 'Employé : voit uniquement sa propre ligne\nChef de site : voit les employés des sites qui lui sont rattachés\nAdmin : voit tous les employés, groupés par site' },
{ type: 'note', content: 'Le récap est arrêté à la fin de la semaine S-2 (dimanche). Exemple : un mardi en S16, les soldes sont calculés jusqu\'au dimanche de la S14 inclus. Les heures et absences postérieures ne sont pas comptées.' }, { type: 'note', content: 'Le récap est arrêté à la fin de la semaine S-1 (dimanche). Exemple : un mardi en S16, les soldes sont calculés jusqu\'au dimanche de la S15 inclus. Les heures et absences postérieures ne sont pas comptées.' },
{ type: 'paragraph', content: 'Les colonnes affichées sont identiques à l\'export PDF admin (Nom, Prénom, Contrat, CP N-1 restant, CP N, Samedis, RTT). L\'accès à cet écran est géré par un flag sur l\'utilisateur, activé depuis le drawer de création/édition d\'un utilisateur par un admin.' }, { type: 'paragraph', content: 'Les colonnes affichées sont identiques à l\'export PDF admin (Nom, Prénom, Contrat, CP N-1 restant, CP N, Samedis, RTT). L\'accès à cet écran est géré par un flag sur l\'utilisateur, activé depuis le drawer de création/édition d\'un utilisateur par un admin.' },
], ],
}, },
+3 -3
View File
@@ -7,16 +7,16 @@ namespace App\Util;
use DateTimeImmutable; use DateTimeImmutable;
/** /**
* Leave recap cutoff rule: as-of end of ISO week S-2 (Sunday 23:59:59). * Leave recap cutoff rule: as-of end of ISO week S-1 (Sunday 23:59:59).
* *
* Example: Tuesday 2026-04-14 (S16) Sunday 2026-04-05 23:59:59 (end of S14). * Example: Tuesday 2026-04-14 (S16) Sunday 2026-04-12 23:59:59 (end of S15).
*/ */
final class LeaveRecapCutoff final class LeaveRecapCutoff
{ {
public static function resolveCutoff(DateTimeImmutable $today): DateTimeImmutable public static function resolveCutoff(DateTimeImmutable $today): DateTimeImmutable
{ {
$currentWeekMonday = $today->modify('monday this week')->setTime(0, 0); $currentWeekMonday = $today->modify('monday this week')->setTime(0, 0);
$cutoffWeekMonday = $currentWeekMonday->modify('-14 days'); $cutoffWeekMonday = $currentWeekMonday->modify('-7 days');
return $cutoffWeekMonday->modify('+6 days')->setTime(23, 59, 59); return $cutoffWeekMonday->modify('+6 days')->setTime(23, 59, 59);
} }
+50
View File
@@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
namespace App\Tests\Util;
use App\Util\LeaveRecapCutoff;
use DateTimeImmutable;
use PHPUnit\Framework\TestCase;
/**
* Cutoff rule: end of ISO week S-1 (previous week's Sunday 23:59:59).
*
* @internal
*/
final class LeaveRecapCutoffTest extends TestCase
{
public function testCutoffIsPreviousWeekSunday(): void
{
// Tuesday 2026-04-14 (S16) → Sunday 2026-04-12 23:59:59 (end of S15).
$cutoff = LeaveRecapCutoff::resolveCutoff(new DateTimeImmutable('2026-04-14'));
self::assertSame('2026-04-12 23:59:59', $cutoff->format('Y-m-d H:i:s'));
}
public function testCutoffFromMondayPointsToPreviousSunday(): void
{
// Monday 2026-06-08 → previous Sunday 2026-06-07 23:59:59.
$cutoff = LeaveRecapCutoff::resolveCutoff(new DateTimeImmutable('2026-06-08'));
self::assertSame('2026-06-07 23:59:59', $cutoff->format('Y-m-d H:i:s'));
}
public function testCutoffFromSundayPointsToPreviousSunday(): void
{
// Sunday 2026-06-14 (still in current ISO week) → previous Sunday 2026-06-07.
$cutoff = LeaveRecapCutoff::resolveCutoff(new DateTimeImmutable('2026-06-14'));
self::assertSame('2026-06-07 23:59:59', $cutoff->format('Y-m-d H:i:s'));
}
public function testCutoffIsAlwaysASundayExactlyOneWeekBeforeCurrentWeek(): void
{
// Today 2026-06-11 (Thursday) → end of S-1 = Sunday 2026-06-07.
$cutoff = LeaveRecapCutoff::resolveCutoff(new DateTimeImmutable('2026-06-11'));
self::assertSame('Sunday', $cutoff->format('l'));
self::assertSame('2026-06-07 23:59:59', $cutoff->format('Y-m-d H:i:s'));
}
}