feat(overtime-contingent) : contingent d'heures supplémentaires payées (#29)
Auto Tag Develop / tag (push) Successful in 7s
Auto Tag Develop / tag (push) Successful in 7s
## Résumé
Suivi par **année civile** (Janv–Déc) des heures supplémentaires payées des employés non-forfait (chauffeurs inclus) face au plafond légal (**350 h** chauffeurs / **220 h** autres).
- **Fiche employé** : encart header `Total H.payés {année} : X h / plafond h` (année civile courante, rouge si dépassement), via `GET /employees/{id}/overtime-contingent`.
- **Export PDF** `GET /overtime-contingent/print?year=&siteIds=` (ROLE_USER, périmètre `findScoped`) : groupé par site, colonnes Janv–Déc + colonne `Total payé / payable`. Drawer liste employés (année + sites).
- Heures payées = `base25 + base50` (hors majoration). Mapping exercice→civil : `mois ≥ 6 ? exercice−1 : exercice`.
- Cœur partagé pur `OvertimePaidContingentCalculator`.
- Ajout « Année civile » dans le titre des deux exports PDF (contingent H.supp. et heures de nuit).
## Tests
- 214 tests PHPUnit verts (calculateur : mapping civil, base-only, plafond ; builder : ventilation mensuelle, ligne à zéro).
## Hors périmètre (consigné)
- Bug latent `SalaryRecapPrintProvider` : rattachement des paiements RTT des mois Juin–Déc par année civile sur un stockage par exercice. À traiter séparément.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Reviewed-on: #29
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
This commit was merged in pull request #29.
This commit is contained in:
@@ -28,6 +28,11 @@
|
||||
<div class="text-right">
|
||||
<p class="font-bold text-[22px]">{{ contractNatureLabel(employee.currentContractNature) }} {{ employeeContractWorkLabel }}{{ forfaitRemainingDaysLabel }}{{ nonForfaitPresenceLabel }}</p>
|
||||
<p class="text-[18px]">{{ employee.site?.name ?? '-' }}</p>
|
||||
<p
|
||||
v-if="overtimeContingentLabel"
|
||||
class="text-[16px] font-semibold"
|
||||
:class="overtimeContingentExceeded ? 'text-red-600' : ''"
|
||||
>{{ overtimeContingentLabel }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="showPicker" class="mt-3 flex items-center gap-3">
|
||||
@@ -300,6 +305,8 @@ const {
|
||||
employeeContractWorkLabel,
|
||||
forfaitRemainingDaysLabel,
|
||||
nonForfaitPresenceLabel,
|
||||
overtimeContingentLabel,
|
||||
overtimeContingentExceeded,
|
||||
contractForm,
|
||||
createContractForm,
|
||||
isContractDrawerOpen,
|
||||
|
||||
@@ -240,6 +240,22 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-else-if="exportChoice === 'overtime-contingent'" class="flex flex-col gap-4">
|
||||
<MalioSelect
|
||||
:model-value="exportYear"
|
||||
:options="exportYearOptions"
|
||||
label="Année *"
|
||||
min-width=""
|
||||
@update:model-value="(v) => { if (v !== null) exportYear = Number(v) }"
|
||||
/>
|
||||
<MalioSelectCheckbox
|
||||
v-model="exportSiteIds"
|
||||
:options="siteOptions"
|
||||
label="Sites"
|
||||
min-width=""
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-center pt-2">
|
||||
<MalioButton
|
||||
label="Valider"
|
||||
@@ -274,16 +290,18 @@ const isDrawerOpen = ref(false)
|
||||
const isSubmitting = ref(false)
|
||||
const isLoading = ref(false)
|
||||
const isExportDrawerOpen = ref(false)
|
||||
const exportChoice = ref<'leave-recap' | 'salary-recap' | 'yearly-hours' | 'night-contingent' | ''>('')
|
||||
const exportChoice = ref<'leave-recap' | 'salary-recap' | 'yearly-hours' | 'night-contingent' | 'overtime-contingent' | ''>('')
|
||||
const exportYear = ref<number>(new Date().getFullYear())
|
||||
const exportMonth = ref<number | ''>(new Date().getMonth() + 1)
|
||||
const exportSalaryMonth = ref<string>(`${new Date().getFullYear()}-${String(new Date().getMonth() + 1).padStart(2, '0')}`)
|
||||
const exportSiteIds = ref<number[]>([])
|
||||
|
||||
const exportTypeOptions = [
|
||||
{ label: 'Récap. congés', value: 'leave-recap' },
|
||||
{ label: 'Récap. salaire', value: 'salary-recap' },
|
||||
{ label: 'Heures annuelles', value: 'yearly-hours' },
|
||||
{ label: 'Contingent H.nuit', value: 'night-contingent' }
|
||||
{ label: 'Contingent H.nuit', value: 'night-contingent' },
|
||||
{ label: 'Contingent H.supp.', value: 'overtime-contingent' }
|
||||
]
|
||||
const exportYearOptions = computed(() => {
|
||||
const current = new Date().getFullYear()
|
||||
@@ -315,11 +333,14 @@ const isExportValid = computed(() => {
|
||||
if (exportChoice.value === 'night-contingent') {
|
||||
return exportYear.value > 0
|
||||
}
|
||||
if (exportChoice.value === 'overtime-contingent') {
|
||||
return exportYear.value > 0
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
const onExportChoiceChange = (value: string | number | null) => {
|
||||
exportChoice.value = (value === null ? '' : String(value)) as 'leave-recap' | 'salary-recap' | 'yearly-hours' | 'night-contingent' | ''
|
||||
exportChoice.value = (value === null ? '' : String(value)) as 'leave-recap' | 'salary-recap' | 'yearly-hours' | 'night-contingent' | 'overtime-contingent' | ''
|
||||
}
|
||||
const { printPdf } = usePdfPrinter()
|
||||
const sitesInitialized = ref(false)
|
||||
@@ -619,6 +640,7 @@ const openExportDrawer = () => {
|
||||
exportYear.value = now.getFullYear()
|
||||
exportMonth.value = now.getMonth() + 1
|
||||
exportSalaryMonth.value = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`
|
||||
exportSiteIds.value = []
|
||||
isExportDrawerOpen.value = true
|
||||
}
|
||||
|
||||
@@ -634,6 +656,9 @@ const handleExportValidate = async () => {
|
||||
await printPdf(`/yearly-hours/print-all?year=${exportYear.value}&month=${exportMonth.value}`)
|
||||
} else if (choice === 'night-contingent') {
|
||||
await printPdf(`/night-hours-contingent/print?year=${exportYear.value}`)
|
||||
} else if (choice === 'overtime-contingent') {
|
||||
const siteParam = exportSiteIds.value.length > 0 ? `&siteIds=${exportSiteIds.value.join(',')}` : ''
|
||||
await printPdf(`/overtime-contingent/print?year=${exportYear.value}${siteParam}`)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user