Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 49ad6306ea | |||
| 9d2e70f81e |
@@ -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
@@ -1,2 +1,2 @@
|
|||||||
parameters:
|
parameters:
|
||||||
app.version: '0.1.112'
|
app.version: '0.1.113'
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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.' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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'));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user