feat(absences) : avancement module absences + suppression du portail client

Deux lots regroupés sur la branche feat/absence-management.

Suppression complète du portail client :
- retire ROLE_CLIENT (security.yaml) ; User::getRoles() ajoute toujours ROLE_USER
- supprime l'entité ClientTicket (+ repo, states, relations), User.client et
  User.allowedProjects, NotificationService, ProjectAllowedExtension, le bloc
  ROLE_CLIENT de MailAccessChecker
- front : pages /portal, layout portal, composants client-ticket/,
  AdminClientTicketTab, services/dto/i18n/docs associés
- fixtures : retire les users client-liot / client-acme
- migration Version20260522110000 (drop client_ticket, user_allowed_projects,
  colonnes liées ; task_document.task_id -> NOT NULL)
- tests : retire les cas obsolètes testant le blocage des clients sur le mail

Module gestion des absences (WIP) :
- entités / migrations (Version20260521160000, Version20260522090000)
- pages absences.vue / team-absences.vue, composants frontend/components/absence/
- services front, AccrueLeaveCommand, PublicHolidayController

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matthieu
2026-05-22 11:31:31 +02:00
parent de98924fd3
commit 2a0b202d32
109 changed files with 3918 additions and 3656 deletions

View File

@@ -1,5 +1,8 @@
<template>
<MalioDrawer v-model="isOpen" :title="isEditing ? $t('timeEntries.editEntry') : $t('timeEntries.addEntry')">
<MalioDrawer v-model="isOpen">
<template #header>
<h2 class="text-xl font-bold">{{ isEditing ? $t('timeEntries.editEntry') : $t('timeEntries.addEntry') }}</h2>
</template>
<form class="space-y-4" @submit.prevent="onSubmit">
<div>
<label class="mb-1 block text-sm font-semibold text-neutral-700">Titre</label>
@@ -58,7 +61,7 @@
v-model="form.userId"
:options="userOptions"
label="Utilisateur"
min-width="w-full"
group-class="w-full"
/>
<MalioSelect
@@ -66,7 +69,7 @@
:options="projectOptions"
label="Projet"
empty-option-label=" Aucun "
min-width="w-full"
group-class="w-full"
/>
<div>

View File

@@ -9,6 +9,7 @@
v-for="day in days"
:key="'header-' + day.dateStr"
class="flex-1 border-r border-neutral-100 py-2 text-center"
:class="{ 'bg-orange-50': day.holiday }"
>
<div class="text-lg font-bold" :class="isToday(day.date) ? 'text-orange-500' : 'text-neutral-900'">
{{ day.dayNum }}
@@ -16,6 +17,14 @@
<div class="text-xs" :class="isToday(day.date) ? 'text-orange-500' : 'text-neutral-500'">
{{ day.label }}
</div>
<div
v-if="day.holiday"
class="flex items-center justify-center gap-0.5 truncate px-1 text-[10px] font-medium text-amber-600"
:title="day.holiday"
>
<Icon name="mdi:star-four-points-outline" size="10" class="flex-shrink-0" />
<span class="truncate">{{ day.holiday }}</span>
</div>
<div class="text-[10px] text-neutral-400">{{ day.totalFormatted }}</div>
</div>
</div>
@@ -40,6 +49,7 @@
:key="day.dateStr"
:ref="(el) => { dayColumnEls[dayIndex] = el as HTMLElement }"
class="relative flex-1 border-r border-neutral-100"
:class="{ 'bg-orange-50': day.holiday }"
@click="onClickGrid($event, day)"
@contextmenu.prevent="onContextMenuGrid($event, day)"
>
@@ -141,8 +151,10 @@
<script setup lang="ts">
import type { TimeEntry } from '~/services/dto/time-entry'
import { useAbsenceService } from '~/services/absences'
const { t } = useI18n()
const absenceService = useAbsenceService()
const props = defineProps<{
entries: TimeEntry[]
@@ -209,6 +221,23 @@ onMounted(() => {
})
})
// --- Public holidays (computed server-side, shared with the absence calendar) ---
const holidays = ref<Record<string, string>>({})
async function loadHolidays() {
const count = props.viewMode === 'week' ? 7 : 1
const start = new Date(props.startDate)
const end = new Date(start)
end.setDate(end.getDate() + count - 1)
try {
holidays.value = await absenceService.getPublicHolidays(toDateStr(start), toDateStr(end))
} catch {
holidays.value = {}
}
}
watch(() => [props.startDate, props.viewMode], loadHolidays, { immediate: true })
// --- Days computation ---
const days = computed(() => {
const count = props.viewMode === 'week' ? 7 : 1
@@ -231,6 +260,7 @@ const days = computed(() => {
dateStr,
dayNum: d.getDate(),
label: dayLabels[d.getDay()],
holiday: holidays.value[dateStr] ?? null,
totalFormatted: `${String(totalH).padStart(2, '0')}:${String(totalM).padStart(2, '0')}:${String(totalS).padStart(2, '0')}`,
})
}

View File

@@ -1,5 +1,8 @@
<template>
<MalioDrawer v-model="isOpen" :title="$t('timeEntries.exportTitle')" drawer-class="max-w-lg">
<MalioDrawer v-model="isOpen" drawer-class="max-w-lg">
<template #header>
<h2 class="text-xl font-bold">{{ $t('timeEntries.exportTitle') }}</h2>
</template>
<div class="flex flex-col gap-6 p-4">
<!-- Period presets -->
<div>
@@ -52,7 +55,7 @@
:label="$t('timeEntries.exportUsers')"
:display-tag="true"
:display-select-all="true"
min-width="!w-full"
group-class="!w-full"
/>
</div>
@@ -63,7 +66,7 @@
:options="clientOptions"
:label="$t('timeEntries.exportClient')"
:empty-option-label="$t('timeEntries.exportAllClients')"
min-width="!w-full"
group-class="!w-full"
/>
</div>
@@ -75,7 +78,7 @@
:label="$t('timeEntries.exportProjects')"
:display-tag="true"
:display-select-all="true"
min-width="!w-full"
group-class="!w-full"
/>
</div>
@@ -87,7 +90,7 @@
:label="$t('timeEntries.exportTags')"
:display-tag="true"
:display-select-all="true"
min-width="!w-full"
group-class="!w-full"
/>
</div>