feat(leave) : show presence days in header for non-forfait, bound to contract start
Non-forfait header now shows '{weeklyHours} heures ({presence} présence)'.
Presence (presenceDaysByMonth/presenceDaysToToday) is bounded to the employee's
contract start so business days before hire are not counted (Dylan CDD: 43.5,
was 246). No change for employees present before the exercise or for forfait
(already capped at phase start). Leave summary now eager-loaded for any employee
with a leave tab to feed the header.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -66,8 +66,9 @@ export const useEmployeeDetailPage = () => {
|
|||||||
await bonus.loadBonusData()
|
await bonus.loadBonusData()
|
||||||
} else if (activeTab.value === 'observation') {
|
} else if (activeTab.value === 'observation') {
|
||||||
await observation.loadObservationData()
|
await observation.loadObservationData()
|
||||||
} else if (isForfait.value && showLeaveTab.value) {
|
} else if (showLeaveTab.value) {
|
||||||
// Eager load: needed for the "X jours restants" header label on forfait employees.
|
// Eager load: the header shows présence (et jours à travailler/restant pour le forfait),
|
||||||
|
// qui proviennent du récap congés — nécessaire même quand on ouvre un autre onglet.
|
||||||
await leave.loadLeaveData()
|
await leave.loadLeaveData()
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
@@ -77,14 +78,21 @@ export const useEmployeeDetailPage = () => {
|
|||||||
|
|
||||||
const contract = useEmployeeContract(employee, loadEmployee)
|
const contract = useEmployeeContract(employee, loadEmployee)
|
||||||
const leave = useEmployeeLeave(employee, loadEmployee, phase.selectedPhase)
|
const leave = useEmployeeLeave(employee, loadEmployee, phase.selectedPhase)
|
||||||
|
const formatDays = (n: number) => (Number.isInteger(n) ? String(n) : (Math.round(n * 100) / 100).toFixed(2).replace('.', ','))
|
||||||
|
// Forfait : « (présence · restant à travailler) ». restant = jours à travailler (prorata) − présence.
|
||||||
const forfaitRemainingDaysLabel = computed(() => {
|
const forfaitRemainingDaysLabel = computed(() => {
|
||||||
if (!isForfait.value) return ''
|
if (!isForfait.value) return ''
|
||||||
const presence = leave.leaveSummary.value?.presenceDaysToToday
|
const presence = leave.leaveSummary.value?.presenceDaysToToday
|
||||||
if (presence === undefined || presence === null) return ''
|
if (presence === undefined || presence === null) return ''
|
||||||
const fmt = (n: number) => (Number.isInteger(n) ? String(n) : (Math.round(n * 100) / 100).toFixed(2).replace('.', ','))
|
|
||||||
// restant à travailler = jours à travailler (prorata) − jours de présence déjà effectués
|
|
||||||
const remaining = forfaitWorkTargetDays.value - presence
|
const remaining = forfaitWorkTargetDays.value - presence
|
||||||
return ` (${fmt(presence)} présence · ${fmt(remaining)} restants)`
|
return ` (${formatDays(presence)} présence · ${formatDays(remaining)} restants)`
|
||||||
|
})
|
||||||
|
// Non-forfait : « (présence) » seul (pas de cible de jours à travailler).
|
||||||
|
const nonForfaitPresenceLabel = computed(() => {
|
||||||
|
if (isForfait.value) return ''
|
||||||
|
const presence = leave.leaveSummary.value?.presenceDaysToToday
|
||||||
|
if (presence === undefined || presence === null) return ''
|
||||||
|
return ` (${formatDays(presence)} présence)`
|
||||||
})
|
})
|
||||||
const rtt = useEmployeeRtt(employee, loadEmployee, phase.selectedPhase)
|
const rtt = useEmployeeRtt(employee, loadEmployee, phase.selectedPhase)
|
||||||
const mileage = useEmployeeMileage(employee, loadEmployee)
|
const mileage = useEmployeeMileage(employee, loadEmployee)
|
||||||
@@ -135,6 +143,7 @@ export const useEmployeeDetailPage = () => {
|
|||||||
showRttTab,
|
showRttTab,
|
||||||
employeeContractWorkLabel,
|
employeeContractWorkLabel,
|
||||||
forfaitRemainingDaysLabel,
|
forfaitRemainingDaysLabel,
|
||||||
|
nonForfaitPresenceLabel,
|
||||||
...phase,
|
...phase,
|
||||||
...contract,
|
...contract,
|
||||||
...leave,
|
...leave,
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
<p>Date d'entrée : {{ employee.entryDate ? employee.entryDate.split('-').reverse().join('/') : '-' }}</p>
|
<p>Date d'entrée : {{ employee.entryDate ? employee.entryDate.split('-').reverse().join('/') : '-' }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-right">
|
<div class="text-right">
|
||||||
<p class="font-bold text-[22px]">{{ contractNatureLabel(employee.currentContractNature) }} {{ employeeContractWorkLabel }}{{ forfaitRemainingDaysLabel }}</p>
|
<p class="font-bold text-[22px]">{{ contractNatureLabel(employee.currentContractNature) }} {{ employeeContractWorkLabel }}{{ forfaitRemainingDaysLabel }}{{ nonForfaitPresenceLabel }}</p>
|
||||||
<p class="text-[18px]">{{ employee.site?.name ?? '-' }}</p>
|
<p class="text-[18px]">{{ employee.site?.name ?? '-' }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -299,6 +299,7 @@ const {
|
|||||||
contractHistory,
|
contractHistory,
|
||||||
employeeContractWorkLabel,
|
employeeContractWorkLabel,
|
||||||
forfaitRemainingDaysLabel,
|
forfaitRemainingDaysLabel,
|
||||||
|
nonForfaitPresenceLabel,
|
||||||
contractForm,
|
contractForm,
|
||||||
createContractForm,
|
createContractForm,
|
||||||
isContractDrawerOpen,
|
isContractDrawerOpen,
|
||||||
|
|||||||
@@ -148,25 +148,31 @@ final readonly class EmployeeLeaveSummaryProvider implements ProviderInterface
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// La présence est bornée au début de contrat de l'employé : on ne compte pas comme
|
||||||
|
// « présents » les jours ouvrés antérieurs à l'embauche (cas d'une entrée en cours
|
||||||
|
// d'exercice, ex. CDD). Sans effet pour un employé présent depuis avant l'exercice,
|
||||||
|
// ni pour le forfait (déjà capé au début de phase).
|
||||||
|
$presenceFrom = $this->resolveEarliestContractStartWithinRange($employee, $periodFrom, $periodTo) ?? $periodFrom;
|
||||||
|
|
||||||
// Forfait-only: leaves taken from N-1 stock do NOT decrement presence days.
|
// Forfait-only: leaves taken from N-1 stock do NOT decrement presence days.
|
||||||
// For non-forfait, previousYearTakenDays is always 0, so the budget has no effect.
|
// For non-forfait, previousYearTakenDays is always 0, so the budget has no effect.
|
||||||
$n1AbsencesBudget = $yearSummary['previousYearTakenDays'];
|
$n1AbsencesBudget = $yearSummary['previousYearTakenDays'];
|
||||||
$summary->presenceDaysByMonth = $this->computePresenceDaysByMonth(
|
$summary->presenceDaysByMonth = $this->computePresenceDaysByMonth(
|
||||||
$employee,
|
$employee,
|
||||||
$periodFrom,
|
$presenceFrom,
|
||||||
$periodTo,
|
$periodTo,
|
||||||
$n1AbsencesBudget
|
$n1AbsencesBudget
|
||||||
);
|
);
|
||||||
|
|
||||||
// Same logic as presenceDaysByMonth but bounded at today: number of presence days
|
// Same logic as presenceDaysByMonth but bounded at today: number of presence days
|
||||||
// accumulated from leave year start up to today (inclusive).
|
// accumulated from contract start up to today (inclusive).
|
||||||
$today = new DateTimeImmutable('today');
|
$today = new DateTimeImmutable('today');
|
||||||
$cappedTo = $today < $periodTo ? $today : $periodTo;
|
$cappedTo = $today < $periodTo ? $today : $periodTo;
|
||||||
$summary->presenceDaysToToday = $today < $periodFrom
|
$summary->presenceDaysToToday = $today < $presenceFrom
|
||||||
? 0.0
|
? 0.0
|
||||||
: array_sum($this->computePresenceDaysByMonth(
|
: array_sum($this->computePresenceDaysByMonth(
|
||||||
$employee,
|
$employee,
|
||||||
$periodFrom,
|
$presenceFrom,
|
||||||
$cappedTo,
|
$cappedTo,
|
||||||
$n1AbsencesBudget
|
$n1AbsencesBudget
|
||||||
));
|
));
|
||||||
|
|||||||
Reference in New Issue
Block a user