feat : sélecteur d'exercice sur l'onglet RTT de la fiche employé
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
Permet de consulter les exercices passés (table hebdomadaire RTT) en réutilisant le pattern de l'onglet Congés. Plage bornée par max(début historique contrat, RTT_START_DATE). Bouton + Payer les RTT verrouillé sur exercices clos. Onglet masqué pour FORFAIT (inchangé). Backend : rttStartDate désormais toujours exposé sur EmployeeRttSummary pour que le sélecteur conserve sa borne lors de la navigation vers un exercice passé. Le masquage existant des lignes Report continue de fonctionner (comparaison mois-à-mois). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -76,6 +76,13 @@
|
|||||||
- Sur un exercice passé (`selectedYear !== currentYear`), les boutons crayon **Jours fractionnés** et **Année N-1 payés** sont **désactivés** : pas d'édition rétroactive des stocks de report.
|
- Sur un exercice passé (`selectedYear !== currentYear`), les boutons crayon **Jours fractionnés** et **Année N-1 payés** sont **désactivés** : pas d'édition rétroactive des stocks de report.
|
||||||
- Doc : `doc/leave-tab.md`.
|
- Doc : `doc/leave-tab.md`.
|
||||||
|
|
||||||
|
## Onglet RTT (fiche employé)
|
||||||
|
- Tableau hebdomadaire (`frontend/components/employees/RttTab.vue`) — exercice fixe Juin(N-1)→Mai(N). Onglet **masqué pour les FORFAIT** (`showRttTab`).
|
||||||
|
- **Sélecteur d'année** sous le tableau dans la zone scrollable. Même mécanique que l'onglet Congés (double plancher) : `max(floor_contrat, floor_rttStartDate)`. Format unique : `Juin 2025 → Mai 2026`.
|
||||||
|
- Changement d'année → recharge via `useEmployeeRtt.setSelectedRttYear(year)` (`getEmployeeRttSummary?year=YYYY`). `EmployeeRttSummary.rttStartDate` est déjà exposé (champ existant) — il sert à la fois au floor du sélecteur et au masquage des lignes Report avant la mise en service.
|
||||||
|
- Sur un exercice passé, le bouton **+ Payer les RTT** est désactivé (pas de paiement rétroactif).
|
||||||
|
- Doc : `doc/rtt-tab.md`.
|
||||||
|
|
||||||
## Récap. congés (écran)
|
## Récap. congés (écran)
|
||||||
- Accès via sidebar `Récap. congés`, conditionné au flag `User.hasLeaveRecapAccess` (défaut `false`) — activé au create/edit user. Le flag s'applique à tous les profils, y compris admin.
|
- Accès via sidebar `Récap. congés`, conditionné au flag `User.hasLeaveRecapAccess` (défaut `false`) — activé au create/edit user. Le flag s'applique à tous les profils, y compris admin.
|
||||||
- Scope : `ROLE_ADMIN` → tous les employés, `ROLE_USER` (chef de site) → employés de ses sites, `ROLE_SELF` → sa ligne
|
- Scope : `ROLE_ADMIN` → tous les employés, `ROLE_USER` (chef de site) → employés de ses sites, `ROLE_SELF` → sa ligne
|
||||||
|
|||||||
52
doc/rtt-tab.md
Normal file
52
doc/rtt-tab.md
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
# Onglet "RTT" — fiche employé
|
||||||
|
|
||||||
|
## Vue d'ensemble
|
||||||
|
|
||||||
|
L'onglet **RTT** de la fiche employé (`frontend/components/employees/RttTab.vue`) affiche un tableau hebdomadaire détaillé des heures supplémentaires accumulées et payées sur un exercice :
|
||||||
|
- bandeau de navigation par mois (chevrons gauche/droite) ;
|
||||||
|
- table semaine par semaine : Heure / Base 25% / 25% / Total 25% / Base 50% / 50% / Total 50% / Total / Cumul ;
|
||||||
|
- ligne Report (carry N-1 ou cumul mois précédents) ;
|
||||||
|
- ligne Total mois, ligne Payé, ligne Reste ;
|
||||||
|
- bouton « + Payer les RTT » dans le bandeau ;
|
||||||
|
- sélecteur d'exercice en pied de tableau.
|
||||||
|
|
||||||
|
L'onglet est **masqué pour les contrats FORFAIT** (filtre `showRttTab` dans `useEmployeeDetailPage`). Les FORFAIT n'accumulent pas de RTT.
|
||||||
|
|
||||||
|
## Période affichée
|
||||||
|
|
||||||
|
Toujours **Juin (Y-1) → Mai (Y)**. Le champ `EmployeeRttSummary.year` correspond à `Y` (année de fin d'exercice) ; ex. `year=2026` = `01/06/2025 → 31/05/2026`.
|
||||||
|
|
||||||
|
## Sélecteur d'année
|
||||||
|
|
||||||
|
Position : sous la table, à l'intérieur de la zone scrollable, à gauche.
|
||||||
|
|
||||||
|
Plage proposée :
|
||||||
|
- du plus récent (= exercice courant) au plus ancien ;
|
||||||
|
- **double plancher** : `max(floor_historique_contrat, floor_data_start_date)`
|
||||||
|
- **floor_historique_contrat** : dérivé de `employee.contractHistory[].startDate` — premier exercice où l'employé avait un contrat ouvert
|
||||||
|
- **floor_data_start_date** : exercice contenant `RTT_START_DATE` (env, ex. `2026-02-23` → exercice 2026)
|
||||||
|
- la valeur est exposée par l'API `GET /employees/{id}/rtt-summary` via le champ `rttStartDate` (déjà existant — mais peuplé uniquement quand la date tombe dans l'exercice retourné, donc le composable utilise la première réponse pour borner la plage).
|
||||||
|
- format unique : `Juin 2025 → Mai 2026`, `Juin 2024 → Mai 2025`…
|
||||||
|
|
||||||
|
Comportement :
|
||||||
|
- changer d'exercice recharge `getEmployeeRttSummary?year=YYYY` (le backend valide 2000–2100) ;
|
||||||
|
- la table redéploie les semaines de l'exercice sélectionné, navigation par mois conservée.
|
||||||
|
|
||||||
|
## Verrouillage des édition sur exercices passés
|
||||||
|
|
||||||
|
Quand `selectedYear !== currentYear` (consultation d'un exercice antérieur), le bouton **+ Payer les RTT** est désactivé. Justification : un paiement rétroactif sur un exercice clos décalerait les soldes courants et le report N-1 calculé.
|
||||||
|
|
||||||
|
La consultation reste possible, l'édition non.
|
||||||
|
|
||||||
|
## Implémentation
|
||||||
|
|
||||||
|
- Composable : `frontend/composables/useEmployeeRtt.ts`
|
||||||
|
- État : `selectedRttYear`, computed `currentRttYear`, `availableRttYears`
|
||||||
|
- API : `setSelectedRttYear(year)`, `loadRttData()`, `resetLoaded()`
|
||||||
|
- `resetLoaded()` (appelé au changement d'employé) remet `selectedRttYear = null`.
|
||||||
|
- Composant : `frontend/components/employees/RttTab.vue`
|
||||||
|
- Props : `selectedYear`, `availableYears`, `currentYear`
|
||||||
|
- Event : `update-selected-year`
|
||||||
|
- Renommage `currentYear` (computed local de l'année du mois affiché) → `displayedMonthYear` pour éviter la collision avec la nouvelle prop.
|
||||||
|
- Page : `frontend/pages/employees/[id].vue`
|
||||||
|
- Backend : aucun changement — `EmployeeRttSummaryProvider` accepte déjà `?year=YYYY` (validation 2000–2100) et expose `rttStartDate`.
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
<Icon name="mdi:chevron-left" size="24"/>
|
<Icon name="mdi:chevron-left" size="24"/>
|
||||||
</button>
|
</button>
|
||||||
<span class="text-lg font-bold tracking-wide min-w-[170px] text-center">
|
<span class="text-lg font-bold tracking-wide min-w-[170px] text-center">
|
||||||
{{ currentMonthLabel }} {{ currentYear }}
|
{{ currentMonthLabel }} {{ displayedMonthYear }}
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
class="rounded px-2 py-1 font-bold hover:bg-primary-600 disabled:opacity-40 disabled:cursor-not-allowed flex items-center"
|
class="rounded px-2 py-1 font-bold hover:bg-primary-600 disabled:opacity-40 disabled:cursor-not-allowed flex items-center"
|
||||||
@@ -27,7 +27,8 @@
|
|||||||
</p>
|
</p>
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
<button
|
<button
|
||||||
class="rounded-md bg-primary-500 px-8 py-2 font-bold text-white hover:bg-primary-600"
|
class="rounded-md bg-primary-500 px-8 py-2 font-bold text-white hover:bg-primary-600 disabled:opacity-40 disabled:cursor-not-allowed"
|
||||||
|
:disabled="isHistoricalYear"
|
||||||
@click="openPaymentDrawer"
|
@click="openPaymentDrawer"
|
||||||
>
|
>
|
||||||
+ Payer les RTT
|
+ Payer les RTT
|
||||||
@@ -183,6 +184,22 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
<div class="mt-6 flex items-center gap-3">
|
||||||
|
<label for="rtt-year-select" class="text-md font-semibold text-primary-500 uppercase">
|
||||||
|
Exercice :
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
id="rtt-year-select"
|
||||||
|
:value="selectedYear ?? ''"
|
||||||
|
:disabled="!availableYears.length"
|
||||||
|
class="border border-primary-500 rounded-md px-3 py-1 text-md font-semibold text-primary-500 bg-white focus:outline-none focus:ring-2 focus:ring-secondary-500/20 disabled:opacity-50"
|
||||||
|
@change="handleYearChange"
|
||||||
|
>
|
||||||
|
<option v-for="option in availableYears" :key="option.value" :value="option.value">
|
||||||
|
{{ option.label }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Payment Drawer -->
|
<!-- Payment Drawer -->
|
||||||
@@ -261,14 +278,36 @@
|
|||||||
import type { EmployeeRttSummary, EmployeeRttWeekSummary } from '~/services/dto/employee-rtt-summary'
|
import type { EmployeeRttSummary, EmployeeRttWeekSummary } from '~/services/dto/employee-rtt-summary'
|
||||||
import AppDrawer from '~/components/AppDrawer.vue'
|
import AppDrawer from '~/components/AppDrawer.vue'
|
||||||
|
|
||||||
|
type RttYearOption = {
|
||||||
|
value: number
|
||||||
|
label: string
|
||||||
|
}
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
summary: EmployeeRttSummary | null
|
summary: EmployeeRttSummary | null
|
||||||
|
selectedYear: number | null
|
||||||
|
availableYears: RttYearOption[]
|
||||||
|
currentYear: number | null
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(event: 'submit-rtt-payment', month: number, base25Minutes: number, bonus25Minutes: number, base50Minutes: number, bonus50Minutes: number): void
|
(event: 'submit-rtt-payment', month: number, base25Minutes: number, bonus25Minutes: number, base50Minutes: number, bonus50Minutes: number): void
|
||||||
|
(event: 'update-selected-year', year: number): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
const isHistoricalYear = computed(() =>
|
||||||
|
props.selectedYear !== null
|
||||||
|
&& props.currentYear !== null
|
||||||
|
&& props.selectedYear !== props.currentYear
|
||||||
|
)
|
||||||
|
|
||||||
|
const handleYearChange = (event: Event) => {
|
||||||
|
const target = event.target as HTMLSelectElement
|
||||||
|
const value = Number(target.value)
|
||||||
|
if (Number.isNaN(value)) return
|
||||||
|
emit('update-selected-year', value)
|
||||||
|
}
|
||||||
|
|
||||||
// --- Last complete week number ---
|
// --- Last complete week number ---
|
||||||
|
|
||||||
const lastCompleteWeek = computed(() => {
|
const lastCompleteWeek = computed(() => {
|
||||||
@@ -324,7 +363,7 @@ const currentMonth = computed(() => orderedMonths[currentMonthIndex.value])
|
|||||||
|
|
||||||
const currentMonthLabel = computed(() => monthLabels[currentMonth.value])
|
const currentMonthLabel = computed(() => monthLabels[currentMonth.value])
|
||||||
|
|
||||||
const currentYear = computed(() => {
|
const displayedMonthYear = computed(() => {
|
||||||
if (!props.summary) return ''
|
if (!props.summary) return ''
|
||||||
return currentMonth.value >= 6 ? props.summary.year - 1 : props.summary.year
|
return currentMonth.value >= 6 ? props.summary.year - 1 : props.summary.year
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -3,25 +3,94 @@ import type { EmployeeRttSummary } from '~/services/dto/employee-rtt-summary'
|
|||||||
import type { Employee } from '~/services/dto/employee'
|
import type { Employee } from '~/services/dto/employee'
|
||||||
import { getEmployeeRttSummary, createRttPayment } from '~/services/employee-rtt-summary'
|
import { getEmployeeRttSummary, createRttPayment } from '~/services/employee-rtt-summary'
|
||||||
|
|
||||||
|
export type RttYearOption = {
|
||||||
|
value: number
|
||||||
|
label: string
|
||||||
|
}
|
||||||
|
|
||||||
export const useEmployeeRtt = (employee: Ref<Employee | null>, reloadEmployee: () => Promise<void>) => {
|
export const useEmployeeRtt = (employee: Ref<Employee | null>, reloadEmployee: () => Promise<void>) => {
|
||||||
const rttSummary = ref<EmployeeRttSummary | null>(null)
|
const rttSummary = ref<EmployeeRttSummary | null>(null)
|
||||||
const isRttLoading = ref(false)
|
const isRttLoading = ref(false)
|
||||||
const rttDataLoaded = ref(false)
|
const rttDataLoaded = ref(false)
|
||||||
|
const selectedRttYear = ref<number | null>(null)
|
||||||
|
|
||||||
|
// Exercice RTT : Juin (Y-1) → Mai (Y). Toujours, peu importe le type de contrat
|
||||||
|
// (l'onglet RTT est masqué pour les FORFAIT côté page).
|
||||||
|
const computeRttYearForDate = (date: Date): number =>
|
||||||
|
date.getMonth() >= 5 ? date.getFullYear() + 1 : date.getFullYear()
|
||||||
|
|
||||||
|
const currentRttYear = computed<number | null>(() => {
|
||||||
|
if (!employee.value) return null
|
||||||
|
return computeRttYearForDate(new Date())
|
||||||
|
})
|
||||||
|
|
||||||
|
const availableRttYears = computed<RttYearOption[]>(() => {
|
||||||
|
if (!employee.value || currentRttYear.value === null) return []
|
||||||
|
const current = currentRttYear.value
|
||||||
|
|
||||||
|
const startDates: string[] = []
|
||||||
|
for (const period of employee.value.contractHistory ?? []) {
|
||||||
|
if (period.startDate) startDates.push(period.startDate)
|
||||||
|
}
|
||||||
|
if (employee.value.entryDate) startDates.push(employee.value.entryDate)
|
||||||
|
|
||||||
|
let contractFloor = current
|
||||||
|
for (const raw of startDates) {
|
||||||
|
const date = new Date(`${raw.substring(0, 10)}T00:00:00`)
|
||||||
|
if (Number.isNaN(date.getTime())) continue
|
||||||
|
const rttYear = computeRttYearForDate(date)
|
||||||
|
if (rttYear < contractFloor) contractFloor = rttYear
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hard floor : rttStartDate (env RTT_START_DATE) — pas d'historique avant.
|
||||||
|
let dataFloor: number | null = null
|
||||||
|
const dataStart = rttSummary.value?.rttStartDate
|
||||||
|
if (dataStart) {
|
||||||
|
const dataStartDate = new Date(`${dataStart.substring(0, 10)}T00:00:00`)
|
||||||
|
if (!Number.isNaN(dataStartDate.getTime())) {
|
||||||
|
dataFloor = computeRttYearForDate(dataStartDate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const minYear = dataFloor !== null ? Math.max(contractFloor, dataFloor) : contractFloor
|
||||||
|
|
||||||
|
const years: RttYearOption[] = []
|
||||||
|
for (let y = current; y >= minYear; y -= 1) {
|
||||||
|
years.push({ value: y, label: `Juin ${y - 1} → Mai ${y}` })
|
||||||
|
}
|
||||||
|
return years
|
||||||
|
})
|
||||||
|
|
||||||
|
const initSelectedRttYear = () => {
|
||||||
|
if (selectedRttYear.value !== null) return
|
||||||
|
if (currentRttYear.value !== null) {
|
||||||
|
selectedRttYear.value = currentRttYear.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const loadRttData = async () => {
|
const loadRttData = async () => {
|
||||||
if (!employee.value || isRttLoading.value) return
|
if (!employee.value || isRttLoading.value) return
|
||||||
|
initSelectedRttYear()
|
||||||
|
if (selectedRttYear.value === null) return
|
||||||
isRttLoading.value = true
|
isRttLoading.value = true
|
||||||
try {
|
try {
|
||||||
const rttYear = new Date().getMonth() >= 5 ? new Date().getFullYear() + 1 : new Date().getFullYear()
|
rttSummary.value = await getEmployeeRttSummary(employee.value.id, selectedRttYear.value)
|
||||||
rttSummary.value = await getEmployeeRttSummary(employee.value.id, rttYear)
|
|
||||||
rttDataLoaded.value = true
|
rttDataLoaded.value = true
|
||||||
} finally {
|
} finally {
|
||||||
isRttLoading.value = false
|
isRttLoading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const setSelectedRttYear = async (year: number) => {
|
||||||
|
if (selectedRttYear.value === year) return
|
||||||
|
selectedRttYear.value = year
|
||||||
|
rttDataLoaded.value = false
|
||||||
|
await loadRttData()
|
||||||
|
}
|
||||||
|
|
||||||
const resetLoaded = () => {
|
const resetLoaded = () => {
|
||||||
rttDataLoaded.value = false
|
rttDataLoaded.value = false
|
||||||
|
selectedRttYear.value = null
|
||||||
}
|
}
|
||||||
|
|
||||||
const submitRttPayment = async (month: number, base25Minutes: number, bonus25Minutes: number, base50Minutes: number, bonus50Minutes: number) => {
|
const submitRttPayment = async (month: number, base25Minutes: number, bonus25Minutes: number, base50Minutes: number, bonus50Minutes: number) => {
|
||||||
@@ -35,6 +104,10 @@ export const useEmployeeRtt = (employee: Ref<Employee | null>, reloadEmployee: (
|
|||||||
rttSummary,
|
rttSummary,
|
||||||
isRttLoading,
|
isRttLoading,
|
||||||
rttDataLoaded,
|
rttDataLoaded,
|
||||||
|
selectedRttYear,
|
||||||
|
currentRttYear,
|
||||||
|
availableRttYears,
|
||||||
|
setSelectedRttYear,
|
||||||
loadRttData,
|
loadRttData,
|
||||||
resetLoaded,
|
resetLoaded,
|
||||||
submitRttPayment
|
submitRttPayment
|
||||||
|
|||||||
@@ -515,6 +515,15 @@ export const documentationSections: DocSection[] = [
|
|||||||
{ type: 'list', content: 'Saisie : mois, nombre de minutes, taux (25% ou 50%)\nPlusieurs paiements possibles par mois\nLes heures payées sont soustraites du solde disponible' },
|
{ type: 'list', content: 'Saisie : mois, nombre de minutes, taux (25% ou 50%)\nPlusieurs paiements possibles par mois\nLes heures payées sont soustraites du solde disponible' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'rtt-selecteur-exercice',
|
||||||
|
title: 'Consulter un exercice passé',
|
||||||
|
requiredLevel: 'admin',
|
||||||
|
blocks: [
|
||||||
|
{ type: 'paragraph', content: 'Un sélecteur d\'exercice est disponible en bas du tableau RTT (zone scrollable, à gauche). Il permet de consulter les exercices passés (Juin → Mai). La plage proposée part de l\'exercice courant et remonte jusqu\'au plus récent entre (a) le premier exercice où l\'employé avait un contrat ouvert et (b) l\'exercice de mise en service du logiciel.' },
|
||||||
|
{ type: 'note', content: 'Sur un exercice passé, le bouton « + Payer les RTT » est désactivé. Aucun paiement rétroactif n\'est autorisé pour préserver la cohérence du report N-1.' },
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'rtt-semaines-mois',
|
id: 'rtt-semaines-mois',
|
||||||
title: 'Attribution des semaines aux mois',
|
title: 'Attribution des semaines aux mois',
|
||||||
|
|||||||
@@ -172,7 +172,16 @@
|
|||||||
<div v-if="isRttLoading" class="mt-6 rounded-lg border border-neutral-200 bg-white p-6 text-md text-neutral-600">
|
<div v-if="isRttLoading" class="mt-6 rounded-lg border border-neutral-200 bg-white p-6 text-md text-neutral-600">
|
||||||
Chargement...
|
Chargement...
|
||||||
</div>
|
</div>
|
||||||
<EmployeesRttTab v-else class="h-full" :summary="rttSummary" @submit-rtt-payment="submitRttPayment" />
|
<EmployeesRttTab
|
||||||
|
v-else
|
||||||
|
class="h-full"
|
||||||
|
:summary="rttSummary"
|
||||||
|
:selected-year="selectedRttYear"
|
||||||
|
:available-years="availableRttYears"
|
||||||
|
:current-year="currentRttYear"
|
||||||
|
@submit-rtt-payment="submitRttPayment"
|
||||||
|
@update-selected-year="setSelectedRttYear"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="activeTab === 'mileage'" class="h-full">
|
<div v-else-if="activeTab === 'mileage'" class="h-full">
|
||||||
<div v-if="isMileageLoading" class="mt-6 rounded-lg border border-neutral-200 bg-white p-6 text-md text-neutral-600">
|
<div v-if="isMileageLoading" class="mt-6 rounded-lg border border-neutral-200 bg-white p-6 text-md text-neutral-600">
|
||||||
@@ -261,6 +270,10 @@ const {
|
|||||||
currentLeaveYear,
|
currentLeaveYear,
|
||||||
availableLeaveYears,
|
availableLeaveYears,
|
||||||
setSelectedLeaveYear,
|
setSelectedLeaveYear,
|
||||||
|
selectedRttYear,
|
||||||
|
currentRttYear,
|
||||||
|
availableRttYears,
|
||||||
|
setSelectedRttYear,
|
||||||
showLeaveTab,
|
showLeaveTab,
|
||||||
showRttTab,
|
showRttTab,
|
||||||
contractHistory,
|
contractHistory,
|
||||||
|
|||||||
@@ -110,13 +110,10 @@ final readonly class EmployeeRttSummaryProvider implements ProviderInterface
|
|||||||
$summary->currentYearRecoveryMinutes = array_sum(array_map(static fn ($d) => $d->totalMinutes, $currentByWeekStart));
|
$summary->currentYearRecoveryMinutes = array_sum(array_map(static fn ($d) => $d->totalMinutes, $currentByWeekStart));
|
||||||
$summary->availableMinutes = $summary->carryFromPreviousYearMinutes + $summary->currentYearRecoveryMinutes;
|
$summary->availableMinutes = $summary->carryFromPreviousYearMinutes + $summary->currentYearRecoveryMinutes;
|
||||||
|
|
||||||
// Pass rttStartDate only if it falls within this exercise
|
// Always expose rttStartDate so the frontend can use it as a hard floor
|
||||||
if (null !== $this->rttStartDate) {
|
// for the year selector. Frontend already uses month-level comparison
|
||||||
$startDate = new DateTimeImmutable($this->rttStartDate);
|
// to hide carry/report rows when the date is outside the exercise.
|
||||||
if ($startDate >= $periodFrom && $startDate <= $periodTo) {
|
|
||||||
$summary->rttStartDate = $this->rttStartDate;
|
$summary->rttStartDate = $this->rttStartDate;
|
||||||
}
|
|
||||||
}
|
|
||||||
$summary->weeks = $this->buildWeekSummaries($weekRanges, $currentByWeekStart, $periodFrom, $periodTo);
|
$summary->weeks = $this->buildWeekSummaries($weekRanges, $currentByWeekStart, $periodFrom, $periodTo);
|
||||||
|
|
||||||
// Post-process: distribute deficit weeks across cumulative balance (50% first, then 25%)
|
// Post-process: distribute deficit weeks across cumulative balance (50% first, then 25%)
|
||||||
|
|||||||
Reference in New Issue
Block a user