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>
595 lines
31 KiB
Vue
595 lines
31 KiB
Vue
<template>
|
|
<section class="flex h-full min-h-0 flex-col overflow-hidden pt-8">
|
|
<!-- Header bar -->
|
|
<div class="flex items-center justify-between rounded-t-md bg-tertiary-500 px-5 py-4 text-black border border-primary-500">
|
|
<div class="flex items-center">
|
|
<button
|
|
class="rounded px-2 py-1 font-bold hover:bg-primary-600 disabled:opacity-40 disabled:cursor-not-allowed flex items-center"
|
|
:disabled="currentMonthIndex === 0"
|
|
@click="currentMonthIndex--"
|
|
>
|
|
<Icon name="mdi:chevron-left" size="24"/>
|
|
</button>
|
|
<span class="text-lg font-bold tracking-wide min-w-[170px] text-center">
|
|
{{ currentMonthLabel }} {{ displayedMonthYear }}
|
|
</span>
|
|
<button
|
|
class="rounded px-2 py-1 font-bold hover:bg-primary-600 disabled:opacity-40 disabled:cursor-not-allowed flex items-center"
|
|
:disabled="currentMonthIndex === 11"
|
|
@click="currentMonthIndex++"
|
|
>
|
|
<Icon name="mdi:chevron-right" size="24"/>
|
|
</button>
|
|
</div>
|
|
<p class="text-[16px]">
|
|
<span class="font-bold">RTT À LA SEMAINE {{ lastCompleteWeek }} : </span>
|
|
<span class="font-bold">{{ formatMinutes(summary?.availableMinutes ?? 0) }}</span>
|
|
</p>
|
|
<div class="flex justify-center">
|
|
<button
|
|
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"
|
|
>
|
|
+ Payer les RTT
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Table -->
|
|
<div class="min-h-0 flex-1 overflow-y-auto">
|
|
<table class="w-full table-fixed border-collapse text-[18px]">
|
|
<colgroup>
|
|
<col />
|
|
<col class="w-[10%]" />
|
|
<col class="w-[10%]" />
|
|
<col class="w-[10%]" />
|
|
<col class="w-[10%]" />
|
|
<col class="w-[10%]" />
|
|
<col class="w-[10%]" />
|
|
<col class="w-[10%]" />
|
|
<col class="w-[10%]" />
|
|
<col class="w-[10%]" />
|
|
</colgroup>
|
|
<thead>
|
|
<tr>
|
|
<th class="px-5 py-[10px] text-left font-bold text-primary-500 border border-primary-500">Semaine</th>
|
|
<th class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500 border-r-2">Heure</th>
|
|
<th class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">Base</th>
|
|
<th class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">25%</th>
|
|
<th class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500 border-r-2">Total 25%</th>
|
|
<th class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">Base</th>
|
|
<th class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">50%</th>
|
|
<th class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500 border-r-2">Total 50%</th>
|
|
<th class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500 border-r-2">Total</th>
|
|
<th class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">Cumul</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<!-- Report N-1 row (RTT rollover carry, June only) -->
|
|
<tr v-if="showCarryRow" class="bg-tertiary-500">
|
|
<td class="px-5 py-[10px] font-bold text-primary-500 border border-primary-500">Report</td>
|
|
<td class="px-4 py-[10px] text-center text-neutral-500 border border-primary-500 border-r-2">-</td>
|
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">{{ formatMinutes(summary!.carryBase25Minutes) }} <span class="text-neutral-400">/ {{ formatCentiemes(summary!.carryBase25Minutes) }}</span></td>
|
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">{{ formatMinutes(summary!.carryBonus25Minutes) }} <span class="text-neutral-400">/ {{ formatCentiemes(summary!.carryBonus25Minutes) }}</span></td>
|
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500 border-r-2">{{ formatMinutes(summary!.carryBase25Minutes + summary!.carryBonus25Minutes) }} <span class="text-neutral-400">/ {{ formatCentiemes(summary!.carryBase25Minutes + summary!.carryBonus25Minutes) }}</span></td>
|
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">{{ formatMinutes(summary!.carryBase50Minutes) }} <span class="text-neutral-400">/ {{ formatCentiemes(summary!.carryBase50Minutes) }}</span></td>
|
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">{{ formatMinutes(summary!.carryBonus50Minutes) }} <span class="text-neutral-400">/ {{ formatCentiemes(summary!.carryBonus50Minutes) }}</span></td>
|
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500 border-r-2">{{ formatMinutes(summary!.carryBase50Minutes + summary!.carryBonus50Minutes) }} <span class="text-neutral-400">/ {{ formatCentiemes(summary!.carryBase50Minutes + summary!.carryBonus50Minutes) }}</span></td>
|
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500 border-r-2">{{ formatMinutes(summary!.carryFromPreviousYearMinutes) }} <span class="text-neutral-400">/ {{ formatCentiemes(summary!.carryFromPreviousYearMinutes) }}</span></td>
|
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">{{ formatMinutes(summary!.carryFromPreviousYearMinutes) }} <span class="text-neutral-400">/ {{ formatCentiemes(summary!.carryFromPreviousYearMinutes) }}</span></td>
|
|
</tr>
|
|
|
|
<!-- Report mois précédent (cumulated balance from previous months, July+) -->
|
|
<tr v-if="showMonthReportRow" class="bg-tertiary-500">
|
|
<td class="px-5 py-[10px] font-bold text-primary-500 border border-primary-500">Report</td>
|
|
<td class="px-4 py-[10px] text-center text-neutral-500 border border-primary-500 border-r-2">-</td>
|
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">{{ formatMinutes(monthReport.base25) }} <span class="text-neutral-400">/ {{ formatCentiemes(monthReport.base25) }}</span></td>
|
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">{{ formatMinutes(monthReport.bonus25) }} <span class="text-neutral-400">/ {{ formatCentiemes(monthReport.bonus25) }}</span></td>
|
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500 border-r-2">{{ formatMinutes(monthReport.total25) }} <span class="text-neutral-400">/ {{ formatCentiemes(monthReport.total25) }}</span></td>
|
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">{{ formatMinutes(monthReport.base50) }} <span class="text-neutral-400">/ {{ formatCentiemes(monthReport.base50) }}</span></td>
|
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">{{ formatMinutes(monthReport.bonus50) }} <span class="text-neutral-400">/ {{ formatCentiemes(monthReport.bonus50) }}</span></td>
|
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500 border-r-2">{{ formatMinutes(monthReport.total50) }} <span class="text-neutral-400">/ {{ formatCentiemes(monthReport.total50) }}</span></td>
|
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500 border-r-2">{{ formatMinutes(monthReport.total) }} <span class="text-neutral-400">/ {{ formatCentiemes(monthReport.total) }}</span></td>
|
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">{{ formatMinutes(monthReport.total) }} <span class="text-neutral-400">/ {{ formatCentiemes(monthReport.total) }}</span></td>
|
|
</tr>
|
|
|
|
<!-- Week rows (always 5) -->
|
|
<tr
|
|
v-for="(week, idx) in paddedWeeks"
|
|
:key="week ? week.weekStart : `empty-${idx}`"
|
|
>
|
|
<td class="px-5 py-[10px] font-bold text-primary-500 border border-primary-500">
|
|
<span v-if="week">Semaine {{ week.weekNumber }}</span>
|
|
<span v-else> </span>
|
|
</td>
|
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500 border-r-2">
|
|
<span v-if="week">{{ formatMinutes(week.overtimeMinutes) }}</span>
|
|
<span v-else>0 h</span>
|
|
</td>
|
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">
|
|
<span v-if="week">{{ formatMinutes(week.totalMinutes >= 0 ? week.base25Minutes : 0) }}</span>
|
|
<span v-else>0 h</span>
|
|
</td>
|
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">
|
|
<span v-if="week">{{ formatMinutes(week.totalMinutes >= 0 ? week.bonus25Minutes : 0) }}</span>
|
|
<span v-else>0 h</span>
|
|
</td>
|
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500 border-r-2">
|
|
<span v-if="week">{{ formatMinutes(week.base25Minutes + week.bonus25Minutes) }}</span>
|
|
<span v-else>0 h</span>
|
|
</td>
|
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">
|
|
<span v-if="week">{{ formatMinutes(week.totalMinutes >= 0 ? week.base50Minutes : 0) }}</span>
|
|
<span v-else>0 h</span>
|
|
</td>
|
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">
|
|
<span v-if="week">{{ formatMinutes(week.totalMinutes >= 0 ? week.bonus50Minutes : 0) }}</span>
|
|
<span v-else>0 h</span>
|
|
</td>
|
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500 border-r-2">
|
|
<span v-if="week">{{ formatMinutes(week.base50Minutes + week.bonus50Minutes) }}</span>
|
|
<span v-else>0 h</span>
|
|
</td>
|
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500 border-r-2">
|
|
<span v-if="week">{{ formatMinutes(week.totalMinutes) }}</span>
|
|
<span v-else>0 h</span>
|
|
</td>
|
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">
|
|
<span v-if="week">{{ formatMinutes(week.cumulativeBalanceMinutes) }} <span class="text-neutral-400">/ {{ formatCentiemes(week.cumulativeBalanceMinutes) }}</span></span>
|
|
<span v-else> </span>
|
|
</td>
|
|
</tr>
|
|
|
|
<!-- Total row -->
|
|
<tr>
|
|
<td class="px-5 py-[10px] font-bold text-primary-500 border border-primary-500 border-t-2">Total</td>
|
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500 border-r-2 border-t-2">{{ formatMinutes(totals.overtime) }}</td>
|
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500 border-t-2">{{ formatMinutes(totals.base25) }}</td>
|
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500 border-t-2">{{ formatMinutes(totals.bonus25) }}</td>
|
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500 border-r-2 border-t-2">{{ formatMinutes(totals.total25) }}</td>
|
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500 border-t-2">{{ formatMinutes(totals.base50) }}</td>
|
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500 border-t-2">{{ formatMinutes(totals.bonus50) }}</td>
|
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500 border-r-2 border-t-2">{{ formatMinutes(totals.total50) }}</td>
|
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500 border-r-2 border-t-2">{{ formatMinutes(totals.total) }}</td>
|
|
<td class="px-4 py-[10px] text-center text-neutral-500 border border-primary-500 border-t-2">-</td>
|
|
</tr>
|
|
|
|
<!-- Payé row -->
|
|
<tr>
|
|
<td class="px-5 py-[10px] font-bold text-primary-500 border border-primary-500">Payé</td>
|
|
<td class="px-4 py-[10px] text-center text-neutral-500 border border-primary-500 border-r-2">-</td>
|
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">{{ currentPayment ? formatMinutes(-currentPayment.paidBase25Minutes) : '0 h' }} <span class="text-neutral-400">/ {{ formatCentiemes(currentPayment ? -currentPayment.paidBase25Minutes : 0) }}</span></td>
|
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">{{ currentPayment ? formatMinutes(-currentPayment.paidBonus25Minutes) : '0 h' }} <span class="text-neutral-400">/ {{ formatCentiemes(currentPayment ? -currentPayment.paidBonus25Minutes : 0) }}</span></td>
|
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500 border-r-2">{{ currentPayment ? formatMinutes(-(currentPayment.paidBase25Minutes + currentPayment.paidBonus25Minutes)) : '0 h' }} <span class="text-neutral-400">/ {{ formatCentiemes(currentPayment ? -(currentPayment.paidBase25Minutes + currentPayment.paidBonus25Minutes) : 0) }}</span></td>
|
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">{{ currentPayment ? formatMinutes(-currentPayment.paidBase50Minutes) : '0 h' }} <span class="text-neutral-400">/ {{ formatCentiemes(currentPayment ? -currentPayment.paidBase50Minutes : 0) }}</span></td>
|
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">{{ currentPayment ? formatMinutes(-currentPayment.paidBonus50Minutes) : '0 h' }} <span class="text-neutral-400">/ {{ formatCentiemes(currentPayment ? -currentPayment.paidBonus50Minutes : 0) }}</span></td>
|
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500 border-r-2">{{ currentPayment ? formatMinutes(-(currentPayment.paidBase50Minutes + currentPayment.paidBonus50Minutes)) : '0 h' }} <span class="text-neutral-400">/ {{ formatCentiemes(currentPayment ? -(currentPayment.paidBase50Minutes + currentPayment.paidBonus50Minutes) : 0) }}</span></td>
|
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500 border-r-2">{{ formatMinutes(paidTotal) }} <span class="text-neutral-400">/ {{ formatCentiemes(paidTotal) }}</span></td>
|
|
<td class="px-4 py-[10px] text-center text-neutral-500 border border-primary-500">-</td>
|
|
</tr>
|
|
|
|
<!-- Reste row -->
|
|
<tr>
|
|
<td class="px-5 py-[10px] font-bold text-primary-500 border border-primary-500">Reste</td>
|
|
<td class="px-4 py-[10px] text-center text-neutral-500 border border-primary-500 border-r-2">-</td>
|
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">{{ formatMinutes(reste.base25) }} <span class="text-neutral-400">/ {{ formatCentiemes(reste.base25) }}</span></td>
|
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">{{ formatMinutes(reste.bonus25) }} <span class="text-neutral-400">/ {{ formatCentiemes(reste.bonus25) }}</span></td>
|
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500 border-r-2">{{ formatMinutes(reste.total25) }} <span class="text-neutral-400">/ {{ formatCentiemes(reste.total25) }}</span></td>
|
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">{{ formatMinutes(reste.base50) }} <span class="text-neutral-400">/ {{ formatCentiemes(reste.base50) }}</span></td>
|
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">{{ formatMinutes(reste.bonus50) }} <span class="text-neutral-400">/ {{ formatCentiemes(reste.bonus50) }}</span></td>
|
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500 border-r-2">{{ formatMinutes(reste.total50) }} <span class="text-neutral-400">/ {{ formatCentiemes(reste.total50) }}</span></td>
|
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500 border-r-2">{{ formatMinutes(reste.total) }} <span class="text-neutral-400">/ {{ formatCentiemes(reste.total) }}</span></td>
|
|
<td class="px-4 py-[10px] text-center text-neutral-500 border border-primary-500">-</td>
|
|
</tr>
|
|
</tbody>
|
|
</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>
|
|
|
|
<!-- Payment Drawer -->
|
|
<AppDrawer v-model="isPaymentDrawerOpen" title="Payer des RTT">
|
|
<form @submit.prevent="onSubmitPayment">
|
|
<div class="mb-4">
|
|
<label class="block text-sm font-medium text-neutral-700">Mois</label>
|
|
<select
|
|
v-model.number="paymentForm.month"
|
|
class="mt-2 w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-md text-neutral-900"
|
|
>
|
|
<option v-for="m in orderedMonthOptions" :key="m.value" :value="m.value">{{ m.label }}</option>
|
|
</select>
|
|
</div>
|
|
<div class="mb-4">
|
|
<label class="block text-sm font-medium text-neutral-700">Base 25% (centièmes)</label>
|
|
<input
|
|
v-model.number="paymentForm.base25Hours"
|
|
type="number"
|
|
step="0.01"
|
|
min="0"
|
|
class="mt-2 w-full rounded-md border border-neutral-300 px-3 py-2 text-base text-neutral-900 focus:border-primary-500 focus:outline-none focus:ring-2 focus:ring-secondary-500/20"
|
|
/>
|
|
</div>
|
|
<div class="mb-4">
|
|
<label class="block text-sm font-medium text-neutral-700">Heures 25% (centièmes)</label>
|
|
<input
|
|
v-model.number="paymentForm.bonus25Hours"
|
|
type="number"
|
|
step="0.01"
|
|
min="0"
|
|
class="mt-2 w-full rounded-md border border-neutral-300 px-3 py-2 text-base text-neutral-900 focus:border-primary-500 focus:outline-none focus:ring-2 focus:ring-secondary-500/20"
|
|
/>
|
|
</div>
|
|
<div class="mb-4">
|
|
<label class="block text-sm font-medium text-neutral-700">Base 50% (centièmes)</label>
|
|
<input
|
|
v-model.number="paymentForm.base50Hours"
|
|
type="number"
|
|
step="0.01"
|
|
min="0"
|
|
class="mt-2 w-full rounded-md border border-neutral-300 px-3 py-2 text-base text-neutral-900 focus:border-primary-500 focus:outline-none focus:ring-2 focus:ring-secondary-500/20"
|
|
/>
|
|
</div>
|
|
<div class="mb-6">
|
|
<label class="block text-sm font-medium text-neutral-700">Heures 50% (centièmes)</label>
|
|
<input
|
|
v-model.number="paymentForm.bonus50Hours"
|
|
type="number"
|
|
step="0.01"
|
|
min="0"
|
|
class="mt-2 w-full rounded-md border border-neutral-300 px-3 py-2 text-base text-neutral-900 focus:border-primary-500 focus:outline-none focus:ring-2 focus:ring-secondary-500/20"
|
|
/>
|
|
</div>
|
|
<div class="flex justify-end gap-3">
|
|
<button
|
|
type="button"
|
|
class="rounded-md border border-neutral-300 px-4 py-2 text-sm font-medium text-neutral-700 hover:bg-neutral-50"
|
|
@click="isPaymentDrawerOpen = false"
|
|
>
|
|
Annuler
|
|
</button>
|
|
<button
|
|
type="submit"
|
|
class="rounded-md bg-primary-500 px-4 py-2 text-sm font-medium text-white hover:bg-primary-600"
|
|
>
|
|
Enregistrer
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</AppDrawer>
|
|
</section>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import type { EmployeeRttSummary, EmployeeRttWeekSummary } from '~/services/dto/employee-rtt-summary'
|
|
import AppDrawer from '~/components/AppDrawer.vue'
|
|
|
|
type RttYearOption = {
|
|
value: number
|
|
label: string
|
|
}
|
|
|
|
const props = defineProps<{
|
|
summary: EmployeeRttSummary | null
|
|
selectedYear: number | null
|
|
availableYears: RttYearOption[]
|
|
currentYear: number | null
|
|
}>()
|
|
|
|
const emit = defineEmits<{
|
|
(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 ---
|
|
|
|
const lastCompleteWeek = computed(() => {
|
|
const now = new Date()
|
|
const startOfYear = new Date(now.getFullYear(), 0, 1)
|
|
const dayOfYear = Math.floor((now.getTime() - startOfYear.getTime()) / 86400000) + 1
|
|
const dayOfWeek = now.getDay() || 7 // Monday = 1, Sunday = 7
|
|
const currentWeek = Math.ceil((dayOfYear - dayOfWeek + 10) / 7)
|
|
return currentWeek - 1
|
|
})
|
|
|
|
// --- Month navigation ---
|
|
|
|
const orderedMonths = [6, 7, 8, 9, 10, 11, 12, 1, 2, 3, 4, 5] as const
|
|
|
|
const monthLabels: Record<number, string> = {
|
|
1: 'JANVIER',
|
|
2: 'FEVRIER',
|
|
3: 'MARS',
|
|
4: 'AVRIL',
|
|
5: 'MAI',
|
|
6: 'JUIN',
|
|
7: 'JUILLET',
|
|
8: 'AOUT',
|
|
9: 'SEPTEMBRE',
|
|
10: 'OCTOBRE',
|
|
11: 'NOVEMBRE',
|
|
12: 'DECEMBRE',
|
|
}
|
|
|
|
const orderedMonthOptions = [
|
|
{ value: 6, label: 'Juin' },
|
|
{ value: 7, label: 'Juillet' },
|
|
{ value: 8, label: 'Aout' },
|
|
{ value: 9, label: 'Septembre' },
|
|
{ value: 10, label: 'Octobre' },
|
|
{ value: 11, label: 'Novembre' },
|
|
{ value: 12, label: 'Decembre' },
|
|
{ value: 1, label: 'Janvier' },
|
|
{ value: 2, label: 'Fevrier' },
|
|
{ value: 3, label: 'Mars' },
|
|
{ value: 4, label: 'Avril' },
|
|
{ value: 5, label: 'Mai' },
|
|
]
|
|
|
|
// Initialize to current month's position in the exercise
|
|
const today = new Date()
|
|
const todayMonth = today.getMonth() + 1
|
|
const initialIndex = orderedMonths.indexOf(todayMonth as (typeof orderedMonths)[number])
|
|
const currentMonthIndex = ref(initialIndex >= 0 ? initialIndex : 0)
|
|
|
|
const currentMonth = computed(() => orderedMonths[currentMonthIndex.value])
|
|
|
|
const currentMonthLabel = computed(() => monthLabels[currentMonth.value])
|
|
|
|
const displayedMonthYear = computed(() => {
|
|
if (!props.summary) return ''
|
|
return currentMonth.value >= 6 ? props.summary.year - 1 : props.summary.year
|
|
})
|
|
|
|
// --- Weeks for current month ---
|
|
|
|
const weeksForCurrentMonth = computed((): EmployeeRttWeekSummary[] => {
|
|
if (!props.summary) return []
|
|
return props.summary.weeks.filter((w) => w.month === currentMonth.value)
|
|
})
|
|
|
|
const paddedWeeks = computed((): (EmployeeRttWeekSummary | null)[] => {
|
|
const weeks = weeksForCurrentMonth.value
|
|
const padded: (EmployeeRttWeekSummary | null)[] = [...weeks]
|
|
while (padded.length < 5) {
|
|
padded.push(null)
|
|
}
|
|
return padded
|
|
})
|
|
|
|
// --- Carry row (RTT rollover from previous year, June only) ---
|
|
|
|
const carryMonth = computed(() => {
|
|
if (!props.summary) return 6
|
|
const cm = props.summary.carryMonth
|
|
return cm >= 12 ? 1 : cm + 1
|
|
})
|
|
|
|
const showCarryRow = computed(() => {
|
|
if (currentMonth.value !== carryMonth.value) return false
|
|
if ((props.summary?.carryFromPreviousYearMinutes ?? 0) === 0) return false
|
|
|
|
// On the first exercise, hide carry if carry month is before rttStartDate
|
|
if (props.summary?.rttStartDate) {
|
|
const startDate = new Date(props.summary.rttStartDate)
|
|
const viewYear = currentMonth.value >= 6 ? props.summary.year - 1 : props.summary.year
|
|
const viewDate = new Date(viewYear, currentMonth.value - 1, 1)
|
|
const startMonthDate = new Date(startDate.getFullYear(), startDate.getMonth(), 1)
|
|
if (viewDate < startMonthDate) return false
|
|
}
|
|
|
|
return true
|
|
})
|
|
|
|
// --- Month report row (cumulated balance from previous months) ---
|
|
|
|
// Months of the exercise in order, starting from the carry month
|
|
const exerciseMonths = computed((): number[] => {
|
|
const start = carryMonth.value
|
|
const startIdx = orderedMonths.indexOf(start as (typeof orderedMonths)[number])
|
|
if (startIdx === -1) return [...orderedMonths]
|
|
return [...orderedMonths.slice(startIdx), ...orderedMonths.slice(0, startIdx)]
|
|
})
|
|
|
|
const monthReport = computed(() => {
|
|
if (!props.summary) return { base25: 0, bonus25: 0, total25: 0, base50: 0, bonus50: 0, total50: 0, total: 0 }
|
|
|
|
const cm = currentMonth.value
|
|
const cmIdx = exerciseMonths.value.indexOf(cm)
|
|
const previousMonths = exerciseMonths.value.slice(0, cmIdx)
|
|
|
|
// Start from carry (included in the cumulation)
|
|
let base25 = props.summary.carryBase25Minutes
|
|
let bonus25 = props.summary.carryBonus25Minutes
|
|
let base50 = props.summary.carryBase50Minutes
|
|
let bonus50 = props.summary.carryBonus50Minutes
|
|
let total = props.summary.carryFromPreviousYearMinutes
|
|
|
|
// Add weeks from previous months
|
|
for (const w of props.summary.weeks) {
|
|
if (previousMonths.includes(w.month)) {
|
|
base25 += w.base25Minutes
|
|
bonus25 += w.bonus25Minutes
|
|
base50 += w.base50Minutes
|
|
bonus50 += w.bonus50Minutes
|
|
total += w.totalMinutes
|
|
}
|
|
}
|
|
|
|
// Subtract payments from previous months
|
|
for (const p of props.summary.monthPayments) {
|
|
if (previousMonths.includes(p.month)) {
|
|
base25 -= p.paidBase25Minutes
|
|
bonus25 -= p.paidBonus25Minutes
|
|
base50 -= p.paidBase50Minutes
|
|
bonus50 -= p.paidBonus50Minutes
|
|
total -= (p.paidBase25Minutes + p.paidBonus25Minutes + p.paidBase50Minutes + p.paidBonus50Minutes)
|
|
}
|
|
}
|
|
|
|
return { base25, bonus25, total25: base25 + bonus25, base50, bonus50, total50: base50 + bonus50, total }
|
|
})
|
|
|
|
const showMonthReportRow = computed(() => {
|
|
// Not on the carry month — carry row handles that
|
|
if (currentMonth.value === carryMonth.value) return false
|
|
|
|
// On the first exercise (containing rttStartDate), hide report for months before the start date
|
|
if (props.summary?.rttStartDate) {
|
|
const startDate = new Date(props.summary.rttStartDate)
|
|
const startYear = startDate.getFullYear()
|
|
const startMonth = startDate.getMonth() + 1
|
|
const viewYear = currentMonth.value >= 6 ? props.summary.year - 1 : props.summary.year
|
|
const viewDate = new Date(viewYear, currentMonth.value - 1, 1)
|
|
const startMonthDate = new Date(startYear, startMonth - 1, 1)
|
|
if (viewDate < startMonthDate) return false
|
|
}
|
|
|
|
const r = monthReport.value
|
|
return r.total !== 0
|
|
})
|
|
|
|
// --- Totals (current month weeks only) ---
|
|
|
|
const totals = computed(() => {
|
|
const weeks = weeksForCurrentMonth.value
|
|
const positive = weeks.filter((w) => w.totalMinutes >= 0)
|
|
return {
|
|
overtime: weeks.reduce((s, w) => s + w.overtimeMinutes, 0),
|
|
base25: positive.reduce((s, w) => s + w.base25Minutes, 0),
|
|
bonus25: positive.reduce((s, w) => s + w.bonus25Minutes, 0),
|
|
total25: weeks.reduce((s, w) => s + w.base25Minutes + w.bonus25Minutes, 0),
|
|
base50: positive.reduce((s, w) => s + w.base50Minutes, 0),
|
|
bonus50: positive.reduce((s, w) => s + w.bonus50Minutes, 0),
|
|
total50: weeks.reduce((s, w) => s + w.base50Minutes + w.bonus50Minutes, 0),
|
|
total: weeks.reduce((s, w) => s + w.totalMinutes, 0),
|
|
}
|
|
})
|
|
|
|
const currentPayment = computed(() => {
|
|
if (!props.summary) return null
|
|
return props.summary.monthPayments.find((p) => p.month === currentMonth.value) ?? null
|
|
})
|
|
|
|
const paidTotal = computed(() => {
|
|
if (!currentPayment.value) return 0
|
|
const p = currentPayment.value
|
|
return -(p.paidBase25Minutes + p.paidBonus25Minutes + p.paidBase50Minutes + p.paidBonus50Minutes)
|
|
})
|
|
|
|
const reste = computed(() => {
|
|
const total25 = monthReport.value.total25 + totals.value.total25
|
|
- (currentPayment.value?.paidBase25Minutes ?? 0) - (currentPayment.value?.paidBonus25Minutes ?? 0)
|
|
const total50 = monthReport.value.total50 + totals.value.total50
|
|
- (currentPayment.value?.paidBase50Minutes ?? 0) - (currentPayment.value?.paidBonus50Minutes ?? 0)
|
|
|
|
const base25 = Math.round(total25 / 1.25)
|
|
const bonus25 = total25 - base25
|
|
const base50 = Math.round(total50 / 1.5)
|
|
const bonus50 = total50 - base50
|
|
const total = monthReport.value.total + totals.value.total + paidTotal.value
|
|
|
|
return { base25, bonus25, total25, base50, bonus50, total50, total }
|
|
})
|
|
|
|
// --- Format ---
|
|
|
|
const formatMinutes = (minutes: number): string => {
|
|
if (minutes === 0) return '0 h'
|
|
const sign = minutes < 0 ? '- ' : ''
|
|
const abs = Math.abs(minutes)
|
|
const hours = Math.floor(abs / 60)
|
|
const rest = abs % 60
|
|
if (rest === 0) return `${sign}${hours} h`
|
|
return `${sign}${hours} h ${rest} m`
|
|
}
|
|
|
|
const formatCentiemes = (minutes: number): string => {
|
|
const value = minutes / 60
|
|
return value.toFixed(2).replace('.', ',')
|
|
}
|
|
|
|
// --- Payment drawer ---
|
|
|
|
const isPaymentDrawerOpen = ref(false)
|
|
const paymentForm = reactive({
|
|
month: 6,
|
|
base25Hours: 0,
|
|
bonus25Hours: 0,
|
|
base50Hours: 0,
|
|
bonus50Hours: 0,
|
|
})
|
|
|
|
const prefillFromExistingPayment = (month: number) => {
|
|
const existing = props.summary?.monthPayments.find((p) => p.month === month) ?? null
|
|
if (existing) {
|
|
paymentForm.base25Hours = Math.round(existing.paidBase25Minutes / 60 * 100) / 100
|
|
paymentForm.bonus25Hours = Math.round(existing.paidBonus25Minutes / 60 * 100) / 100
|
|
paymentForm.base50Hours = Math.round(existing.paidBase50Minutes / 60 * 100) / 100
|
|
paymentForm.bonus50Hours = Math.round(existing.paidBonus50Minutes / 60 * 100) / 100
|
|
} else {
|
|
paymentForm.base25Hours = 0
|
|
paymentForm.bonus25Hours = 0
|
|
paymentForm.base50Hours = 0
|
|
paymentForm.bonus50Hours = 0
|
|
}
|
|
}
|
|
|
|
watch(() => paymentForm.month, (newMonth) => {
|
|
prefillFromExistingPayment(newMonth)
|
|
})
|
|
|
|
watch(() => paymentForm.base25Hours, (value) => {
|
|
paymentForm.bonus25Hours = Math.round(value * 0.25 * 100) / 100
|
|
})
|
|
|
|
watch(() => paymentForm.base50Hours, (value) => {
|
|
paymentForm.bonus50Hours = Math.round(value * 0.50 * 100) / 100
|
|
})
|
|
|
|
const openPaymentDrawer = () => {
|
|
paymentForm.month = currentMonth.value
|
|
prefillFromExistingPayment(currentMonth.value)
|
|
isPaymentDrawerOpen.value = true
|
|
}
|
|
|
|
const onSubmitPayment = () => {
|
|
emit(
|
|
'submit-rtt-payment',
|
|
paymentForm.month,
|
|
Math.round(paymentForm.base25Hours * 60),
|
|
Math.round(paymentForm.bonus25Hours * 60),
|
|
Math.round(paymentForm.base50Hours * 60),
|
|
Math.round(paymentForm.bonus50Hours * 60),
|
|
)
|
|
isPaymentDrawerOpen.value = false
|
|
}
|
|
</script>
|