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 @@
-
+
+
+ Avec contrat
+ Sans contrat
+ Tous
+
@@ -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; }