From cef364fcec5d7f4294a3b110268347f09853cca6 Mon Sep 17 00:00:00 2001 From: tristan Date: Mon, 16 Mar 2026 14:37:00 +0100 Subject: [PATCH] =?UTF-8?q?fix=20:=20fix=20affichage=20employ=C3=A9=20sur?= =?UTF-8?q?=20les=20pages=20d'heures=20+=20ajout=20d'un=20filtre=20employ?= =?UTF-8?q?=C3=A9=20sur=20la=20liste=20+=20fix=20impression=20recap=20sala?= =?UTF-8?q?ire?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/functional-rules.md | 11 ++++++ frontend/components/hours/HoursWeekView.vue | 4 ++ frontend/composables/useDriverHoursPage.ts | 12 +++++- frontend/composables/useHoursPage.ts | 11 +++++- frontend/pages/driver-hours.vue | 3 +- frontend/pages/employees/index.vue | 23 +++++++---- frontend/pages/hours.vue | 3 +- frontend/services/dto/employee.ts | 1 + frontend/services/dto/work-hour.ts | 3 ++ src/Dto/WorkHours/WeeklyDaySummary.php | 1 + src/Dto/WorkHours/WeeklySummaryRow.php | 2 + src/Entity/Employee.php | 6 +++ src/State/WorkHourWeeklySummaryProvider.php | 44 ++++++++++++++------- templates/salary-recap/print.html.twig | 4 +- 14 files changed, 98 insertions(+), 30 deletions(-) diff --git a/doc/functional-rules.md b/doc/functional-rules.md index aded520..90938b4 100644 --- a/doc/functional-rules.md +++ b/doc/functional-rules.md @@ -40,6 +40,10 @@ Documents complementaires: ## 3) Heures (vue jour) +- Visibilité des employés: + - vue jour: un employé sans contrat à la date sélectionnée est masqué + - vue semaine: un employé sans contrat sur aucun jour de la semaine est masqué + - même règle pour les heures classiques et les heures conducteurs - Saisie par salarié et par date: - matin / après-midi / soir - pour `PRESENCE`: demi-journées matin/après-midi @@ -134,9 +138,11 @@ Documents complementaires: - `dayHoursMinutes`, `nightHoursMinutes` et `workshopHoursMinutes` (entiers, minutes) sur `WorkHour` - `hasBreakfast`, `hasLunch`, `hasDinner`, `hasOvernight` (booleans) sur `WorkHour` - les champs time classiques (morning/afternoon/evening) sont mis à null pour les chauffeurs +- Absences `countAsWorkedHours=true`: les minutes créditées sont ajoutées aux heures de jour (vue jour et vue semaine), même logique que les employés classiques - Validation: même logique que les heures classiques (`isValid`, `isSiteValid`, bulk) - Vue semaine: - jour/nuit/atelier par jour + indicateurs repas/dîner/nuitée + - panier de nuit (PN): affiché par jour si nightMinutes > dayMinutes, et total hebdo dans la colonne Jour/Nuit sem. - totaux hebdo: jour, nuit, atelier, total, compteurs petit déj/déjeuner/dîner/nuitée - pas de calcul d'heures supplémentaires pour les conducteurs - Le flag `isDriver` est sur `EmployeeContractPeriod` (un employé peut changer de statut chauffeur selon la période) @@ -170,6 +176,11 @@ Tous les filtres checkbox sont cochés par défaut à l'ouverture du drawer. - Modification employé: - uniquement prénom, nom, site - pas de modification de contrat depuis ce drawer +- Liste employés — filtre par statut de contrat: + - 3 options: "Avec contrat" (défaut), "Sans contrat", "Tous" + - "Avec contrat": employés ayant une période de contrat active à la date du jour + - "Sans contrat": employés sans période de contrat active + - "Tous": aucun filtrage sur le contrat - Détail employé: - onglet `Suivi contrat` avec affichage de l'historique des périodes de contrat - chaque ligne expose: nature (`CDI`/`CDD`/`INTERIM`), contrat/temps de travail, date de début, date de fin (ou "En cours") diff --git a/frontend/components/hours/HoursWeekView.vue b/frontend/components/hours/HoursWeekView.vue index 99919ec..481561d 100644 --- a/frontend/components/hours/HoursWeekView.vue +++ b/frontend/components/hours/HoursWeekView.vue @@ -14,6 +14,7 @@ +25% +50% Total
récup.
+ Panier
nuit
@@ -68,6 +69,9 @@
{{ row.trackingMode === 'PRESENCE' || isInterimContract(row.contractType) ? '-' : formatMinutes(row.weeklyRecoveryMinutes ?? 0) }}
+
+ {{ (row.weeklyNightBasketCount ?? 0) > 0 ? row.weeklyNightBasketCount : '-' }} +
diff --git a/frontend/composables/useDriverHoursPage.ts b/frontend/composables/useDriverHoursPage.ts index b05619b..6a8b04e 100644 --- a/frontend/composables/useDriverHoursPage.ts +++ b/frontend/composables/useDriverHoursPage.ts @@ -107,13 +107,19 @@ export const useDriverHoursPage = () => { }) }) + const displayedEmployees = computed(() => { + return visibleEmployees.value.filter((employee) => hasContractAtSelectedDate(employee.id)) + }) + const visibleEmployeeIdSet = computed(() => new Set(visibleEmployees.value.map((employee) => employee.id))) const filteredWeeklySummary = computed(() => { if (!weeklySummary.value) return null return { ...weeklySummary.value, - rows: weeklySummary.value.rows.filter((row) => visibleEmployeeIdSet.value.has(row.employeeId)) + rows: weeklySummary.value.rows.filter((row) => + visibleEmployeeIdSet.value.has(row.employeeId) && row.hasContractForWeek !== false + ) } }) @@ -362,7 +368,8 @@ export const useDriverHoursPage = () => { const getRowMetrics = (employeeId: number) => { const row = rows.value[employeeId] ?? emptyRow() - const dayMinutes = toMinutes(row.dayHours) + const credited = dayContextByEmployeeId.value.get(employeeId)?.creditedMinutes ?? 0 + const dayMinutes = toMinutes(row.dayHours) + credited const nightMinutes = toMinutes(row.nightHours) const workshopMinutes = toMinutes(row.workshopHours) const totalMinutes = dayMinutes + nightMinutes + workshopMinutes @@ -917,6 +924,7 @@ export const useDriverHoursPage = () => { selectedSiteIds, employees, visibleEmployees, + displayedEmployees, rows, absenceTypes, absenceForm, diff --git a/frontend/composables/useHoursPage.ts b/frontend/composables/useHoursPage.ts index 97459c5..d4521f4 100644 --- a/frontend/composables/useHoursPage.ts +++ b/frontend/composables/useHoursPage.ts @@ -77,7 +77,7 @@ export const useHoursPage = () => { return `1.2fr 0.6fr repeat(6, 0.8fr) ${metricCol} ${metricCol} ${metricCol} ${validationCols}` }) - const weekGridCols = '1.6fr repeat(7, 1fr) repeat(6, 0.6fr)' + const weekGridCols = '1.6fr repeat(7, 1fr) repeat(6, 0.6fr) 0.3fr' const sites = computed(() => { const siteMap = new Map() @@ -109,13 +109,19 @@ export const useHoursPage = () => { }) }) + const displayedEmployees = computed(() => { + return visibleEmployees.value.filter((employee) => hasContractAtSelectedDate(employee.id)) + }) + const visibleEmployeeIdSet = computed(() => new Set(visibleEmployees.value.map((employee) => employee.id))) const filteredWeeklySummary = computed(() => { if (!weeklySummary.value) return null return { ...weeklySummary.value, - rows: weeklySummary.value.rows.filter((row) => visibleEmployeeIdSet.value.has(row.employeeId)) + rows: weeklySummary.value.rows.filter((row) => + visibleEmployeeIdSet.value.has(row.employeeId) && row.hasContractForWeek !== false + ) } }) @@ -1096,6 +1102,7 @@ export const useHoursPage = () => { selectedSiteIds, employees, visibleEmployees, + displayedEmployees, rows, absenceTypes, absenceForm, diff --git a/frontend/pages/driver-hours.vue b/frontend/pages/driver-hours.vue index 061002d..61396eb 100644 --- a/frontend/pages/driver-hours.vue +++ b/frontend/pages/driver-hours.vue @@ -38,7 +38,7 @@ -
+
+
@@ -49,7 +57,7 @@

{{ employee.firstName }} {{ employee.lastName }}

Nom du poste occupé

-

Site ({{ employee.site?.name ?? '-' }})

+

{{ employee.site?.name ?? '-' }}

@@ -248,20 +256,21 @@ const employees = ref([]) const sites = ref([]) const contracts = ref([]) const employeeFilter = ref('') +const contractStatusFilter = ref<'active' | 'inactive' | 'all'>('active') const selectedSiteIds = ref([]) const filteredEmployees = computed(() => { if (selectedSiteIds.value.length === 0) return [] const filter = employeeFilter.value.trim().toLowerCase() - const bySite = employees.value.filter((employee) => { + return employees.value.filter((employee) => { const siteId = employee.site?.id - return !!siteId && selectedSiteIds.value.includes(siteId) - }) + if (!siteId || !selectedSiteIds.value.includes(siteId)) return false - if (!filter) return bySite + if (contractStatusFilter.value === 'active' && !employee.hasActiveContract) return false + if (contractStatusFilter.value === 'inactive' && employee.hasActiveContract) return false - return bySite.filter((employee) => { + if (!filter) return true const firstName = employee.firstName?.toLowerCase() ?? '' const lastName = employee.lastName?.toLowerCase() ?? '' return firstName.includes(filter) || lastName.includes(filter) diff --git a/frontend/pages/hours.vue b/frontend/pages/hours.vue index 6361293..c4fa0ed 100644 --- a/frontend/pages/hours.vue +++ b/frontend/pages/hours.vue @@ -38,7 +38,7 @@ resolveCurrentContractPeriod(); + } + #[Groups(['employee:read'])] public function getIsDriver(): bool { diff --git a/src/State/WorkHourWeeklySummaryProvider.php b/src/State/WorkHourWeeklySummaryProvider.php index fe5b38e..e1b0cd5 100644 --- a/src/State/WorkHourWeeklySummaryProvider.php +++ b/src/State/WorkHourWeeklySummaryProvider.php @@ -187,16 +187,17 @@ final readonly class WorkHourWeeklySummaryProvider implements ProviderInterface continue; } - $weeklyDayMinutes = 0; - $weeklyNightMinutes = 0; - $weeklyWorkshopMinutes = 0; - $weeklyTotalMinutes = 0; - $weeklyPresenceCount = 0.0; - $weeklyBreakfastCount = 0; - $weeklyLunchCount = 0; - $weeklyDinnerCount = 0; - $weeklyOvernightCount = 0; - $daily = []; + $weeklyDayMinutes = 0; + $weeklyNightMinutes = 0; + $weeklyWorkshopMinutes = 0; + $weeklyTotalMinutes = 0; + $weeklyPresenceCount = 0.0; + $weeklyNightBasketCount = 0; + $weeklyBreakfastCount = 0; + $weeklyLunchCount = 0; + $weeklyDinnerCount = 0; + $weeklyOvernightCount = 0; + $daily = []; // Les contrats au suivi "présence" ne manipulent pas les heures, mais des demi-journées. $weekAnchorContract = $contractsByEmployeeDate[$employeeId][$anchorDateYmd] ?? $contractsByEmployeeDate[$employeeId][$days[0]] @@ -208,8 +209,12 @@ final readonly class WorkHourWeeklySummaryProvider implements ProviderInterface ?? $contractNaturesByEmployeeDate[$employeeId][$days[0]] ?? ContractNature::CDI; $employeeContractsByDate = []; + $hasContractForWeek = false; foreach ($days as $date) { $employeeContractsByDate[$date] = $contractsByEmployeeDate[$employeeId][$date] ?? null; + if (null !== $employeeContractsByDate[$date]) { + $hasContractForWeek = true; + } } foreach ($days as $date) { @@ -228,11 +233,12 @@ final readonly class WorkHourWeeklySummaryProvider implements ProviderInterface $dayMinutes = ($entry['dayHoursMinutes'] ?? 0); $nightMinutes = ($entry['nightHoursMinutes'] ?? 0); $workshopMinutes = ($entry['workshopHoursMinutes'] ?? 0); - $totalMinutes = $dayMinutes + $nightMinutes + $workshopMinutes; - $hasBreakfast = $entry['hasBreakfast'] ?? false; - $hasLunch = $entry['hasLunch'] ?? false; - $hasDinner = $entry['hasDinner'] ?? false; - $hasOvernight = $entry['hasOvernight'] ?? false; + $totalMinutes = $dayMinutes + $nightMinutes + $workshopMinutes + $creditedMinutes; + $dayMinutes += $creditedMinutes; + $hasBreakfast = $entry['hasBreakfast'] ?? false; + $hasLunch = $entry['hasLunch'] ?? false; + $hasDinner = $entry['hasDinner'] ?? false; + $hasOvernight = $entry['hasOvernight'] ?? false; if ($hasBreakfast) { ++$weeklyBreakfastCount; } @@ -265,6 +271,11 @@ final readonly class WorkHourWeeklySummaryProvider implements ProviderInterface $present = min(1.0, $morning + $afternoon + $creditedPresence); } + $hasNightBasket = $nightMinutes > $dayMinutes && $nightMinutes > 0; + if ($hasNightBasket) { + ++$weeklyNightBasketCount; + } + $weeklyDayMinutes += $dayMinutes; $weeklyNightMinutes += $nightMinutes; $weeklyWorkshopMinutes += $workshopMinutes; @@ -283,6 +294,7 @@ final readonly class WorkHourWeeklySummaryProvider implements ProviderInterface hasAbsence: $absenceByEmployeeDate[$employeeId][$date] ?? false, absenceLabel: $absenceLabelByEmployeeDate[$employeeId][$date] ?? null, absenceColor: $absenceColorByEmployeeDate[$employeeId][$date] ?? null, + hasNightBasket: $hasNightBasket, hasBreakfast: $hasBreakfast, hasLunch: $hasLunch, hasDinner: $hasDinner, @@ -325,11 +337,13 @@ final readonly class WorkHourWeeklySummaryProvider implements ProviderInterface weeklyOvertime25Minutes: $weeklyOvertime25Minutes, weeklyOvertime50Minutes: $weeklyOvertime50Minutes, weeklyRecoveryMinutes: $weeklyRecoveryMinutes, + weeklyNightBasketCount: $weeklyNightBasketCount, isDriver: $isDriver, weeklyBreakfastCount: $weeklyBreakfastCount, weeklyLunchCount: $weeklyLunchCount, weeklyDinnerCount: $weeklyDinnerCount, weeklyOvernightCount: $weeklyOvernightCount, + hasContractForWeek: $hasContractForWeek, ); } diff --git a/templates/salary-recap/print.html.twig b/templates/salary-recap/print.html.twig index 66d4c00..ec77de8 100644 --- a/templates/salary-recap/print.html.twig +++ b/templates/salary-recap/print.html.twig @@ -60,7 +60,7 @@ thead th { text-align: center; font-weight: 700; - font-size: 11px; + font-size: 10px; white-space: normal; } @@ -78,7 +78,7 @@ } td.obs { } - tbody td { font-size: 12px; } + tbody td { font-size: 10px; }