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)
- 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
- 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()`
- 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)
+1 -1
View File
@@ -1,2 +1,2 @@
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_USER` (chef de site) : employés des sites autorisés (`UserSiteRole`)
- `ROLE_SELF` : uniquement son employé lié
- **Cutoff temporel** : le récap est figé à la fin de la semaine S-2 (dimanche 23:59:59)
- Formule : `cutoffDate = dimanche(lundi_semaine_courante 14 jours)`
- Exemple : mardi 14/04/2026 (S16) → dimanche 05/04/2026 (fin S14)
- **Cutoff temporel** : le récap est figé à la fin de la semaine S-1 (dimanche 23:59:59)
- Formule : `cutoffDate = dimanche(lundi_semaine_courante 7 jours)`
- Exemple : mardi 14/04/2026 (S16) → dimanche 12/04/2026 (fin S15)
- `isValid` n'entre PAS en compte : cutoff purement temporel
- Les heures et absences postérieures au cutoff sont ignorées dans les calculs
- Colonnes identiques au PDF (voir §10)
+3 -3
View File
@@ -2,14 +2,14 @@
## 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.
## 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
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: [
{ 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: '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.' },
],
},
+3 -3
View File
@@ -7,16 +7,16 @@ namespace App\Util;
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
{
public static function resolveCutoff(DateTimeImmutable $today): DateTimeImmutable
{
$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);
}
+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'));
}
}