537 lines
27 KiB
Vue
537 lines
27 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 }} {{ currentYear }}
|
|
</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"
|
|
@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-[11%]" />
|
|
<col class="w-[11%]" />
|
|
<col class="w-[11%]" />
|
|
<col class="w-[11%]" />
|
|
<col class="w-[11%]" />
|
|
<col class="w-[11%]" />
|
|
<col class="w-[11%]" />
|
|
<col class="w-[11%]" />
|
|
</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">Total</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">{{ 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">{{ 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">
|
|
<span v-if="week">{{ formatMinutes(week.totalMinutes) }}</span>
|
|
<span v-else>0 h</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-t-2">{{ formatMinutes(totals.total) }}</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' }}</td>
|
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">{{ currentPayment ? formatMinutes(-currentPayment.paidBonus25Minutes) : '0 h' }}</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' }}</td>
|
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">{{ currentPayment ? formatMinutes(-currentPayment.paidBase50Minutes) : '0 h' }}</td>
|
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">{{ currentPayment ? formatMinutes(-currentPayment.paidBonus50Minutes) : '0 h' }}</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' }}</td>
|
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">{{ formatMinutes(paidTotal) }}</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">{{ formatMinutes(reste.total) }} <span class="text-neutral-400">/ {{ formatCentiemes(reste.total) }}</span></td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</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% (heures)</label>
|
|
<input
|
|
v-model.number="paymentForm.base25Hours"
|
|
type="number"
|
|
step="0.5"
|
|
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% (heures)</label>
|
|
<input
|
|
v-model.number="paymentForm.bonus25Hours"
|
|
type="number"
|
|
step="0.5"
|
|
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% (heures)</label>
|
|
<input
|
|
v-model.number="paymentForm.base50Hours"
|
|
type="number"
|
|
step="0.5"
|
|
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% (heures)</label>
|
|
<input
|
|
v-model.number="paymentForm.bonus50Hours"
|
|
type="number"
|
|
step="0.5"
|
|
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'
|
|
|
|
const props = defineProps<{
|
|
summary: EmployeeRttSummary | null
|
|
}>()
|
|
|
|
const emit = defineEmits<{
|
|
(event: 'submit-rtt-payment', month: number, base25Minutes: number, bonus25Minutes: number, base50Minutes: number, bonus50Minutes: number): void
|
|
}>()
|
|
|
|
// --- 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 currentYear = 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 = existing.paidBase25Minutes / 60
|
|
paymentForm.bonus25Hours = existing.paidBonus25Minutes / 60
|
|
paymentForm.base50Hours = existing.paidBase50Minutes / 60
|
|
paymentForm.bonus50Hours = existing.paidBonus50Minutes / 60
|
|
} else {
|
|
paymentForm.base25Hours = 0
|
|
paymentForm.bonus25Hours = 0
|
|
paymentForm.base50Hours = 0
|
|
paymentForm.bonus50Hours = 0
|
|
}
|
|
}
|
|
|
|
watch(() => paymentForm.month, (newMonth) => {
|
|
prefillFromExistingPayment(newMonth)
|
|
})
|
|
|
|
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>
|