fix : redirection après login + écran des heures chauffeurs
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled

This commit is contained in:
2026-03-16 09:13:35 +01:00
parent 3d26d6b50f
commit 01f8058f56
14 changed files with 255 additions and 77 deletions

View File

@@ -124,18 +124,20 @@ Documents complementaires:
- Colonnes spécifiques (vue jour):
- Heure de jour (durée HH:MM via TimeSelect)
- Heure de nuit (durée HH:MM via TimeSelect)
- Total (somme jour + nuit, calculé)
- Heure atelier (durée HH:MM via TimeSelect)
- Total (somme jour + nuit + atelier, calculé)
- Petit déjeuner (checkbox)
- Déjeuner (checkbox)
- Dîner (checkbox)
- Nuitée (checkbox)
- Stockage backend:
- `dayHoursMinutes` et `nightHoursMinutes` (entiers, minutes) sur `WorkHour`
- `hasBreakfast`, `hasLunch`, `hasOvernight` (booleans) sur `WorkHour`
- `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
- Validation: même logique que les heures classiques (`isValid`, `isSiteValid`, bulk)
- Vue semaine:
- jour/nuit par jour + indicateurs repas/nuitée
- totaux hebdo: jour, nuit, total, compteurs petit déj/déjeuner/nuitée
- jour/nuit/atelier par jour + indicateurs repas/dîner/nuitée
- 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)
- Exposé en API via un getter virtuel sur `Employee` (`employee:read`) qui résout depuis la période active

View File

@@ -9,9 +9,11 @@
<span class="pl-2">Absence</span>
<span class="pl-4">Heure de jour</span>
<span class="pl-2">Heure de nuit</span>
<span class="pl-2">Heure atelier</span>
<span class="pl-2">Total</span>
<span>Petit déj.</span>
<span>Déjeuner</span>
<span>Dîner</span>
<span>Nuitée</span>
<span v-if="isAdmin" class="flex justify-between items-center">
<span>Valider</span>
@@ -96,6 +98,12 @@
:disabled="!hasContractAtSelectedDate(employee.id) || isRowLocked(employee.id)"
/>
</div>
<div class="pl-2">
<TimeSelect
v-model="rows[employee.id].workshopHours"
:disabled="!hasContractAtSelectedDate(employee.id) || isRowLocked(employee.id)"
/>
</div>
<div class="pl-2 text-sm font-semibold">
{{ formatMinutes(getRowMetrics(employee.id).totalMinutes) }}
</div>
@@ -115,6 +123,14 @@
:disabled="!hasContractAtSelectedDate(employee.id) || isRowLocked(employee.id)"
/>
</div>
<div class="flex">
<input
v-model="rows[employee.id].hasDinner"
type="checkbox"
class="cursor-pointer h-4 w-4"
:disabled="!hasContractAtSelectedDate(employee.id) || isRowLocked(employee.id)"
/>
</div>
<div class="flex">
<input
v-model="rows[employee.id].hasOvernight"

View File

@@ -7,8 +7,9 @@
:style="{ gridTemplateColumns: weekGridCols }"
>
<span>Nom</span>
<span v-for="day in weekDayHeaders" :key="day.date" class="text-left">{{ day.label }}</span>
<span v-for="day in weekDayHeaders" :key="day.date" class="text-left">{{ day.weekday }}<br>{{ day.dayDate }}</span>
<span>Jour/Nuit <br>sem.</span>
<span>Atelier <br>sem.</span>
<span>Total <br>sem.</span>
<span>Total <br>h. supp.</span>
<span>+25%</span>
@@ -16,7 +17,8 @@
<span>Total <br>récup.</span>
<span>Petit <br>déj.</span>
<span>Déj.</span>
<span>Nuitée</span>
<span>Dîner</span>
<span>Nuit.</span>
</div>
<div class="border-x border-b border-primary-500 rounded-b-md">
@@ -44,9 +46,11 @@
>
<div>J {{ formatMinutes(daily.dayMinutes) }}</div>
<div>N {{ formatMinutes(daily.nightMinutes) }}</div>
<div v-if="daily.hasBreakfast || daily.hasLunch || daily.hasOvernight" class="text-[10px] flex gap-1 mt-0.5">
<div v-if="daily.workshopMinutes">A {{ formatMinutes(daily.workshopMinutes) }}</div>
<div v-if="daily.hasBreakfast || daily.hasLunch || daily.hasDinner || daily.hasOvernight" class="text-[10px] flex gap-1 mt-0.5">
<span v-if="daily.hasBreakfast" title="Petit déjeuner">PD</span>
<span v-if="daily.hasLunch" title="Déjeuner">DJ</span>
<span v-if="daily.hasDinner" title="Dîner">DI</span>
<span v-if="daily.hasOvernight" title="Nuitée">NU</span>
</div>
</div>
@@ -55,6 +59,9 @@
<div>J {{ formatMinutes(row.weeklyDayMinutes) }}</div>
<div>N {{ formatMinutes(row.weeklyNightMinutes) }}</div>
</div>
<div class="font-semibold">
{{ formatMinutes(row.weeklyWorkshopMinutes ?? 0) }}
</div>
<div class="font-semibold">
{{ formatMinutes(row.weeklyTotalMinutes) }}
</div>
@@ -72,6 +79,7 @@
</div>
<div class="font-semibold">{{ row.weeklyBreakfastCount ?? 0 }}</div>
<div class="font-semibold">{{ row.weeklyLunchCount ?? 0 }}</div>
<div class="font-semibold">{{ row.weeklyDinnerCount ?? 0 }}</div>
<div class="font-semibold">{{ row.weeklyOvernightCount ?? 0 }}</div>
</div>
</div>
@@ -94,7 +102,7 @@ defineProps<{
isWeekLoading: boolean
weekGridCols: string
weeklySummary: WeeklyWorkHourSummary | null
weekDayHeaders: Array<{ date: string; label: string }>
weekDayHeaders: Array<{ date: string; weekday: string; dayDate: string }>
formatMinutes: (minutes: number) => string
}>()
</script>

View File

@@ -22,7 +22,6 @@ import {
} from '~/services/work-hours'
import {
formatDateLongFr,
formatWeekDayHeaderFr,
formatWeekRangeFr,
getIsoWeekNumber,
getOffsetFromTodayYmd,
@@ -73,10 +72,10 @@ export const useDriverHoursPage = () => {
const dayGridCols = computed(() => {
const metricCol = '0.4fr'
const validationCols = isAdmin.value ? `${metricCol}` : `${metricCol} ${metricCol}`
return `1.2fr 0.6fr 0.8fr 0.8fr ${metricCol} ${metricCol} ${metricCol} ${metricCol} ${validationCols}`
return `1.2fr 0.6fr 0.8fr 0.8fr 0.8fr ${metricCol} ${metricCol} ${metricCol} ${metricCol} ${metricCol} ${validationCols}`
})
const weekGridCols = '1.6fr repeat(7, 1fr) repeat(6, 0.6fr) repeat(3, 0.4fr)'
const weekGridCols = '1.6fr repeat(7, 0.6fr) repeat(7, 0.6fr) repeat(4, 0.4fr)'
const sites = computed<Site[]>(() => {
const siteMap = new Map<number, Site>()
@@ -265,7 +264,13 @@ export const useDriverHoursPage = () => {
const weekDayHeaders = computed(() => {
const days = weeklySummary.value?.days ?? []
return days.map((date) => ({ date, label: formatWeekDayHeaderFr(date) }))
return days.map((date) => {
const parsed = parseYmd(date)
if (!parsed) return { date, weekday: '', dayDate: '' }
const weekday = new Intl.DateTimeFormat('fr-FR', { weekday: 'short' }).format(parsed)
const dayDate = new Intl.DateTimeFormat('fr-FR', { day: '2-digit', month: '2-digit' }).format(parsed)
return { date, weekday, dayDate }
})
})
const shiftDate = (steps: number) => {
@@ -331,8 +336,10 @@ export const useDriverHoursPage = () => {
workHourId: null,
dayHours: '',
nightHours: '',
workshopHours: '',
hasBreakfast: false,
hasLunch: false,
hasDinner: false,
hasOvernight: false,
isSiteValid: false,
isValid: false,
@@ -357,8 +364,9 @@ export const useDriverHoursPage = () => {
const row = rows.value[employeeId] ?? emptyRow()
const dayMinutes = toMinutes(row.dayHours)
const nightMinutes = toMinutes(row.nightHours)
const totalMinutes = dayMinutes + nightMinutes
return { dayMinutes, nightMinutes, totalMinutes }
const workshopMinutes = toMinutes(row.workshopHours)
const totalMinutes = dayMinutes + nightMinutes + workshopMinutes
return { dayMinutes, nightMinutes, workshopMinutes, totalMinutes }
}
const getRowAbsenceLabel = (employeeId: number) => {
@@ -412,8 +420,10 @@ export const useDriverHoursPage = () => {
workHourId: workHour?.id ?? null,
dayHours: minutesToTimeString(workHour?.dayHoursMinutes),
nightHours: minutesToTimeString(workHour?.nightHoursMinutes),
workshopHours: minutesToTimeString(workHour?.workshopHoursMinutes),
hasBreakfast: workHour?.hasBreakfast ?? false,
hasLunch: workHour?.hasLunch ?? false,
hasDinner: workHour?.hasDinner ?? false,
hasOvernight: workHour?.hasOvernight ?? false,
isSiteValid: workHour?.isSiteValid ?? false,
isValid: workHour?.isValid ?? false,
@@ -556,8 +566,10 @@ export const useDriverHoursPage = () => {
isPresentAfternoon: false,
dayHoursMinutes: null,
nightHoursMinutes: null,
workshopHoursMinutes: null,
hasBreakfast: false,
hasLunch: false,
hasDinner: false,
hasOvernight: false
})
@@ -859,6 +871,7 @@ export const useDriverHoursPage = () => {
const row = rows.value[employeeId] ?? emptyRow()
const dayMin = toMinutes(row.dayHours)
const nightMin = toMinutes(row.nightHours)
const workshopMin = toMinutes(row.workshopHours)
return {
employeeId,
@@ -872,8 +885,10 @@ export const useDriverHoursPage = () => {
isPresentAfternoon: false,
dayHoursMinutes: dayMin || null,
nightHoursMinutes: nightMin || null,
workshopHoursMinutes: workshopMin || null,
hasBreakfast: row.hasBreakfast,
hasLunch: row.hasLunch,
hasDinner: row.hasDinner,
hasOvernight: row.hasOvernight
}
})

View File

@@ -21,14 +21,16 @@
<NuxtLink
to="/hours"
class="flex items-center gap-2 py-2 text-md text-black hover:bg-tertiary-500 hover:text-primary-500"
:class="route.path.startsWith('/hours')
? 'bg-tertiary-500 text-primary-500 font-bold'
: ''"
:class="[
route.path.startsWith('/hours') ? 'bg-tertiary-500 text-primary-500 font-bold' : '',
!isAdmin ? 'border-t border-secondary-500 pt-3' : ''
]"
>
<Icon name="mdi:clock-time-four-outline" size="24"/>
<p>Heures</p>
</NuxtLink>
<NuxtLink
v-if="isAdmin"
to="/driver-hours"
class="flex items-center gap-2 py-2 text-md text-black hover:bg-tertiary-500 hover:text-primary-500"
:class="route.path.startsWith('/driver-hours')

View File

@@ -68,7 +68,8 @@ const handleSubmit = async () => {
try {
await auth.login(username.value, password.value)
await router.push('/calendar')
const isAdmin = auth.user?.roles?.includes('ROLE_ADMIN')
await router.push(isAdmin ? '/calendar' : '/hours')
} finally {
isSubmitting.value = false
}

View File

@@ -15,8 +15,10 @@ export type WorkHour = {
isPresentAfternoon?: boolean
dayHoursMinutes?: number | null
nightHoursMinutes?: number | null
workshopHoursMinutes?: number | null
hasBreakfast?: boolean
hasLunch?: boolean
hasDinner?: boolean
hasOvernight?: boolean
isSiteValid?: boolean
isValid?: boolean
@@ -35,8 +37,10 @@ export type WorkHourEntryPayload = {
isPresentAfternoon?: boolean
dayHoursMinutes?: number | null
nightHoursMinutes?: number | null
workshopHoursMinutes?: number | null
hasBreakfast?: boolean
hasLunch?: boolean
hasDinner?: boolean
hasOvernight?: boolean
}
@@ -44,6 +48,7 @@ export type WeeklyWorkHourDailySummary = {
date: string
dayMinutes: number
nightMinutes: number
workshopMinutes?: number
totalMinutes: number
present?: number | null
hasAbsence?: boolean
@@ -51,6 +56,7 @@ export type WeeklyWorkHourDailySummary = {
absenceColor?: string | null
hasBreakfast?: boolean
hasLunch?: boolean
hasDinner?: boolean
hasOvernight?: boolean
}
@@ -65,6 +71,7 @@ export type WeeklyWorkHourRowSummary = {
daily: WeeklyWorkHourDailySummary[]
weeklyDayMinutes: number
weeklyNightMinutes: number
weeklyWorkshopMinutes?: number
weeklyTotalMinutes: number
weeklyPresenceCount?: number
weeklyOvertimeTotalMinutes?: number
@@ -74,6 +81,7 @@ export type WeeklyWorkHourRowSummary = {
isDriver?: boolean
weeklyBreakfastCount?: number
weeklyLunchCount?: number
weeklyDinnerCount?: number
weeklyOvernightCount?: number
}
@@ -106,8 +114,10 @@ export type DriverHourRow = {
workHourId: number | null
dayHours: string
nightHours: string
workshopHours: string
hasBreakfast: boolean
hasLunch: boolean
hasDinner: boolean
hasOvernight: boolean
isSiteValid: boolean
isValid: boolean

View File

@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20260316100000 extends AbstractMigration
{
public function getDescription(): string
{
return 'Add has_dinner column to work_hours';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE work_hours ADD has_dinner BOOLEAN DEFAULT FALSE NOT NULL');
}
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE work_hours DROP COLUMN has_dinner');
}
}

View File

@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20260316100100 extends AbstractMigration
{
public function getDescription(): string
{
return 'Add workshop_hours_minutes column to work_hours';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE work_hours ADD workshop_hours_minutes INTEGER DEFAULT NULL');
}
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE work_hours DROP COLUMN workshop_hours_minutes');
}
}

View File

@@ -10,6 +10,7 @@ final class WeeklyDaySummary
public string $date,
public int $dayMinutes,
public int $nightMinutes,
public int $workshopMinutes,
public int $totalMinutes,
public ?float $present = null,
public bool $hasAbsence = false,
@@ -17,6 +18,7 @@ final class WeeklyDaySummary
public ?string $absenceColor = null,
public bool $hasBreakfast = false,
public bool $hasLunch = false,
public bool $hasDinner = false,
public bool $hasOvernight = false,
) {}
}

View File

@@ -20,6 +20,7 @@ final class WeeklySummaryRow
public array $daily,
public int $weeklyDayMinutes,
public int $weeklyNightMinutes,
public int $weeklyWorkshopMinutes,
public int $weeklyTotalMinutes,
public float $weeklyPresenceCount,
public int $weeklyOvertimeTotalMinutes,
@@ -29,6 +30,7 @@ final class WeeklySummaryRow
public bool $isDriver = false,
public int $weeklyBreakfastCount = 0,
public int $weeklyLunchCount = 0,
public int $weeklyDinnerCount = 0,
public int $weeklyOvernightCount = 0,
) {}
}

View File

@@ -107,6 +107,10 @@ class WorkHour
#[Groups(['work_hour:read'])]
private ?int $nightHoursMinutes = null;
#[ORM\Column(type: 'integer', nullable: true)]
#[Groups(['work_hour:read'])]
private ?int $workshopHoursMinutes = null;
#[ORM\Column(type: 'boolean', options: ['default' => false])]
#[Groups(['work_hour:read'])]
private bool $hasBreakfast = false;
@@ -115,6 +119,10 @@ class WorkHour
#[Groups(['work_hour:read'])]
private bool $hasLunch = false;
#[ORM\Column(type: 'boolean', options: ['default' => false])]
#[Groups(['work_hour:read'])]
private bool $hasDinner = false;
#[ORM\Column(type: 'boolean', options: ['default' => false])]
#[Groups(['work_hour:read'])]
private bool $hasOvernight = false;
@@ -256,6 +264,18 @@ class WorkHour
return $this;
}
public function getWorkshopHoursMinutes(): ?int
{
return $this->workshopHoursMinutes;
}
public function setWorkshopHoursMinutes(?int $workshopHoursMinutes): self
{
$this->workshopHoursMinutes = $workshopHoursMinutes;
return $this;
}
public function getHasBreakfast(): bool
{
return $this->hasBreakfast;
@@ -280,6 +300,18 @@ class WorkHour
return $this;
}
public function getHasDinner(): bool
{
return $this->hasDinner;
}
public function setHasDinner(bool $hasDinner): self
{
$this->hasDinner = $hasDinner;
return $this;
}
public function getHasOvernight(): bool
{
return $this->hasOvernight;

View File

@@ -229,8 +229,10 @@ final readonly class WorkHourBulkUpsertProcessor implements ProcessorInterface
* isPresentAfternoon:bool,
* dayHoursMinutes:?int,
* nightHoursMinutes:?int,
* workshopHoursMinutes:?int,
* hasBreakfast:bool,
* hasLunch:bool,
* hasDinner:bool,
* hasOvernight:bool
* }
*/
@@ -238,37 +240,41 @@ final readonly class WorkHourBulkUpsertProcessor implements ProcessorInterface
{
if ($isDriver) {
return [
'morningFrom' => null,
'morningTo' => null,
'afternoonFrom' => null,
'afternoonTo' => null,
'eveningFrom' => null,
'eveningTo' => null,
'isPresentMorning' => false,
'isPresentAfternoon' => false,
'dayHoursMinutes' => $this->normalizeMinutes($entry['dayHoursMinutes'] ?? null, $employeeId, 'dayHoursMinutes'),
'nightHoursMinutes' => $this->normalizeMinutes($entry['nightHoursMinutes'] ?? null, $employeeId, 'nightHoursMinutes'),
'hasBreakfast' => $this->normalizePresence($entry['hasBreakfast'] ?? false, $employeeId, 'hasBreakfast'),
'hasLunch' => $this->normalizePresence($entry['hasLunch'] ?? false, $employeeId, 'hasLunch'),
'hasOvernight' => $this->normalizePresence($entry['hasOvernight'] ?? false, $employeeId, 'hasOvernight'),
'morningFrom' => null,
'morningTo' => null,
'afternoonFrom' => null,
'afternoonTo' => null,
'eveningFrom' => null,
'eveningTo' => null,
'isPresentMorning' => false,
'isPresentAfternoon' => false,
'dayHoursMinutes' => $this->normalizeMinutes($entry['dayHoursMinutes'] ?? null, $employeeId, 'dayHoursMinutes'),
'nightHoursMinutes' => $this->normalizeMinutes($entry['nightHoursMinutes'] ?? null, $employeeId, 'nightHoursMinutes'),
'workshopHoursMinutes' => $this->normalizeMinutes($entry['workshopHoursMinutes'] ?? null, $employeeId, 'workshopHoursMinutes'),
'hasBreakfast' => $this->normalizePresence($entry['hasBreakfast'] ?? false, $employeeId, 'hasBreakfast'),
'hasLunch' => $this->normalizePresence($entry['hasLunch'] ?? false, $employeeId, 'hasLunch'),
'hasDinner' => $this->normalizePresence($entry['hasDinner'] ?? false, $employeeId, 'hasDinner'),
'hasOvernight' => $this->normalizePresence($entry['hasOvernight'] ?? false, $employeeId, 'hasOvernight'),
];
}
if ($isPresenceTracking) {
return [
'morningFrom' => null,
'morningTo' => null,
'afternoonFrom' => null,
'afternoonTo' => null,
'eveningFrom' => null,
'eveningTo' => null,
'isPresentMorning' => $this->normalizePresence($entry['isPresentMorning'] ?? false, $employeeId, 'isPresentMorning'),
'isPresentAfternoon' => $this->normalizePresence($entry['isPresentAfternoon'] ?? false, $employeeId, 'isPresentAfternoon'),
'dayHoursMinutes' => null,
'nightHoursMinutes' => null,
'hasBreakfast' => false,
'hasLunch' => false,
'hasOvernight' => false,
'morningFrom' => null,
'morningTo' => null,
'afternoonFrom' => null,
'afternoonTo' => null,
'eveningFrom' => null,
'eveningTo' => null,
'isPresentMorning' => $this->normalizePresence($entry['isPresentMorning'] ?? false, $employeeId, 'isPresentMorning'),
'isPresentAfternoon' => $this->normalizePresence($entry['isPresentAfternoon'] ?? false, $employeeId, 'isPresentAfternoon'),
'dayHoursMinutes' => null,
'nightHoursMinutes' => null,
'workshopHoursMinutes' => null,
'hasBreakfast' => false,
'hasLunch' => false,
'hasDinner' => false,
'hasOvernight' => false,
];
}
@@ -281,13 +287,15 @@ final readonly class WorkHourBulkUpsertProcessor implements ProcessorInterface
'eveningTo' => $this->normalizeTime($entry['eveningTo'] ?? null, $employeeId, 'eveningTo'),
// On conserve aussi la présence si envoyée (cas forfait affiché côté UI),
// même si le contrat résolu ce jour est en suivi horaire.
'isPresentMorning' => $this->normalizePresence($entry['isPresentMorning'] ?? false, $employeeId, 'isPresentMorning'),
'isPresentAfternoon' => $this->normalizePresence($entry['isPresentAfternoon'] ?? false, $employeeId, 'isPresentAfternoon'),
'dayHoursMinutes' => null,
'nightHoursMinutes' => null,
'hasBreakfast' => false,
'hasLunch' => false,
'hasOvernight' => false,
'isPresentMorning' => $this->normalizePresence($entry['isPresentMorning'] ?? false, $employeeId, 'isPresentMorning'),
'isPresentAfternoon' => $this->normalizePresence($entry['isPresentAfternoon'] ?? false, $employeeId, 'isPresentAfternoon'),
'dayHoursMinutes' => null,
'nightHoursMinutes' => null,
'workshopHoursMinutes' => null,
'hasBreakfast' => false,
'hasLunch' => false,
'hasDinner' => false,
'hasOvernight' => false,
];
}
@@ -368,8 +376,10 @@ final readonly class WorkHourBulkUpsertProcessor implements ProcessorInterface
* isPresentAfternoon:bool,
* dayHoursMinutes:?int,
* nightHoursMinutes:?int,
* workshopHoursMinutes:?int,
* hasBreakfast:bool,
* hasLunch:bool,
* hasDinner:bool,
* hasOvernight:bool
* } $entry
*/
@@ -385,8 +395,10 @@ final readonly class WorkHourBulkUpsertProcessor implements ProcessorInterface
&& false === $entry['isPresentAfternoon']
&& (null === $entry['dayHoursMinutes'] || 0 === $entry['dayHoursMinutes'])
&& (null === $entry['nightHoursMinutes'] || 0 === $entry['nightHoursMinutes'])
&& (null === $entry['workshopHoursMinutes'] || 0 === $entry['workshopHoursMinutes'])
&& false === $entry['hasBreakfast']
&& false === $entry['hasLunch']
&& false === $entry['hasDinner']
&& false === $entry['hasOvernight'];
}
@@ -402,8 +414,10 @@ final readonly class WorkHourBulkUpsertProcessor implements ProcessorInterface
* isPresentAfternoon:bool,
* dayHoursMinutes:?int,
* nightHoursMinutes:?int,
* workshopHoursMinutes:?int,
* hasBreakfast:bool,
* hasLunch:bool,
* hasDinner:bool,
* hasOvernight:bool
* } $entry
*/
@@ -420,8 +434,10 @@ final readonly class WorkHourBulkUpsertProcessor implements ProcessorInterface
->setIsPresentAfternoon($entry['isPresentAfternoon'])
->setDayHoursMinutes($entry['dayHoursMinutes'])
->setNightHoursMinutes($entry['nightHoursMinutes'])
->setWorkshopHoursMinutes($entry['workshopHoursMinutes'])
->setHasBreakfast($entry['hasBreakfast'])
->setHasLunch($entry['hasLunch'])
->setHasDinner($entry['hasDinner'])
->setHasOvernight($entry['hasOvernight'])
// Toute modification invalide la validation chef de site.
->setIsSiteValid(false)
@@ -442,8 +458,10 @@ final readonly class WorkHourBulkUpsertProcessor implements ProcessorInterface
* isPresentAfternoon:bool,
* dayHoursMinutes:?int,
* nightHoursMinutes:?int,
* workshopHoursMinutes:?int,
* hasBreakfast:bool,
* hasLunch:bool,
* hasDinner:bool,
* hasOvernight:bool
* } $entry
*/
@@ -459,8 +477,10 @@ final readonly class WorkHourBulkUpsertProcessor implements ProcessorInterface
&& $workHour->getIsPresentAfternoon() === $entry['isPresentAfternoon']
&& $workHour->getDayHoursMinutes() === $entry['dayHoursMinutes']
&& $workHour->getNightHoursMinutes() === $entry['nightHoursMinutes']
&& $workHour->getWorkshopHoursMinutes() === $entry['workshopHoursMinutes']
&& $workHour->getHasBreakfast() === $entry['hasBreakfast']
&& $workHour->getHasLunch() === $entry['hasLunch']
&& $workHour->getHasDinner() === $entry['hasDinner']
&& $workHour->getHasOvernight() === $entry['hasOvernight'];
}
}

View File

@@ -127,14 +127,16 @@ final readonly class WorkHourWeeklySummaryProvider implements ProviderInterface
// Pré-calcul des métriques par salarié/date pour simplifier l'agrégation finale.
$dateKey = $workHour->getWorkDate()->format('Y-m-d');
$metricsByEmployeeDate[$employeeId][$dateKey] = [
'metrics' => $this->computeMetrics($workHour),
'isPresentMorning' => $workHour->getIsPresentMorning(),
'isPresentAfternoon' => $workHour->getIsPresentAfternoon(),
'dayHoursMinutes' => $workHour->getDayHoursMinutes(),
'nightHoursMinutes' => $workHour->getNightHoursMinutes(),
'hasBreakfast' => $workHour->getHasBreakfast(),
'hasLunch' => $workHour->getHasLunch(),
'hasOvernight' => $workHour->getHasOvernight(),
'metrics' => $this->computeMetrics($workHour),
'isPresentMorning' => $workHour->getIsPresentMorning(),
'isPresentAfternoon' => $workHour->getIsPresentAfternoon(),
'dayHoursMinutes' => $workHour->getDayHoursMinutes(),
'nightHoursMinutes' => $workHour->getNightHoursMinutes(),
'workshopHoursMinutes' => $workHour->getWorkshopHoursMinutes(),
'hasBreakfast' => $workHour->getHasBreakfast(),
'hasLunch' => $workHour->getHasLunch(),
'hasDinner' => $workHour->getHasDinner(),
'hasOvernight' => $workHour->getHasOvernight(),
];
}
@@ -185,14 +187,16 @@ final readonly class WorkHourWeeklySummaryProvider implements ProviderInterface
continue;
}
$weeklyDayMinutes = 0;
$weeklyNightMinutes = 0;
$weeklyTotalMinutes = 0;
$weeklyPresenceCount = 0.0;
$weeklyBreakfastCount = 0;
$weeklyLunchCount = 0;
$weeklyOvernightCount = 0;
$daily = [];
$weeklyDayMinutes = 0;
$weeklyNightMinutes = 0;
$weeklyWorkshopMinutes = 0;
$weeklyTotalMinutes = 0;
$weeklyPresenceCount = 0.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]]
@@ -217,21 +221,27 @@ final readonly class WorkHourWeeklySummaryProvider implements ProviderInterface
$hasBreakfast = false;
$hasLunch = false;
$hasDinner = false;
$hasOvernight = false;
if ($isDateDriver) {
$dayMinutes = ($entry['dayHoursMinutes'] ?? 0);
$nightMinutes = ($entry['nightHoursMinutes'] ?? 0);
$totalMinutes = $dayMinutes + $nightMinutes;
$hasBreakfast = $entry['hasBreakfast'] ?? false;
$hasLunch = $entry['hasLunch'] ?? false;
$hasOvernight = $entry['hasOvernight'] ?? false;
$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;
if ($hasBreakfast) {
++$weeklyBreakfastCount;
}
if ($hasLunch) {
++$weeklyLunchCount;
}
if ($hasDinner) {
++$weeklyDinnerCount;
}
if ($hasOvernight) {
++$weeklyOvernightCount;
}
@@ -239,9 +249,10 @@ final readonly class WorkHourWeeklySummaryProvider implements ProviderInterface
$metrics = $entry['metrics'] ?? new WorkMetrics();
// Les absences "comptées comme travaillées" alimentent le total du jour.
$metrics->addCreditedMinutes($creditedMinutes);
$dayMinutes = $metrics->dayMinutes;
$nightMinutes = $metrics->nightMinutes;
$totalMinutes = $metrics->totalMinutes;
$dayMinutes = $metrics->dayMinutes;
$nightMinutes = $metrics->nightMinutes;
$workshopMinutes = 0;
$totalMinutes = $metrics->totalMinutes;
}
$present = null;
@@ -256,6 +267,7 @@ final readonly class WorkHourWeeklySummaryProvider implements ProviderInterface
$weeklyDayMinutes += $dayMinutes;
$weeklyNightMinutes += $nightMinutes;
$weeklyWorkshopMinutes += $workshopMinutes;
$weeklyTotalMinutes += $totalMinutes;
if (null !== $present) {
$weeklyPresenceCount += $present;
@@ -265,6 +277,7 @@ final readonly class WorkHourWeeklySummaryProvider implements ProviderInterface
date: $date,
dayMinutes: $dayMinutes,
nightMinutes: $nightMinutes,
workshopMinutes: $workshopMinutes,
totalMinutes: $totalMinutes,
present: $present,
hasAbsence: $absenceByEmployeeDate[$employeeId][$date] ?? false,
@@ -272,6 +285,7 @@ final readonly class WorkHourWeeklySummaryProvider implements ProviderInterface
absenceColor: $absenceColorByEmployeeDate[$employeeId][$date] ?? null,
hasBreakfast: $hasBreakfast,
hasLunch: $hasLunch,
hasDinner: $hasDinner,
hasOvernight: $hasOvernight,
);
}
@@ -304,6 +318,7 @@ final readonly class WorkHourWeeklySummaryProvider implements ProviderInterface
daily: $daily,
weeklyDayMinutes: $weeklyDayMinutes,
weeklyNightMinutes: $weeklyNightMinutes,
weeklyWorkshopMinutes: $weeklyWorkshopMinutes,
weeklyTotalMinutes: $weeklyTotalMinutes,
weeklyPresenceCount: $weeklyPresenceCount,
weeklyOvertimeTotalMinutes: $weeklyOvertimeTotalMinutes,
@@ -313,6 +328,7 @@ final readonly class WorkHourWeeklySummaryProvider implements ProviderInterface
isDriver: $isDriver,
weeklyBreakfastCount: $weeklyBreakfastCount,
weeklyLunchCount: $weeklyLunchCount,
weeklyDinnerCount: $weeklyDinnerCount,
weeklyOvernightCount: $weeklyOvernightCount,
);
}