fix : redirection après login + écran des heures chauffeurs
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
26
migrations/Version20260316100000.php
Normal file
26
migrations/Version20260316100000.php
Normal 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');
|
||||
}
|
||||
}
|
||||
26
migrations/Version20260316100100.php
Normal file
26
migrations/Version20260316100100.php
Normal 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');
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
) {}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
) {}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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'];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user