Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2f25a3cd52 | ||
| 1fe7f2cdde |
@@ -30,6 +30,7 @@
|
|||||||
- Contracts: `trackingMode` (TIME=hours, PRESENCE=half-days), `weeklyHours`
|
- Contracts: `trackingMode` (TIME=hours, PRESENCE=half-days), `weeklyHours`
|
||||||
- Contract types: FORFAIT, THIRTY_FIVE_HOURS, THIRTY_NINE_HOURS, INTERIM, CUSTOM
|
- Contract types: FORFAIT, THIRTY_FIVE_HOURS, THIRTY_NINE_HOURS, INTERIM, CUSTOM
|
||||||
- Contract nature (per period): CDI, CDD, INTERIM
|
- Contract nature (per period): CDI, CDD, INTERIM
|
||||||
|
- **Agence d'intérim** (`InterimAgency` entity, table `interim_agencies`): optionnelle sur `EmployeeContractPeriod` quand nature = INTERIM. Pas de CRUD UI — gérée en BDD. API lecture seule `GET /interim_agencies`. Affichée "Intérim (NomAgence)" sur la liste employés et l'historique contrat.
|
||||||
- Employee contract history: `employee_contract_periods`, resolved by `EmployeeContractResolver`
|
- Employee contract history: `employee_contract_periods`, resolved by `EmployeeContractResolver`
|
||||||
- **Planning jours travaillés** (`EmployeeContractPeriod.workDaysHours` : JSON `{iso_day: minutes}`) : obligatoire pour tout contrat TIME **hors 35h/39h/INTERIM** (ex. 4h, 25h, 28h). Somme = `weeklyHours × 60`. Utilisé par `HolidayVirtualHoursResolver` (crédit férié) et `WorkedHoursCreditPolicy` (crédit absence) pour ne créditer que les jours effectivement travaillés. Validation : `EmployeeContractPeriodValidator::assertWorkDaysHours`.
|
- **Planning jours travaillés** (`EmployeeContractPeriod.workDaysHours` : JSON `{iso_day: minutes}`) : obligatoire pour tout contrat TIME **hors 35h/39h/INTERIM** (ex. 4h, 25h, 28h). Somme = `weeklyHours × 60`. Utilisé par `HolidayVirtualHoursResolver` (crédit férié) et `WorkedHoursCreditPolicy` (crédit absence) pour ne créditer que les jours effectivement travaillés. Validation : `EmployeeContractPeriodValidator::assertWorkDaysHours`.
|
||||||
- Absences: stored per day (auto-split), AM/PM/full day, clear corresponding hour slots
|
- Absences: stored per day (auto-split), AM/PM/full day, clear corresponding hour slots
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
parameters:
|
parameters:
|
||||||
app.version: '0.1.89'
|
app.version: '0.1.90'
|
||||||
|
|||||||
@@ -130,6 +130,7 @@ Documents complementaires:
|
|||||||
- pas de bonus 25%
|
- pas de bonus 25%
|
||||||
- pas de bonus 50%
|
- pas de bonus 50%
|
||||||
- pas de total récup
|
- pas de total récup
|
||||||
|
- agence d'intérim optionnelle (table `interim_agencies`): affichée sur la fiche employé et le détail contrat sous la forme "Intérim (NomAgence)"
|
||||||
|
|
||||||
## 6bis) Heures Conducteurs
|
## 6bis) Heures Conducteurs
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
:style="{ gridTemplateColumns: dayGridCols }"
|
:style="{ gridTemplateColumns: dayGridCols }"
|
||||||
>
|
>
|
||||||
<span>Nom</span>
|
<span>Nom</span>
|
||||||
<span class="pl-2">Absence</span>
|
<span class="pl-2">Statut</span>
|
||||||
<span class="pl-4">Heure de jour</span>
|
<span class="pl-4">Heure de jour</span>
|
||||||
<span class="pl-2">Heure de nuit</span>
|
<span class="pl-2">Heure de nuit</span>
|
||||||
<span class="pl-2">Heure atelier</span>
|
<span class="pl-2">Heure atelier</span>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
:key="`${item.startDate}-${item.endDate ?? 'open'}-${item.contractId ?? item.contractName}`"
|
:key="`${item.startDate}-${item.endDate ?? 'open'}-${item.contractId ?? item.contractName}`"
|
||||||
class="grid grid-cols-4 border-b border-primary-500 px-6 py-3 text-md font-bold text-primary-500 last:border-b-0 hover:bg-tertiary-500"
|
class="grid grid-cols-4 border-b border-primary-500 px-6 py-3 text-md font-bold text-primary-500 last:border-b-0 hover:bg-tertiary-500"
|
||||||
>
|
>
|
||||||
<p>{{ contractNatureLabel(item.contractNature) }}</p>
|
<p>{{ item.interimAgencyName ? `${contractNatureLabel(item.contractNature)} (${item.interimAgencyName})` : contractNatureLabel(item.contractNature) }}</p>
|
||||||
<p>{{ contractHistoryLabel(item) }}</p>
|
<p>{{ contractHistoryLabel(item) }}</p>
|
||||||
<p>{{ formatDate(item.startDate) }}</p>
|
<p>{{ formatDate(item.startDate) }}</p>
|
||||||
<p>{{ formatDate(item.endDate) }}</p>
|
<p>{{ formatDate(item.endDate) }}</p>
|
||||||
@@ -221,6 +221,22 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div v-if="createContractForm.contractNature === 'INTERIM'">
|
||||||
|
<label class="text-md font-semibold text-neutral-700" for="create-interim-agency">
|
||||||
|
Agence d'intérim
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
id="create-interim-agency"
|
||||||
|
v-model="createContractForm.interimAgencyId"
|
||||||
|
class="mt-2 w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-md text-neutral-900"
|
||||||
|
>
|
||||||
|
<option value="">Aucune</option>
|
||||||
|
<option v-for="agency in interimAgencies" :key="agency.id" :value="agency.id">
|
||||||
|
{{ agency.name }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label class="text-md font-semibold text-neutral-700" for="create-contract-id">
|
<label class="text-md font-semibold text-neutral-700" for="create-contract-id">
|
||||||
Temps de travail <span class="text-red-600">*</span>
|
Temps de travail <span class="text-red-600">*</span>
|
||||||
@@ -282,6 +298,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Contract } from '~/services/dto/contract'
|
import type { Contract } from '~/services/dto/contract'
|
||||||
import type { ContractHistoryItem } from '~/services/dto/employee'
|
import type { ContractHistoryItem } from '~/services/dto/employee'
|
||||||
|
import type { InterimAgency } from '~/services/interim-agencies'
|
||||||
import WorkDaysHoursInput from '~/components/employees/WorkDaysHoursInput.vue'
|
import WorkDaysHoursInput from '~/components/employees/WorkDaysHoursInput.vue'
|
||||||
|
|
||||||
type SuspensionForm = {
|
type SuspensionForm = {
|
||||||
@@ -310,6 +327,7 @@ type CreateContractForm = {
|
|||||||
endDate: string
|
endDate: string
|
||||||
isDriver: boolean
|
isDriver: boolean
|
||||||
workDaysHours: Record<number, number> | null
|
workDaysHours: Record<number, number> | null
|
||||||
|
interimAgencyId: number | ''
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
@@ -351,6 +369,7 @@ const props = defineProps<{
|
|||||||
onSubmitSuspension: (index: number) => void
|
onSubmitSuspension: (index: number) => void
|
||||||
onAddSuspensionForm: () => void
|
onAddSuspensionForm: () => void
|
||||||
currentContractPeriodId?: number | null
|
currentContractPeriodId?: number | null
|
||||||
|
interimAgencies: InterimAgency[]
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const drawerTab = ref<'close' | 'suspend'>('close')
|
const drawerTab = ref<'close' | 'suspend'>('close')
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
:style="{ gridTemplateColumns: dayGridCols }"
|
:style="{ gridTemplateColumns: dayGridCols }"
|
||||||
>
|
>
|
||||||
<span>Nom</span>
|
<span>Nom</span>
|
||||||
<span class="pl-2">Absence</span>
|
<span class="pl-2">Statut</span>
|
||||||
<span class="pl-4">Début matin</span>
|
<span class="pl-4">Début matin</span>
|
||||||
<span class="pr-2">Fin matin</span>
|
<span class="pr-2">Fin matin</span>
|
||||||
<span class="pl-2">Début après-midi</span>
|
<span class="pl-2">Début après-midi</span>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import type { ContractHistoryItem, Employee } from '~/services/dto/employee'
|
|||||||
import { listContracts } from '~/services/contracts'
|
import { listContracts } from '~/services/contracts'
|
||||||
import { updateEmployee } from '~/services/employees'
|
import { updateEmployee } from '~/services/employees'
|
||||||
import { createSuspension, updateSuspension } from '~/services/contractSuspensions'
|
import { createSuspension, updateSuspension } from '~/services/contractSuspensions'
|
||||||
|
import { listInterimAgencies, type InterimAgency } from '~/services/interim-agencies'
|
||||||
import { formatNullableYmdToFr, getTodayYmd, shiftYmd } from '~/utils/date'
|
import { formatNullableYmdToFr, getTodayYmd, shiftYmd } from '~/utils/date'
|
||||||
import { contractNatureLabel, formatWorkDaysHoursSummary, isContractNature, requiresContractEndDate, requiresWorkDaysHours, showsContractEndDate } from '~/utils/contract'
|
import { contractNatureLabel, formatWorkDaysHoursSummary, isContractNature, requiresContractEndDate, requiresWorkDaysHours, showsContractEndDate } from '~/utils/contract'
|
||||||
|
|
||||||
@@ -17,6 +18,7 @@ type SuspensionForm = {
|
|||||||
export const useEmployeeContract = (employee: Ref<Employee | null>, reloadEmployee: () => Promise<void>) => {
|
export const useEmployeeContract = (employee: Ref<Employee | null>, reloadEmployee: () => Promise<void>) => {
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const contracts = ref<Contract[]>([])
|
const contracts = ref<Contract[]>([])
|
||||||
|
const interimAgencies = ref<InterimAgency[]>([])
|
||||||
const isContractDrawerOpen = ref(false)
|
const isContractDrawerOpen = ref(false)
|
||||||
const isContractSubmitting = ref(false)
|
const isContractSubmitting = ref(false)
|
||||||
const isCreateContractDrawerOpen = ref(false)
|
const isCreateContractDrawerOpen = ref(false)
|
||||||
@@ -46,7 +48,8 @@ export const useEmployeeContract = (employee: Ref<Employee | null>, reloadEmploy
|
|||||||
startDate: '',
|
startDate: '',
|
||||||
endDate: '',
|
endDate: '',
|
||||||
isDriver: false,
|
isDriver: false,
|
||||||
workDaysHours: null as Record<number, number> | null
|
workDaysHours: null as Record<number, number> | null,
|
||||||
|
interimAgencyId: '' as number | ''
|
||||||
})
|
})
|
||||||
|
|
||||||
const createValidationTouched = reactive({
|
const createValidationTouched = reactive({
|
||||||
@@ -207,6 +210,7 @@ export const useEmployeeContract = (employee: Ref<Employee | null>, reloadEmploy
|
|||||||
createContractForm.endDate = ''
|
createContractForm.endDate = ''
|
||||||
createContractForm.isDriver = false
|
createContractForm.isDriver = false
|
||||||
createContractForm.workDaysHours = null
|
createContractForm.workDaysHours = null
|
||||||
|
createContractForm.interimAgencyId = ''
|
||||||
createContractForm.startDate = editableContractPeriod.value?.endDate
|
createContractForm.startDate = editableContractPeriod.value?.endDate
|
||||||
? (shiftYmd(editableContractPeriod.value.endDate, 1) ?? editableContractPeriod.value.endDate)
|
? (shiftYmd(editableContractPeriod.value.endDate, 1) ?? editableContractPeriod.value.endDate)
|
||||||
: getTodayYmd()
|
: getTodayYmd()
|
||||||
@@ -283,7 +287,8 @@ export const useEmployeeContract = (employee: Ref<Employee | null>, reloadEmploy
|
|||||||
contractStartDate: createContractForm.startDate,
|
contractStartDate: createContractForm.startDate,
|
||||||
contractEndDate: createContractForm.endDate || null,
|
contractEndDate: createContractForm.endDate || null,
|
||||||
isDriverInput: createContractForm.isDriver,
|
isDriverInput: createContractForm.isDriver,
|
||||||
workDaysHoursInput: requiresCreateWorkDaysHours.value ? createContractForm.workDaysHours : null
|
workDaysHoursInput: requiresCreateWorkDaysHours.value ? createContractForm.workDaysHours : null,
|
||||||
|
interimAgencyId: createContractForm.contractNature === 'INTERIM' && createContractForm.interimAgencyId !== '' ? Number(createContractForm.interimAgencyId) : null
|
||||||
})
|
})
|
||||||
isCreateContractDrawerOpen.value = false
|
isCreateContractDrawerOpen.value = false
|
||||||
await reloadEmployee()
|
await reloadEmployee()
|
||||||
@@ -335,6 +340,16 @@ export const useEmployeeContract = (employee: Ref<Employee | null>, reloadEmploy
|
|||||||
contracts.value = await listContracts()
|
contracts.value = await listContracts()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const loadInterimAgencies = async () => {
|
||||||
|
interimAgencies.value = await listInterimAgencies()
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => createContractForm.contractNature, (nature) => {
|
||||||
|
if (nature !== 'INTERIM') {
|
||||||
|
createContractForm.interimAgencyId = ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
watch(showsCreateContractEndDate, (shows) => {
|
watch(showsCreateContractEndDate, (shows) => {
|
||||||
if (!shows) {
|
if (!shows) {
|
||||||
createContractForm.endDate = ''
|
createContractForm.endDate = ''
|
||||||
@@ -386,6 +401,8 @@ export const useEmployeeContract = (employee: Ref<Employee | null>, reloadEmploy
|
|||||||
submitSuspension,
|
submitSuspension,
|
||||||
addSuspensionForm,
|
addSuspensionForm,
|
||||||
currentActiveContractPeriodId,
|
currentActiveContractPeriodId,
|
||||||
loadContracts
|
interimAgencies,
|
||||||
|
loadContracts,
|
||||||
|
loadInterimAgencies
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ export const useEmployeeDetailPage = () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await contract.loadContracts()
|
await Promise.all([contract.loadContracts(), contract.loadInterimAgencies()])
|
||||||
await loadEmployee()
|
await loadEmployee()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -207,10 +207,10 @@ export const documentationSections: DocSection[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'gestion-types-absence',
|
id: 'gestion-types-absence',
|
||||||
title: 'Gestion des types d\'absence',
|
title: 'Gestion des types de statut',
|
||||||
requiredLevel: 'admin',
|
requiredLevel: 'admin',
|
||||||
blocks: [
|
blocks: [
|
||||||
{ type: 'paragraph', content: 'Les types d\'absence définissent les catégories disponibles lors de la pose d\'une absence.' },
|
{ type: 'paragraph', content: 'Les types de statut définissent les catégories disponibles lors de la pose d\'une absence.' },
|
||||||
{ type: 'list', content: 'Code : identifiant court (max 10 caractères), ex: C, M, AT\nLibellé : nom affiché, ex: Congé, Maladie, Accident du travail\nCouleur : code couleur pour le calendrier et la vue jour\nOption "Compté comme travaillé" : si activé, l\'absence crédite des heures en mode TIME' },
|
{ type: 'list', content: 'Code : identifiant court (max 10 caractères), ex: C, M, AT\nLibellé : nom affiché, ex: Congé, Maladie, Accident du travail\nCouleur : code couleur pour le calendrier et la vue jour\nOption "Compté comme travaillé" : si activé, l\'absence crédite des heures en mode TIME' },
|
||||||
{ type: 'note', content: 'L\'option "Compté comme travaillé" impacte le calcul des heures supplémentaires. En mode TIME, les minutes sont créditées selon le contrat. En mode PRESENCE, aucun crédit n\'est appliqué.' },
|
{ type: 'note', content: 'L\'option "Compté comme travaillé" impacte le calcul des heures supplémentaires. En mode TIME, les minutes sont créditées selon le contrat. En mode PRESENCE, aucun crédit n\'est appliqué.' },
|
||||||
],
|
],
|
||||||
@@ -258,7 +258,7 @@ export const documentationSections: DocSection[] = [
|
|||||||
requiredLevel: 'admin',
|
requiredLevel: 'admin',
|
||||||
blocks: [
|
blocks: [
|
||||||
{ type: 'paragraph', content: 'La création d\'un employé se fait via le drawer d\'ajout.' },
|
{ type: 'paragraph', content: 'La création d\'un employé se fait via le drawer d\'ajout.' },
|
||||||
{ type: 'list', content: 'Champs : prénom, nom, site\nNature du contrat : CDI, CDD ou INTERIM\nType de contrat / temps de travail (Forfait, 35h, 39h, etc.)\nDate de début (obligatoire)\nDate de fin (obligatoire pour CDD et INTERIM)' },
|
{ type: 'list', content: 'Champs : prénom, nom, site\nNature du contrat : CDI, CDD ou INTERIM\nAgence d\'intérim (visible uniquement pour INTERIM, optionnel)\nType de contrat / temps de travail (Forfait, 35h, 39h, etc.)\nDate de début (obligatoire)\nDate de fin (obligatoire pour CDD et INTERIM)' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -82,7 +82,7 @@
|
|||||||
: ''"
|
: ''"
|
||||||
>
|
>
|
||||||
<Icon name="mdi:umbrella-beach-outline" size="24"/>
|
<Icon name="mdi:umbrella-beach-outline" size="24"/>
|
||||||
<p>Types d'absence</p>
|
<p>Types de statut</p>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
to="/users"
|
to="/users"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="h-full flex flex-col overflow-hidden">
|
<div class="h-full flex flex-col overflow-hidden">
|
||||||
<div class="flex items-center justify-between pb-6">
|
<div class="flex items-center justify-between pb-6">
|
||||||
<h1 class="text-4xl font-bold text-primary-500">Types d'absence</h1>
|
<h1 class="text-4xl font-bold text-primary-500">Types de statut</h1>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="rounded-lg bg-primary-500 px-4 py-2 text-md font-semibold text-white hover:bg-secondary-500"
|
class="rounded-lg bg-primary-500 px-4 py-2 text-md font-semibold text-white hover:bg-secondary-500"
|
||||||
@@ -164,7 +164,7 @@ import type { AbsenceType } from '~/services/dto/absence-type'
|
|||||||
import { createAbsenceType, deleteAbsenceType, listAbsenceTypes, updateAbsenceType } from '~/services/absence-types'
|
import { createAbsenceType, deleteAbsenceType, listAbsenceTypes, updateAbsenceType } from '~/services/absence-types'
|
||||||
|
|
||||||
useHead({
|
useHead({
|
||||||
title: 'Types d\'absences'
|
title: 'Types de statut'
|
||||||
})
|
})
|
||||||
|
|
||||||
const isDrawerOpen = ref(false)
|
const isDrawerOpen = ref(false)
|
||||||
|
|||||||
@@ -148,6 +148,7 @@
|
|||||||
:on-submit-suspension="submitSuspension"
|
:on-submit-suspension="submitSuspension"
|
||||||
:on-add-suspension-form="addSuspensionForm"
|
:on-add-suspension-form="addSuspensionForm"
|
||||||
:current-contract-period-id="currentActiveContractPeriodId"
|
:current-contract-period-id="currentActiveContractPeriodId"
|
||||||
|
:interim-agencies="interimAgencies"
|
||||||
/>
|
/>
|
||||||
<div v-else-if="showLeaveTab && activeTab === 'leave'" class="h-full">
|
<div v-else-if="showLeaveTab && activeTab === 'leave'" class="h-full">
|
||||||
<div v-if="isLeaveLoading" class="mt-6 rounded-lg border border-neutral-200 bg-white p-6 text-md text-neutral-600">
|
<div v-if="isLeaveLoading" class="mt-6 rounded-lg border border-neutral-200 bg-white p-6 text-md text-neutral-600">
|
||||||
@@ -295,6 +296,7 @@ const {
|
|||||||
submitSuspension,
|
submitSuspension,
|
||||||
addSuspensionForm,
|
addSuspensionForm,
|
||||||
currentActiveContractPeriodId,
|
currentActiveContractPeriodId,
|
||||||
|
interimAgencies,
|
||||||
isLeaveLoading,
|
isLeaveLoading,
|
||||||
isRttLoading,
|
isRttLoading,
|
||||||
mileageAllowances,
|
mileageAllowances,
|
||||||
|
|||||||
@@ -72,7 +72,7 @@
|
|||||||
class="absolute inset-0 flex items-center justify-center bg-primary-500 p-4 text-white opacity-0 transition-opacity duration-200 group-hover:opacity-100">
|
class="absolute inset-0 flex items-center justify-center bg-primary-500 p-4 text-white opacity-0 transition-opacity duration-200 group-hover:opacity-100">
|
||||||
<div class="w-full rounded-md bg-white/15 p-4 text-sm">
|
<div class="w-full rounded-md bg-white/15 p-4 text-sm">
|
||||||
<p class="text-base font-semibold">{{ employee.lastName }} {{ employee.firstName }}</p>
|
<p class="text-base font-semibold">{{ employee.lastName }} {{ employee.firstName }}</p>
|
||||||
<p><strong>Type:</strong> {{ contractNatureLabel(employee.currentContractNature) }}</p>
|
<p><strong>Type:</strong> {{ employee.currentInterimAgencyName ? `${contractNatureLabel(employee.currentContractNature)} (${employee.currentInterimAgencyName})` : contractNatureLabel(employee.currentContractNature) }}</p>
|
||||||
<p><strong>Temps de travail:</strong> {{ employee.contract?.name ?? '-' }}</p>
|
<p><strong>Temps de travail:</strong> {{ employee.contract?.name ?? '-' }}</p>
|
||||||
<p><strong>Site:</strong> {{ employee.site?.name ?? '-' }}</p>
|
<p><strong>Site:</strong> {{ employee.site?.name ?? '-' }}</p>
|
||||||
<p><strong>Date d'entrée :</strong> {{ employee.entryDate ? employee.entryDate.split('-').reverse().join('/') : '-' }}</p>
|
<p><strong>Date d'entrée :</strong> {{ employee.entryDate ? employee.entryDate.split('-').reverse().join('/') : '-' }}</p>
|
||||||
@@ -147,6 +147,21 @@
|
|||||||
Le type de contrat est obligatoire.
|
Le type de contrat est obligatoire.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="form.contractNature === 'INTERIM'">
|
||||||
|
<label class="text-md font-semibold text-neutral-700" for="interim-agency">
|
||||||
|
Agence d'intérim
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
id="interim-agency"
|
||||||
|
v-model="form.interimAgencyId"
|
||||||
|
class="mt-2 w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-md text-neutral-900"
|
||||||
|
>
|
||||||
|
<option value="">Aucune</option>
|
||||||
|
<option v-for="agency in interimAgencies" :key="agency.id" :value="agency.id">
|
||||||
|
{{ agency.name }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="text-md font-semibold text-neutral-700" for="contract">
|
<label class="text-md font-semibold text-neutral-700" for="contract">
|
||||||
Temps de travail <span class="text-red-600">*</span>
|
Temps de travail <span class="text-red-600">*</span>
|
||||||
@@ -191,7 +206,7 @@
|
|||||||
:class="contractEndDateFieldClass"
|
:class="contractEndDateFieldClass"
|
||||||
/>
|
/>
|
||||||
<p v-if="showContractEndDateError" class="mt-1 text-sm text-red-600">
|
<p v-if="showContractEndDateError" class="mt-1 text-sm text-red-600">
|
||||||
La date de fin est obligatoire pour un CDD.
|
La date de fin est obligatoire pour un CDD ou un Intérim.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="rounded-md border border-neutral-200 bg-neutral-50 p-3">
|
<div class="rounded-md border border-neutral-200 bg-neutral-50 p-3">
|
||||||
@@ -246,6 +261,7 @@ import type {Site} from '~/services/dto/site'
|
|||||||
import {listContracts} from '~/services/contracts'
|
import {listContracts} from '~/services/contracts'
|
||||||
import {createEmployee, deleteEmployee, listEmployees, updateEmployee} from '~/services/employees'
|
import {createEmployee, deleteEmployee, listEmployees, updateEmployee} from '~/services/employees'
|
||||||
import {listSites} from '~/services/sites'
|
import {listSites} from '~/services/sites'
|
||||||
|
import {listInterimAgencies, type InterimAgency} from '~/services/interim-agencies'
|
||||||
import SiteFilterSelector from '~/components/SiteFilterSelector.vue'
|
import SiteFilterSelector from '~/components/SiteFilterSelector.vue'
|
||||||
import SalaryRecapDrawer from '~/components/SalaryRecapDrawer.vue'
|
import SalaryRecapDrawer from '~/components/SalaryRecapDrawer.vue'
|
||||||
import {contractNatureLabel, isContractNature, requiresContractEndDate, showsContractEndDate} from '~/utils/contract'
|
import {contractNatureLabel, isContractNature, requiresContractEndDate, showsContractEndDate} from '~/utils/contract'
|
||||||
@@ -269,6 +285,7 @@ const drawerTitle = computed(() =>
|
|||||||
const employees = ref<Employee[]>([])
|
const employees = ref<Employee[]>([])
|
||||||
const sites = ref<Site[]>([])
|
const sites = ref<Site[]>([])
|
||||||
const contracts = ref<Contract[]>([])
|
const contracts = ref<Contract[]>([])
|
||||||
|
const interimAgencies = ref<InterimAgency[]>([])
|
||||||
const employeeFilter = ref('')
|
const employeeFilter = ref('')
|
||||||
const contractStatusFilter = ref<'active' | 'inactive' | 'all'>('active')
|
const contractStatusFilter = ref<'active' | 'inactive' | 'all'>('active')
|
||||||
const selectedSiteIds = ref<number[]>([])
|
const selectedSiteIds = ref<number[]>([])
|
||||||
@@ -300,7 +317,8 @@ const form = reactive({
|
|||||||
contractStartDate: '',
|
contractStartDate: '',
|
||||||
contractEndDate: '',
|
contractEndDate: '',
|
||||||
isDriver: false,
|
isDriver: false,
|
||||||
workDaysHours: null as Record<number, number> | null
|
workDaysHours: null as Record<number, number> | null,
|
||||||
|
interimAgencyId: '' as number | ''
|
||||||
})
|
})
|
||||||
|
|
||||||
const validationTouched = reactive({
|
const validationTouched = reactive({
|
||||||
@@ -451,8 +469,12 @@ const loadContracts = async () => {
|
|||||||
contracts.value = await listContracts()
|
contracts.value = await listContracts()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const loadInterimAgencies = async () => {
|
||||||
|
interimAgencies.value = await listInterimAgencies()
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await Promise.all([loadEmployees(), loadSites(), loadContracts()])
|
await Promise.all([loadEmployees(), loadSites(), loadContracts(), loadInterimAgencies()])
|
||||||
if (form.contractStartDate === '') {
|
if (form.contractStartDate === '') {
|
||||||
form.contractStartDate = new Date().toISOString().slice(0, 10)
|
form.contractStartDate = new Date().toISOString().slice(0, 10)
|
||||||
}
|
}
|
||||||
@@ -503,7 +525,8 @@ const handleSubmit = async () => {
|
|||||||
contractStartDate: form.contractStartDate,
|
contractStartDate: form.contractStartDate,
|
||||||
contractEndDate: form.contractEndDate || null,
|
contractEndDate: form.contractEndDate || null,
|
||||||
isDriverInput: form.isDriver,
|
isDriverInput: form.isDriver,
|
||||||
workDaysHoursInput: requiresSchedule.value ? form.workDaysHours : null
|
workDaysHoursInput: requiresSchedule.value ? form.workDaysHours : null,
|
||||||
|
interimAgencyId: form.contractNature === 'INTERIM' && form.interimAgencyId !== '' ? Number(form.interimAgencyId) : null
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -516,6 +539,7 @@ const handleSubmit = async () => {
|
|||||||
form.contractEndDate = ''
|
form.contractEndDate = ''
|
||||||
form.isDriver = false
|
form.isDriver = false
|
||||||
form.workDaysHours = null
|
form.workDaysHours = null
|
||||||
|
form.interimAgencyId = ''
|
||||||
editingEmployee.value = null
|
editingEmployee.value = null
|
||||||
isDrawerOpen.value = false
|
isDrawerOpen.value = false
|
||||||
await loadEmployees()
|
await loadEmployees()
|
||||||
@@ -542,6 +566,12 @@ watch(showsContractEndDateComputed, (shows) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
watch(() => form.contractNature, (nature) => {
|
||||||
|
if (nature !== 'INTERIM') {
|
||||||
|
form.interimAgencyId = ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
watch(requiresSchedule, (required) => {
|
watch(requiresSchedule, (required) => {
|
||||||
if (!required) {
|
if (!required) {
|
||||||
form.workDaysHours = null
|
form.workDaysHours = null
|
||||||
@@ -567,6 +597,7 @@ const openCreate = () => {
|
|||||||
form.contractEndDate = ''
|
form.contractEndDate = ''
|
||||||
form.isDriver = false
|
form.isDriver = false
|
||||||
form.workDaysHours = null
|
form.workDaysHours = null
|
||||||
|
form.interimAgencyId = ''
|
||||||
isDrawerOpen.value = true
|
isDrawerOpen.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ export type ContractHistoryItem = {
|
|||||||
suspensions?: ContractSuspension[]
|
suspensions?: ContractSuspension[]
|
||||||
isDriver?: boolean
|
isDriver?: boolean
|
||||||
workDaysHours?: Record<number, number> | null
|
workDaysHours?: Record<number, number> | null
|
||||||
|
interimAgencyId?: number | null
|
||||||
|
interimAgencyName?: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Employee = {
|
export type Employee = {
|
||||||
@@ -37,4 +39,6 @@ export type Employee = {
|
|||||||
displayOrder?: number
|
displayOrder?: number
|
||||||
entryDate?: string | null
|
entryDate?: string | null
|
||||||
currentSuspensions?: ContractSuspension[]
|
currentSuspensions?: ContractSuspension[]
|
||||||
|
currentInterimAgencyId?: number | null
|
||||||
|
currentInterimAgencyName?: string | null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ export const createEmployee = async (payload: {
|
|||||||
contractEndDate?: string | null
|
contractEndDate?: string | null
|
||||||
isDriverInput?: boolean
|
isDriverInput?: boolean
|
||||||
workDaysHoursInput?: Record<number, number> | null
|
workDaysHoursInput?: Record<number, number> | null
|
||||||
|
interimAgencyId?: number | null
|
||||||
}) => {
|
}) => {
|
||||||
const api = useApi()
|
const api = useApi()
|
||||||
return api.post<Employee>('/employees', {
|
return api.post<Employee>('/employees', {
|
||||||
@@ -47,7 +48,8 @@ export const createEmployee = async (payload: {
|
|||||||
contractStartDate: payload.contractStartDate,
|
contractStartDate: payload.contractStartDate,
|
||||||
contractEndDate: payload.contractEndDate ?? null,
|
contractEndDate: payload.contractEndDate ?? null,
|
||||||
isDriverInput: payload.isDriverInput ?? false,
|
isDriverInput: payload.isDriverInput ?? false,
|
||||||
workDaysHoursInput: payload.workDaysHoursInput ?? null
|
workDaysHoursInput: payload.workDaysHoursInput ?? null,
|
||||||
|
interimAgencyId: payload.interimAgencyId ?? null
|
||||||
}, {
|
}, {
|
||||||
toastSuccessKey: 'success.employee.create',
|
toastSuccessKey: 'success.employee.create',
|
||||||
toastErrorKey: 'errors.employee.create'
|
toastErrorKey: 'errors.employee.create'
|
||||||
@@ -69,6 +71,7 @@ export const updateEmployee = async (
|
|||||||
displayOrder?: number
|
displayOrder?: number
|
||||||
isDriverInput?: boolean
|
isDriverInput?: boolean
|
||||||
workDaysHoursInput?: Record<number, number> | null
|
workDaysHoursInput?: Record<number, number> | null
|
||||||
|
interimAgencyId?: number | null
|
||||||
}
|
}
|
||||||
) => {
|
) => {
|
||||||
const api = useApi()
|
const api = useApi()
|
||||||
@@ -103,6 +106,9 @@ export const updateEmployee = async (
|
|||||||
if (payload.workDaysHoursInput !== undefined) {
|
if (payload.workDaysHoursInput !== undefined) {
|
||||||
body.workDaysHoursInput = payload.workDaysHoursInput
|
body.workDaysHoursInput = payload.workDaysHoursInput
|
||||||
}
|
}
|
||||||
|
if (payload.interimAgencyId !== undefined) {
|
||||||
|
body.interimAgencyId = payload.interimAgencyId
|
||||||
|
}
|
||||||
|
|
||||||
return api.patch<Employee>(`/employees/${id}`, body, {
|
return api.patch<Employee>(`/employees/${id}`, body, {
|
||||||
toastSuccessKey: 'success.employee.update',
|
toastSuccessKey: 'success.employee.update',
|
||||||
|
|||||||
16
frontend/services/interim-agencies.ts
Normal file
16
frontend/services/interim-agencies.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { extractItems } from '~/utils/api'
|
||||||
|
|
||||||
|
export type InterimAgency = {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const listInterimAgencies = async (): Promise<InterimAgency[]> => {
|
||||||
|
const api = useApi()
|
||||||
|
const data = await api.get<InterimAgency[] | { 'hydra:member'?: InterimAgency[] }>(
|
||||||
|
'/interim_agencies',
|
||||||
|
{},
|
||||||
|
{ toast: false }
|
||||||
|
)
|
||||||
|
return extractItems<InterimAgency>(data)
|
||||||
|
}
|
||||||
@@ -13,7 +13,7 @@ export const showsContractEndDate = (nature: ContractNature) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const requiresContractEndDate = (nature: ContractNature) => {
|
export const requiresContractEndDate = (nature: ContractNature) => {
|
||||||
return nature === 'CDD'
|
return nature === 'CDD' || nature === 'INTERIM'
|
||||||
}
|
}
|
||||||
|
|
||||||
export const isContractNature = (value: string): value is ContractNature => {
|
export const isContractNature = (value: string): value is ContractNature => {
|
||||||
|
|||||||
32
migrations/Version20260417120000.php
Normal file
32
migrations/Version20260417120000.php
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace DoctrineMigrations;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
final class Version20260417120000 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return 'Create interim_agencies table and add interim_agency_id to employee_contract_periods';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('CREATE TABLE interim_agencies (id SERIAL PRIMARY KEY, name VARCHAR(150) NOT NULL UNIQUE)');
|
||||||
|
$this->addSql('ALTER TABLE employee_contract_periods ADD interim_agency_id INT DEFAULT NULL');
|
||||||
|
$this->addSql('ALTER TABLE employee_contract_periods ADD CONSTRAINT fk_ecp_interim_agency FOREIGN KEY (interim_agency_id) REFERENCES interim_agencies (id) ON DELETE SET NULL');
|
||||||
|
$this->addSql('CREATE INDEX idx_ecp_interim_agency ON employee_contract_periods (interim_agency_id)');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('ALTER TABLE employee_contract_periods DROP CONSTRAINT IF EXISTS fk_ecp_interim_agency');
|
||||||
|
$this->addSql('DROP INDEX IF EXISTS idx_ecp_interim_agency');
|
||||||
|
$this->addSql('ALTER TABLE employee_contract_periods DROP COLUMN interim_agency_id');
|
||||||
|
$this->addSql('DROP TABLE interim_agencies');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -34,5 +34,9 @@ final class ContractHistoryItem
|
|||||||
*/
|
*/
|
||||||
#[Groups(['employee:read'])]
|
#[Groups(['employee:read'])]
|
||||||
public ?array $workDaysHours = null,
|
public ?array $workDaysHours = null,
|
||||||
|
#[Groups(['employee:read'])]
|
||||||
|
public ?int $interimAgencyId = null,
|
||||||
|
#[Groups(['employee:read'])]
|
||||||
|
public ?string $interimAgencyName = null,
|
||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,6 +98,9 @@ class Employee
|
|||||||
#[Groups(['employee:write'])]
|
#[Groups(['employee:write'])]
|
||||||
private ?array $workDaysHoursInput = null;
|
private ?array $workDaysHoursInput = null;
|
||||||
|
|
||||||
|
#[Groups(['employee:write'])]
|
||||||
|
private ?int $interimAgencyId = null;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->createdAt = new DateTimeImmutable();
|
$this->createdAt = new DateTimeImmutable();
|
||||||
@@ -295,6 +298,30 @@ class Employee
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getInterimAgencyId(): ?int
|
||||||
|
{
|
||||||
|
return $this->interimAgencyId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setInterimAgencyId(?int $interimAgencyId): self
|
||||||
|
{
|
||||||
|
$this->interimAgencyId = $interimAgencyId;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Groups(['employee:read'])]
|
||||||
|
public function getCurrentInterimAgencyId(): ?int
|
||||||
|
{
|
||||||
|
return $this->resolveCurrentContractPeriod()?->getInterimAgency()?->getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Groups(['employee:read'])]
|
||||||
|
public function getCurrentInterimAgencyName(): ?string
|
||||||
|
{
|
||||||
|
return $this->resolveCurrentContractPeriod()?->getInterimAgency()?->getName();
|
||||||
|
}
|
||||||
|
|
||||||
#[Groups(['employee:read'])]
|
#[Groups(['employee:read'])]
|
||||||
public function getHasActiveContract(): bool
|
public function getHasActiveContract(): bool
|
||||||
{
|
{
|
||||||
@@ -393,6 +420,8 @@ class Employee
|
|||||||
suspensions: $suspensionData,
|
suspensions: $suspensionData,
|
||||||
isDriver: $period->getIsDriver(),
|
isDriver: $period->getIsDriver(),
|
||||||
workDaysHours: $period->getWorkDaysHours(),
|
workDaysHours: $period->getWorkDaysHours(),
|
||||||
|
interimAgencyId: $period->getInterimAgency()?->getId(),
|
||||||
|
interimAgencyName: $period->getInterimAgency()?->getName(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
$periods
|
$periods
|
||||||
|
|||||||
@@ -55,6 +55,10 @@ class EmployeeContractPeriod
|
|||||||
#[ORM\Column(type: 'json', nullable: true)]
|
#[ORM\Column(type: 'json', nullable: true)]
|
||||||
private ?array $workDaysHours = null;
|
private ?array $workDaysHours = null;
|
||||||
|
|
||||||
|
#[ORM\ManyToOne(targetEntity: InterimAgency::class)]
|
||||||
|
#[ORM\JoinColumn(nullable: true)]
|
||||||
|
private ?InterimAgency $interimAgency = null;
|
||||||
|
|
||||||
#[ORM\Column(type: 'text', nullable: true)]
|
#[ORM\Column(type: 'text', nullable: true)]
|
||||||
private ?string $comment = null;
|
private ?string $comment = null;
|
||||||
|
|
||||||
@@ -204,6 +208,18 @@ class EmployeeContractPeriod
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getInterimAgency(): ?InterimAgency
|
||||||
|
{
|
||||||
|
return $this->interimAgency;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setInterimAgency(?InterimAgency $interimAgency): self
|
||||||
|
{
|
||||||
|
$this->interimAgency = $interimAgency;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Collection<int, ContractSuspension>
|
* @return Collection<int, ContractSuspension>
|
||||||
*/
|
*/
|
||||||
|
|||||||
51
src/Entity/InterimAgency.php
Normal file
51
src/Entity/InterimAgency.php
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use ApiPlatform\Metadata\ApiResource;
|
||||||
|
use ApiPlatform\Metadata\GetCollection;
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
use Symfony\Component\Serializer\Attribute\Groups;
|
||||||
|
|
||||||
|
#[ApiResource(
|
||||||
|
operations: [
|
||||||
|
new GetCollection(),
|
||||||
|
],
|
||||||
|
normalizationContext: ['groups' => ['interim_agency:read']],
|
||||||
|
paginationEnabled: false,
|
||||||
|
security: "is_granted('ROLE_USER')",
|
||||||
|
order: ['name' => 'ASC'],
|
||||||
|
)]
|
||||||
|
#[ORM\Entity]
|
||||||
|
#[ORM\Table(name: 'interim_agencies')]
|
||||||
|
class InterimAgency
|
||||||
|
{
|
||||||
|
#[ORM\Id]
|
||||||
|
#[ORM\GeneratedValue]
|
||||||
|
#[ORM\Column(type: 'integer')]
|
||||||
|
#[Groups(['interim_agency:read', 'employee:read'])]
|
||||||
|
private ?int $id = null;
|
||||||
|
|
||||||
|
#[ORM\Column(type: 'string', length: 150, unique: true)]
|
||||||
|
#[Groups(['interim_agency:read', 'employee:read'])]
|
||||||
|
private string $name = '';
|
||||||
|
|
||||||
|
public function getId(): ?int
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getName(): string
|
||||||
|
{
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setName(string $name): self
|
||||||
|
{
|
||||||
|
$this->name = $name;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,6 +20,7 @@ final readonly class EmployeeContractChangeRequest
|
|||||||
public ?string $contractComment,
|
public ?string $contractComment,
|
||||||
public ?bool $isDriver = null,
|
public ?bool $isDriver = null,
|
||||||
public ?array $workDaysHours = null,
|
public ?array $workDaysHours = null,
|
||||||
|
public ?int $interimAgencyId = null,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public function hasPeriodChangeRequest(): bool
|
public function hasPeriodChangeRequest(): bool
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ final class EmployeeContractChangeRequestFactory
|
|||||||
contractComment: $employee->getContractComment(),
|
contractComment: $employee->getContractComment(),
|
||||||
isDriver: $employee->getIsDriverInput(),
|
isDriver: $employee->getIsDriverInput(),
|
||||||
workDaysHours: $employee->getWorkDaysHoursInput(),
|
workDaysHours: $employee->getWorkDaysHoursInput(),
|
||||||
|
interimAgencyId: $employee->getInterimAgencyId(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ namespace App\Service\Contracts;
|
|||||||
use App\Entity\Contract;
|
use App\Entity\Contract;
|
||||||
use App\Entity\Employee;
|
use App\Entity\Employee;
|
||||||
use App\Entity\EmployeeContractPeriod;
|
use App\Entity\EmployeeContractPeriod;
|
||||||
|
use App\Entity\InterimAgency;
|
||||||
use App\Enum\ContractNature;
|
use App\Enum\ContractNature;
|
||||||
use DateTimeImmutable;
|
use DateTimeImmutable;
|
||||||
|
|
||||||
@@ -23,6 +24,7 @@ final class EmployeeContractPeriodBuilder
|
|||||||
ContractNature $nature,
|
ContractNature $nature,
|
||||||
bool $isDriver = false,
|
bool $isDriver = false,
|
||||||
?array $workDaysHours = null,
|
?array $workDaysHours = null,
|
||||||
|
?InterimAgency $interimAgency = null,
|
||||||
): EmployeeContractPeriod {
|
): EmployeeContractPeriod {
|
||||||
return new EmployeeContractPeriod()
|
return new EmployeeContractPeriod()
|
||||||
->setEmployee($employee)
|
->setEmployee($employee)
|
||||||
@@ -32,6 +34,7 @@ final class EmployeeContractPeriodBuilder
|
|||||||
->setContractNature($nature)
|
->setContractNature($nature)
|
||||||
->setIsDriver($isDriver)
|
->setIsDriver($isDriver)
|
||||||
->setWorkDaysHours($workDaysHours)
|
->setWorkDaysHours($workDaysHours)
|
||||||
|
->setInterimAgency($interimAgency)
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ namespace App\Service\Contracts;
|
|||||||
use App\Entity\Contract;
|
use App\Entity\Contract;
|
||||||
use App\Entity\Employee;
|
use App\Entity\Employee;
|
||||||
use App\Entity\EmployeeContractPeriod;
|
use App\Entity\EmployeeContractPeriod;
|
||||||
|
use App\Entity\InterimAgency;
|
||||||
use App\Enum\ContractNature;
|
use App\Enum\ContractNature;
|
||||||
use App\Repository\EmployeeContractPeriodRepository;
|
use App\Repository\EmployeeContractPeriodRepository;
|
||||||
use DateTimeImmutable;
|
use DateTimeImmutable;
|
||||||
@@ -30,6 +31,7 @@ final readonly class EmployeeContractPeriodManager implements EmployeeContractPe
|
|||||||
ContractNature $nature,
|
ContractNature $nature,
|
||||||
bool $isDriver = false,
|
bool $isDriver = false,
|
||||||
?array $workDaysHours = null,
|
?array $workDaysHours = null,
|
||||||
|
?int $interimAgencyId = null,
|
||||||
): void {
|
): void {
|
||||||
$this->periodValidator->assertPeriodDates($startDate, $endDate, $nature);
|
$this->periodValidator->assertPeriodDates($startDate, $endDate, $nature);
|
||||||
$this->periodValidator->assertWorkDaysHours($contract, $nature, $workDaysHours);
|
$this->periodValidator->assertWorkDaysHours($contract, $nature, $workDaysHours);
|
||||||
@@ -39,7 +41,8 @@ final readonly class EmployeeContractPeriodManager implements EmployeeContractPe
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->persistNewPeriod($employee, $contract, $startDate, $endDate, $nature, $isDriver, $workDaysHours);
|
$interimAgency = $this->resolveInterimAgency($interimAgencyId);
|
||||||
|
$this->persistNewPeriod($employee, $contract, $startDate, $endDate, $nature, $isDriver, $workDaysHours, $interimAgency);
|
||||||
$this->entityManager->flush();
|
$this->entityManager->flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,6 +81,7 @@ final readonly class EmployeeContractPeriodManager implements EmployeeContractPe
|
|||||||
?EmployeeContractPeriod $todayPeriod,
|
?EmployeeContractPeriod $todayPeriod,
|
||||||
bool $isDriver = false,
|
bool $isDriver = false,
|
||||||
?array $workDaysHours = null,
|
?array $workDaysHours = null,
|
||||||
|
?int $interimAgencyId = null,
|
||||||
): void {
|
): void {
|
||||||
$this->periodValidator->assertPeriodDates($startDate, $endDate, $nature);
|
$this->periodValidator->assertPeriodDates($startDate, $endDate, $nature);
|
||||||
$this->periodValidator->assertWorkDaysHours($contract, $nature, $workDaysHours);
|
$this->periodValidator->assertWorkDaysHours($contract, $nature, $workDaysHours);
|
||||||
@@ -90,7 +94,8 @@ final readonly class EmployeeContractPeriodManager implements EmployeeContractPe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->persistNewPeriod($employee, $contract, $startDate, $endDate, $nature, $isDriver, $workDaysHours);
|
$interimAgency = $this->resolveInterimAgency($interimAgencyId);
|
||||||
|
$this->persistNewPeriod($employee, $contract, $startDate, $endDate, $nature, $isDriver, $workDaysHours, $interimAgency);
|
||||||
$this->entityManager->flush();
|
$this->entityManager->flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,8 +110,23 @@ final readonly class EmployeeContractPeriodManager implements EmployeeContractPe
|
|||||||
ContractNature $nature,
|
ContractNature $nature,
|
||||||
bool $isDriver = false,
|
bool $isDriver = false,
|
||||||
?array $workDaysHours = null,
|
?array $workDaysHours = null,
|
||||||
|
?InterimAgency $interimAgency = null,
|
||||||
): void {
|
): void {
|
||||||
$period = $this->periodBuilder->build($employee, $contract, $startDate, $endDate, $nature, $isDriver, $workDaysHours);
|
$period = $this->periodBuilder->build($employee, $contract, $startDate, $endDate, $nature, $isDriver, $workDaysHours, $interimAgency);
|
||||||
$this->entityManager->persist($period);
|
$this->entityManager->persist($period);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function resolveInterimAgency(?int $id): ?InterimAgency
|
||||||
|
{
|
||||||
|
if (null === $id) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$agency = $this->entityManager->find(InterimAgency::class, $id);
|
||||||
|
if (null === $agency) {
|
||||||
|
throw new UnprocessableEntityHttpException(sprintf('Interim agency with id %d not found.', $id));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $agency;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ interface EmployeeContractPeriodManagerInterface
|
|||||||
ContractNature $nature,
|
ContractNature $nature,
|
||||||
bool $isDriver = false,
|
bool $isDriver = false,
|
||||||
?array $workDaysHours = null,
|
?array $workDaysHours = null,
|
||||||
|
?int $interimAgencyId = null,
|
||||||
): void;
|
): void;
|
||||||
|
|
||||||
public function closeCurrentPeriod(
|
public function closeCurrentPeriod(
|
||||||
@@ -45,5 +46,6 @@ interface EmployeeContractPeriodManagerInterface
|
|||||||
?EmployeeContractPeriod $todayPeriod,
|
?EmployeeContractPeriod $todayPeriod,
|
||||||
bool $isDriver = false,
|
bool $isDriver = false,
|
||||||
?array $workDaysHours = null,
|
?array $workDaysHours = null,
|
||||||
|
?int $interimAgencyId = null,
|
||||||
): void;
|
): void;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ final readonly class EmployeeWriteProcessor implements ProcessorInterface
|
|||||||
nature: $nature,
|
nature: $nature,
|
||||||
isDriver: $changeRequest->isDriver ?? false,
|
isDriver: $changeRequest->isDriver ?? false,
|
||||||
workDaysHours: $changeRequest->workDaysHours,
|
workDaysHours: $changeRequest->workDaysHours,
|
||||||
|
interimAgencyId: $changeRequest->interimAgencyId,
|
||||||
);
|
);
|
||||||
|
|
||||||
$data->setEntryDate($startDate);
|
$data->setEntryDate($startDate);
|
||||||
@@ -140,6 +141,7 @@ final readonly class EmployeeWriteProcessor implements ProcessorInterface
|
|||||||
todayPeriod: $effectivePeriod,
|
todayPeriod: $effectivePeriod,
|
||||||
isDriver: $changeRequest->isDriver ?? false,
|
isDriver: $changeRequest->isDriver ?? false,
|
||||||
workDaysHours: $changeRequest->workDaysHours,
|
workDaysHours: $changeRequest->workDaysHours,
|
||||||
|
interimAgencyId: $changeRequest->interimAgencyId,
|
||||||
);
|
);
|
||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
|
|||||||
Reference in New Issue
Block a user