Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8563ddb08c | ||
| 353d4d9d2b | |||
|
|
8745e5e425 | ||
| 4d8c850a77 | |||
| 1974ace1f2 | |||
|
|
a99a12a759 | ||
| 548b5d63a6 | |||
|
|
ed9df4e178 | ||
| 625b4af5ba |
@@ -1,2 +1,2 @@
|
|||||||
parameters:
|
parameters:
|
||||||
app.version: '0.1.46'
|
app.version: '0.1.50'
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ Documents complementaires:
|
|||||||
- Validation: même logique que les heures classiques (`isValid`, `isSiteValid`, bulk)
|
- Validation: même logique que les heures classiques (`isValid`, `isSiteValid`, bulk)
|
||||||
- Vue semaine:
|
- Vue semaine:
|
||||||
- jour/nuit/atelier par jour + indicateurs repas/dîner/nuitée
|
- jour/nuit/atelier par jour + indicateurs repas/dîner/nuitée
|
||||||
- panier de nuit (PN): affiché par jour si nightMinutes > dayMinutes, et total hebdo dans la colonne Jour/Nuit sem.
|
- panier de nuit (PN): affiché par jour si (nightMinutes > dayMinutes) OU (nightMinutes >= 240, soit au moins 4h de travail entre 21h et 6h), et total hebdo dans la colonne Jour/Nuit sem.
|
||||||
- totaux hebdo: jour, nuit, atelier, total, compteurs petit déj/déjeuner/dîner/nuitée
|
- totaux hebdo: jour, nuit, atelier, total, compteurs petit déj/déjeuner/dîner/nuitée
|
||||||
- pas de calcul d'heures supplémentaires pour les conducteurs
|
- pas de calcul d'heures supplémentaires pour les conducteurs
|
||||||
- Le flag `isDriver` est sur `EmployeeContractPeriod` (un employé peut changer de statut chauffeur selon la période)
|
- Le flag `isDriver` est sur `EmployeeContractPeriod` (un employé peut changer de statut chauffeur selon la période)
|
||||||
@@ -212,12 +212,14 @@ Tous les filtres checkbox sont cochés par défaut à l'ouverture du drawer.
|
|||||||
- en cours d'acquisition jours: `25/12 = 2,08` jours/mois
|
- en cours d'acquisition jours: `25/12 = 2,08` jours/mois
|
||||||
- en cours d'acquisition samedis: `5/12 = 0,42` samedi/mois (non detaille en UI)
|
- en cours d'acquisition samedis: `5/12 = 0,42` samedi/mois (non detaille en UI)
|
||||||
- en cas de début/fin en cours de mois, l'acquisition est proratisée au nombre de jours calendaires couverts dans le mois
|
- en cas de début/fin en cours de mois, l'acquisition est proratisée au nombre de jours calendaires couverts dans le mois
|
||||||
|
- en cas de suspension en cours de mois, l'acquisition est proratisée en jours ouvrés (lun-ven hors fériés) travaillés / 22 (standard mensuel)
|
||||||
- samedis acquis affiches: uniquement `opening_saturdays` (report N-1)
|
- samedis acquis affiches: uniquement `opening_saturdays` (report N-1)
|
||||||
- contrat `4h`:
|
- contrat `4h`:
|
||||||
- acquis annuel CP: `10`
|
- acquis annuel CP: `10`
|
||||||
- acquis annuel samedi: `0`
|
- acquis annuel samedi: `0`
|
||||||
- en cours d'acquisition: `0.83` jour/mois
|
- en cours d'acquisition: `0.83` jour/mois
|
||||||
- en cas de début/fin en cours de mois, l'acquisition est proratisée au nombre de jours calendaires couverts dans le mois
|
- en cas de début/fin en cours de mois, l'acquisition est proratisée au nombre de jours calendaires couverts dans le mois
|
||||||
|
- en cas de suspension en cours de mois, l'acquisition est proratisée en jours ouvrés (lun-ven hors fériés) travaillés / 22
|
||||||
- contrat `FORFAIT`:
|
- contrat `FORFAIT`:
|
||||||
- base annuelle: `jours ouvrés de l'exercice (lundi-vendredi, hors jours fériés métropole) - 218`
|
- base annuelle: `jours ouvrés de l'exercice (lundi-vendredi, hors jours fériés métropole) - 218`
|
||||||
- prorata: en cas de démarrage/fin de contrat en cours d'année civile, le calcul ne couvre que l'intervalle actif du contrat dans l'année
|
- prorata: en cas de démarrage/fin de contrat en cours d'année civile, le calcul ne couvre que l'intervalle actif du contrat dans l'année
|
||||||
@@ -325,7 +327,7 @@ Tous les filtres checkbox sont cochés par défaut à l'ouverture du drawer.
|
|||||||
| Base | Contract.name | Via EmployeeContractResolver pour le mois |
|
| Base | Contract.name | Via EmployeeContractResolver pour le mois |
|
||||||
| Jour de présence Cadre | WorkHour | Uniquement FORFAIT (PRESENCE). Somme isPresentMorning (0.5) + isPresentAfternoon (0.5) |
|
| Jour de présence Cadre | WorkHour | Uniquement FORFAIT (PRESENCE). Somme isPresentMorning (0.5) + isPresentAfternoon (0.5) |
|
||||||
| Heures de nuit | WorkHour | Non-chauffeurs: calcul intervalles nuit (00:00-06:00, 21:00-24:00). Chauffeurs: somme nightHoursMinutes |
|
| Heures de nuit | WorkHour | Non-chauffeurs: calcul intervalles nuit (00:00-06:00, 21:00-24:00). Chauffeurs: somme nightHoursMinutes |
|
||||||
| Panier de nuit | WorkHour | Nombre de jours où nightMinutes > dayMinutes |
|
| Panier de nuit | WorkHour | Nombre de jours où (nightMinutes > dayMinutes) OU (nightMinutes >= 240, soit 4h entre 21h-6h) |
|
||||||
| Heures payés | EmployeeRttPayment | Somme base25Minutes + base50Minutes du mois, convertie en heures |
|
| Heures payés | EmployeeRttPayment | Somme base25Minutes + base50Minutes du mois, convertie en heures |
|
||||||
| Congés - Nombre | Absence code 'C' | Jours (demi-journées = 0.5) |
|
| Congés - Nombre | Absence code 'C' | Jours (demi-journées = 0.5) |
|
||||||
| Congés - Date | Absence code 'C' | Dates formatées dd/mm |
|
| Congés - Date | Absence code 'C' | Dates formatées dd/mm |
|
||||||
|
|||||||
@@ -64,7 +64,7 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<!-- Report N-1 row (RTT rollover carry, June only) -->
|
<!-- Report N-1 row (RTT rollover carry, June only) -->
|
||||||
<tr v-if="showCarryRow">
|
<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-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 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) }}</td>
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">{{ formatMinutes(summary!.carryBase25Minutes) }}</td>
|
||||||
@@ -77,7 +77,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<!-- Report mois précédent (cumulated balance from previous months, July+) -->
|
<!-- Report mois précédent (cumulated balance from previous months, July+) -->
|
||||||
<tr v-if="showMonthReportRow">
|
<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-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 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) }}</td>
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">{{ formatMinutes(monthReport.base25) }}</td>
|
||||||
@@ -103,11 +103,11 @@
|
|||||||
<span v-else>0 h</span>
|
<span v-else>0 h</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">
|
||||||
<span v-if="week">{{ formatMinutes(week.base25Minutes) }}</span>
|
<span v-if="week">{{ formatMinutes(week.totalMinutes >= 0 ? week.base25Minutes : 0) }}</span>
|
||||||
<span v-else>0 h</span>
|
<span v-else>0 h</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">
|
||||||
<span v-if="week">{{ formatMinutes(week.bonus25Minutes) }}</span>
|
<span v-if="week">{{ formatMinutes(week.totalMinutes >= 0 ? week.bonus25Minutes : 0) }}</span>
|
||||||
<span v-else>0 h</span>
|
<span v-else>0 h</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500 border-r-2">
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500 border-r-2">
|
||||||
@@ -115,11 +115,11 @@
|
|||||||
<span v-else>0 h</span>
|
<span v-else>0 h</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">
|
||||||
<span v-if="week">{{ formatMinutes(week.base50Minutes) }}</span>
|
<span v-if="week">{{ formatMinutes(week.totalMinutes >= 0 ? week.base50Minutes : 0) }}</span>
|
||||||
<span v-else>0 h</span>
|
<span v-else>0 h</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">
|
||||||
<span v-if="week">{{ formatMinutes(week.bonus50Minutes) }}</span>
|
<span v-if="week">{{ formatMinutes(week.totalMinutes >= 0 ? week.bonus50Minutes : 0) }}</span>
|
||||||
<span v-else>0 h</span>
|
<span v-else>0 h</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500 border-r-2">
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500 border-r-2">
|
||||||
@@ -162,13 +162,13 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td class="px-5 py-[10px] font-bold text-primary-500 border border-primary-500">Reste</td>
|
<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 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 + totals.base25 - (currentPayment?.paidBase25Minutes ?? 0)) }}</td>
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">{{ formatMinutes(reste.base25) }}</td>
|
||||||
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">{{ formatMinutes(monthReport.bonus25 + totals.bonus25 - (currentPayment?.paidBonus25Minutes ?? 0)) }}</td>
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">{{ formatMinutes(reste.bonus25) }}</td>
|
||||||
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500 border-r-2">{{ formatMinutes(monthReport.total25 + totals.total25 - (currentPayment?.paidBase25Minutes ?? 0) - (currentPayment?.paidBonus25Minutes ?? 0)) }}</td>
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500 border-r-2">{{ formatMinutes(reste.total25) }}</td>
|
||||||
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">{{ formatMinutes(monthReport.base50 + totals.base50 - (currentPayment?.paidBase50Minutes ?? 0)) }}</td>
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">{{ formatMinutes(reste.base50) }}</td>
|
||||||
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">{{ formatMinutes(monthReport.bonus50 + totals.bonus50 - (currentPayment?.paidBonus50Minutes ?? 0)) }}</td>
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">{{ formatMinutes(reste.bonus50) }}</td>
|
||||||
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500 border-r-2">{{ formatMinutes(monthReport.total50 + totals.total50 - (currentPayment?.paidBase50Minutes ?? 0) - (currentPayment?.paidBonus50Minutes ?? 0)) }}</td>
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500 border-r-2">{{ formatMinutes(reste.total50) }}</td>
|
||||||
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">{{ formatMinutes(resteTotal) }}</td>
|
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">{{ formatMinutes(reste.total) }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -419,13 +419,14 @@ const showMonthReportRow = computed(() => {
|
|||||||
|
|
||||||
const totals = computed(() => {
|
const totals = computed(() => {
|
||||||
const weeks = weeksForCurrentMonth.value
|
const weeks = weeksForCurrentMonth.value
|
||||||
|
const positive = weeks.filter((w) => w.totalMinutes >= 0)
|
||||||
return {
|
return {
|
||||||
overtime: weeks.reduce((s, w) => s + w.overtimeMinutes, 0),
|
overtime: weeks.reduce((s, w) => s + w.overtimeMinutes, 0),
|
||||||
base25: weeks.reduce((s, w) => s + w.base25Minutes, 0),
|
base25: positive.reduce((s, w) => s + w.base25Minutes, 0),
|
||||||
bonus25: weeks.reduce((s, w) => s + w.bonus25Minutes, 0),
|
bonus25: positive.reduce((s, w) => s + w.bonus25Minutes, 0),
|
||||||
total25: weeks.reduce((s, w) => s + w.base25Minutes + w.bonus25Minutes, 0),
|
total25: weeks.reduce((s, w) => s + w.base25Minutes + w.bonus25Minutes, 0),
|
||||||
base50: weeks.reduce((s, w) => s + w.base50Minutes, 0),
|
base50: positive.reduce((s, w) => s + w.base50Minutes, 0),
|
||||||
bonus50: weeks.reduce((s, w) => s + w.bonus50Minutes, 0),
|
bonus50: positive.reduce((s, w) => s + w.bonus50Minutes, 0),
|
||||||
total50: weeks.reduce((s, w) => s + w.base50Minutes + w.bonus50Minutes, 0),
|
total50: weeks.reduce((s, w) => s + w.base50Minutes + w.bonus50Minutes, 0),
|
||||||
total: weeks.reduce((s, w) => s + w.totalMinutes, 0),
|
total: weeks.reduce((s, w) => s + w.totalMinutes, 0),
|
||||||
}
|
}
|
||||||
@@ -442,8 +443,19 @@ const paidTotal = computed(() => {
|
|||||||
return -(p.paidBase25Minutes + p.paidBonus25Minutes + p.paidBase50Minutes + p.paidBonus50Minutes)
|
return -(p.paidBase25Minutes + p.paidBonus25Minutes + p.paidBase50Minutes + p.paidBonus50Minutes)
|
||||||
})
|
})
|
||||||
|
|
||||||
const resteTotal = computed(() => {
|
const reste = computed(() => {
|
||||||
return monthReport.value.total + totals.value.total + paidTotal.value
|
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 ---
|
// --- Format ---
|
||||||
|
|||||||
@@ -1045,7 +1045,7 @@ export const useHoursPage = () => {
|
|||||||
isSubmitting.value = true
|
isSubmitting.value = true
|
||||||
try {
|
try {
|
||||||
const entries = employees.value
|
const entries = employees.value
|
||||||
.filter((employee) => hasContractAtSelectedDate(employee.id))
|
.filter((employee) => hasContractAtSelectedDate(employee.id) && !isRowLocked(employee.id))
|
||||||
.map((employee) => {
|
.map((employee) => {
|
||||||
const employeeId = employee.id
|
const employeeId = employee.id
|
||||||
const row = rows.value[employeeId] ?? emptyRow()
|
const row = rows.value[employeeId] ?? emptyRow()
|
||||||
|
|||||||
@@ -278,10 +278,11 @@ final readonly class LeaveBalanceComputationService
|
|||||||
return 0.0;
|
return 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
$periodStart = $this->normalizeDate($periodStart);
|
$periodStart = $this->normalizeDate($periodStart);
|
||||||
$periodEnd = $this->normalizeDate($periodEnd);
|
$periodEnd = $this->normalizeDate($periodEnd);
|
||||||
$coveredMonths = 0.0;
|
$publicHolidays = [] !== $suspensions ? $this->buildPublicHolidayMap($periodStart, $periodEnd) : [];
|
||||||
$cursor = $periodStart->modify('first day of this month')->setTime(0, 0);
|
$coveredMonths = 0.0;
|
||||||
|
$cursor = $periodStart->modify('first day of this month')->setTime(0, 0);
|
||||||
while ($cursor <= $periodEnd) {
|
while ($cursor <= $periodEnd) {
|
||||||
$monthStart = $cursor > $periodStart ? $cursor : $periodStart;
|
$monthStart = $cursor > $periodStart ? $cursor : $periodStart;
|
||||||
$monthEnd = $cursor->modify('last day of this month')->setTime(0, 0);
|
$monthEnd = $cursor->modify('last day of this month')->setTime(0, 0);
|
||||||
@@ -289,11 +290,19 @@ final readonly class LeaveBalanceComputationService
|
|||||||
$monthEnd = $periodEnd;
|
$monthEnd = $periodEnd;
|
||||||
}
|
}
|
||||||
|
|
||||||
$coveredDays = ((int) $monthEnd->diff($monthStart)->format('%a')) + 1;
|
|
||||||
if ([] !== $suspensions) {
|
if ([] !== $suspensions) {
|
||||||
$suspendedDays = $this->suspensionDaysCalculator->countSuspendedDaysInMonth($monthStart, $monthEnd, $suspensions);
|
$suspendedDays = $this->suspensionDaysCalculator->countSuspendedDaysInMonth($monthStart, $monthEnd, $suspensions);
|
||||||
$coveredDays = max(0, $coveredDays - $suspendedDays);
|
if ($suspendedDays > 0) {
|
||||||
|
$businessDays = $this->countBusinessDaysInRange($monthStart, $monthEnd, $publicHolidays);
|
||||||
|
$suspendedBusinessDays = $this->suspensionDaysCalculator->countSuspendedBusinessDays($monthStart, $monthEnd, $suspensions, $publicHolidays);
|
||||||
|
$coveredMonths += max(0, $businessDays - $suspendedBusinessDays) / 22.0;
|
||||||
|
$cursor = $cursor->modify('first day of next month');
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$coveredDays = ((int) $monthEnd->diff($monthStart)->format('%a')) + 1;
|
||||||
$daysInMonth = (int) $cursor->format('t');
|
$daysInMonth = (int) $cursor->format('t');
|
||||||
$coveredMonths += $coveredDays / $daysInMonth;
|
$coveredMonths += $coveredDays / $daysInMonth;
|
||||||
|
|
||||||
@@ -317,8 +326,15 @@ final readonly class LeaveBalanceComputationService
|
|||||||
|
|
||||||
private function countBusinessDays(DateTimeImmutable $from, DateTimeImmutable $to): int
|
private function countBusinessDays(DateTimeImmutable $from, DateTimeImmutable $to): int
|
||||||
{
|
{
|
||||||
$publicHolidays = $this->buildPublicHolidayMap($from, $to);
|
return $this->countBusinessDaysInRange($from, $to, $this->buildPublicHolidayMap($from, $to));
|
||||||
$count = 0;
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, string> $publicHolidays pre-built map
|
||||||
|
*/
|
||||||
|
private function countBusinessDaysInRange(DateTimeImmutable $from, DateTimeImmutable $to, array $publicHolidays): int
|
||||||
|
{
|
||||||
|
$count = 0;
|
||||||
for ($cursor = $from; $cursor <= $to; $cursor = $cursor->modify('+1 day')) {
|
for ($cursor = $from; $cursor <= $to; $cursor = $cursor->modify('+1 day')) {
|
||||||
$weekDay = (int) $cursor->format('N');
|
$weekDay = (int) $cursor->format('N');
|
||||||
$dayKey = $cursor->format('Y-m-d');
|
$dayKey = $cursor->format('Y-m-d');
|
||||||
|
|||||||
@@ -390,10 +390,11 @@ final readonly class EmployeeLeaveSummaryProvider implements ProviderInterface
|
|||||||
return 0.0;
|
return 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
$periodStart = $this->normalizeDate($periodStart);
|
$periodStart = $this->normalizeDate($periodStart);
|
||||||
$periodEnd = $this->normalizeDate($periodEnd);
|
$periodEnd = $this->normalizeDate($periodEnd);
|
||||||
$coveredMonths = 0.0;
|
$publicHolidays = [] !== $suspensions ? $this->buildPublicHolidayMap($periodStart, $periodEnd) : [];
|
||||||
$cursor = $periodStart->modify('first day of this month')->setTime(0, 0);
|
$coveredMonths = 0.0;
|
||||||
|
$cursor = $periodStart->modify('first day of this month')->setTime(0, 0);
|
||||||
while ($cursor <= $periodEnd) {
|
while ($cursor <= $periodEnd) {
|
||||||
$monthStart = $cursor > $periodStart ? $cursor : $periodStart;
|
$monthStart = $cursor > $periodStart ? $cursor : $periodStart;
|
||||||
$monthEnd = $cursor->modify('last day of this month')->setTime(0, 0);
|
$monthEnd = $cursor->modify('last day of this month')->setTime(0, 0);
|
||||||
@@ -401,11 +402,19 @@ final readonly class EmployeeLeaveSummaryProvider implements ProviderInterface
|
|||||||
$monthEnd = $periodEnd;
|
$monthEnd = $periodEnd;
|
||||||
}
|
}
|
||||||
|
|
||||||
$coveredDays = ((int) $monthEnd->diff($monthStart)->format('%a')) + 1;
|
|
||||||
if ([] !== $suspensions) {
|
if ([] !== $suspensions) {
|
||||||
$suspendedDays = $this->suspensionDaysCalculator->countSuspendedDaysInMonth($monthStart, $monthEnd, $suspensions);
|
$suspendedDays = $this->suspensionDaysCalculator->countSuspendedDaysInMonth($monthStart, $monthEnd, $suspensions);
|
||||||
$coveredDays = max(0, $coveredDays - $suspendedDays);
|
if ($suspendedDays > 0) {
|
||||||
|
$businessDays = $this->countBusinessDays($monthStart, $monthEnd, $publicHolidays);
|
||||||
|
$suspendedBusinessDays = $this->suspensionDaysCalculator->countSuspendedBusinessDays($monthStart, $monthEnd, $suspensions, $publicHolidays);
|
||||||
|
$coveredMonths += max(0, $businessDays - $suspendedBusinessDays) / 22.0;
|
||||||
|
$cursor = $cursor->modify('first day of next month');
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$coveredDays = ((int) $monthEnd->diff($monthStart)->format('%a')) + 1;
|
||||||
$daysInMonth = (int) $cursor->format('t');
|
$daysInMonth = (int) $cursor->format('t');
|
||||||
$coveredMonths += $coveredDays / $daysInMonth;
|
$coveredMonths += $coveredDays / $daysInMonth;
|
||||||
|
|
||||||
@@ -526,10 +535,13 @@ final readonly class EmployeeLeaveSummaryProvider implements ProviderInterface
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
private function countBusinessDays(DateTimeImmutable $from, DateTimeImmutable $to): int
|
/**
|
||||||
|
* @param null|array<string, string> $publicHolidays pre-built map (built if null)
|
||||||
|
*/
|
||||||
|
private function countBusinessDays(DateTimeImmutable $from, DateTimeImmutable $to, ?array $publicHolidays = null): int
|
||||||
{
|
{
|
||||||
$publicHolidays = $this->buildPublicHolidayMap($from, $to);
|
$publicHolidays ??= $this->buildPublicHolidayMap($from, $to);
|
||||||
$count = 0;
|
$count = 0;
|
||||||
for ($cursor = $from; $cursor <= $to; $cursor = $cursor->modify('+1 day')) {
|
for ($cursor = $from; $cursor <= $to; $cursor = $cursor->modify('+1 day')) {
|
||||||
$weekDay = (int) $cursor->format('N');
|
$weekDay = (int) $cursor->format('N');
|
||||||
$dayKey = $cursor->format('Y-m-d');
|
$dayKey = $cursor->format('Y-m-d');
|
||||||
|
|||||||
@@ -298,7 +298,7 @@ class SalaryRecapPrintProvider implements ProviderInterface
|
|||||||
$nightMinutesTotal += $wh->getNightHoursMinutes() ?? 0;
|
$nightMinutesTotal += $wh->getNightHoursMinutes() ?? 0;
|
||||||
$dayMin = $wh->getDayHoursMinutes() ?? 0;
|
$dayMin = $wh->getDayHoursMinutes() ?? 0;
|
||||||
$nightMin = $wh->getNightHoursMinutes() ?? 0;
|
$nightMin = $wh->getNightHoursMinutes() ?? 0;
|
||||||
if ($nightMin > $dayMin && $nightMin > 0) {
|
if (($nightMin > $dayMin && $nightMin > 0) || $nightMin >= 240) {
|
||||||
++$nightBasketCount;
|
++$nightBasketCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -322,7 +322,7 @@ class SalaryRecapPrintProvider implements ProviderInterface
|
|||||||
} else {
|
} else {
|
||||||
$metrics = $this->computeNightMinutes($wh);
|
$metrics = $this->computeNightMinutes($wh);
|
||||||
$nightMinutesTotal += $metrics['nightMinutes'];
|
$nightMinutesTotal += $metrics['nightMinutes'];
|
||||||
if ($metrics['nightMinutes'] > $metrics['dayMinutes'] && $metrics['nightMinutes'] > 0) {
|
if (($metrics['nightMinutes'] > $metrics['dayMinutes'] && $metrics['nightMinutes'] > 0) || $metrics['nightMinutes'] >= 240) {
|
||||||
++$nightBasketCount;
|
++$nightBasketCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -271,7 +271,7 @@ final readonly class WorkHourWeeklySummaryProvider implements ProviderInterface
|
|||||||
$present = min(1.0, $morning + $afternoon + $creditedPresence);
|
$present = min(1.0, $morning + $afternoon + $creditedPresence);
|
||||||
}
|
}
|
||||||
|
|
||||||
$hasNightBasket = $nightMinutes > $dayMinutes && $nightMinutes > 0;
|
$hasNightBasket = ($nightMinutes > $dayMinutes && $nightMinutes > 0) || $nightMinutes >= 240;
|
||||||
if ($hasNightBasket) {
|
if ($hasNightBasket) {
|
||||||
++$weeklyNightBasketCount;
|
++$weeklyNightBasketCount;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user