Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
100ab340d4 | ||
| 0257e59671 |
@@ -1,2 +1,2 @@
|
|||||||
parameters:
|
parameters:
|
||||||
app.version: '0.1.75'
|
app.version: '0.1.76'
|
||||||
|
|||||||
@@ -245,7 +245,7 @@ Tous les filtres checkbox sont cochés par défaut à l'ouverture du drawer.
|
|||||||
- pour `FORFAIT`:
|
- pour `FORFAIT`:
|
||||||
- pris: basé sur toutes les absences (demi-journées incluses)
|
- pris: basé sur toutes les absences (demi-journées incluses)
|
||||||
- restants = acquis - pris (borné à 0)
|
- restants = acquis - pris (borné à 0)
|
||||||
- paiement congés N-1: saisie RH via `PATCH /employees/{id}/paid-leave-days` (body: `paidLeaveDays`, `year`). Stocké dans `employee_leave_balances.paid_leave_days`. Les jours payés sont soustraits du reste à prendre N-1 (`previousYearRemainingDays = max(0, acquis_N-1 - pris_N-1 - payés)`). Uniquement pour les contrats forfait.
|
- paiement congés N-1: saisie RH via `PATCH /employees/{id}/paid-leave-days` (body: `paidLeaveDays`, `year`). Stocké dans `employee_leave_balances.paid_leave_days`. Les jours payés réduisent le stock N-1 **avant** l'attribution des jours pris : `disponible_N-1 = max(0, acquis_N-1 - payés)`, puis `pris_N-1 = min(disponible_N-1, total_pris)`, surplus pris basculé sur N. Reste à prendre N-1 = `max(0, disponible_N-1 - pris_N-1)`. Uniquement pour les contrats forfait.
|
||||||
- report annuel:
|
- report annuel:
|
||||||
- le reliquat (`restants`) de l'exercice précédent est reporté dans les acquis de l'exercice courant
|
- le reliquat (`restants`) de l'exercice précédent est reporté dans les acquis de l'exercice courant
|
||||||
- pour `CDI`/`CDD` non forfait: report séparé jours + samedis
|
- pour `CDI`/`CDD` non forfait: report séparé jours + samedis
|
||||||
@@ -275,8 +275,9 @@ Tous les filtres checkbox sont cochés par défaut à l'ouverture du drawer.
|
|||||||
- total mensuel des minutes de récupération
|
- total mensuel des minutes de récupération
|
||||||
- compteur global exercice = `report N-1 + acquis N`
|
- compteur global exercice = `report N-1 + acquis N`
|
||||||
- attribution mensuelle des semaines:
|
- attribution mensuelle des semaines:
|
||||||
- une semaine ISO est affichée une seule fois, dans le mois qui contient le **samedi** de cette semaine
|
- une semaine ISO qui chevauche deux mois est affichée dans **les deux mois**, avec les valeurs réparties proportionnellement aux minutes travaillées de chaque portion
|
||||||
- si le weekend tombe en début de mois suivant, c'est le mois suivant qui porte la semaine
|
- le calcul des heures supplémentaires reste hebdomadaire (seuils 35h/39h/43h appliqués sur la semaine entière), seul l'affichage est scindé
|
||||||
|
- exemple: S14 lundi-mardi en mars, mercredi-dimanche en avril → la S14 apparaît en mars (avec la part des heures de lun-mar) et en avril (avec la part mer-dim)
|
||||||
- logique de calcul:
|
- logique de calcul:
|
||||||
- base identique aux calculs d'heures supplémentaires de la vue semaine Heures
|
- base identique aux calculs d'heures supplémentaires de la vue semaine Heures
|
||||||
- minutes de récupération hebdomadaires = `HS totales + bonus 25% + bonus 50%`
|
- minutes de récupération hebdomadaires = `HS totales + bonus 25% + bonus 50%`
|
||||||
|
|||||||
@@ -32,9 +32,9 @@ Principe:
|
|||||||
|
|
||||||
## 4) Attribution mensuelle des semaines
|
## 4) Attribution mensuelle des semaines
|
||||||
|
|
||||||
- une semaine ISO est affichee une seule fois, dans le mois qui contient le **samedi** de cette semaine
|
- une semaine ISO qui chevauche deux mois est affichee dans **les deux mois**, avec les valeurs reparties proportionnellement aux minutes travaillees de chaque portion
|
||||||
- si le weekend tombe en debut du mois suivant, c'est ce mois qui porte la semaine
|
- le calcul des heures supplementaires reste hebdomadaire (seuils 35h/39h/43h appliques sur la semaine entiere), seul l'affichage est scinde
|
||||||
- pas de prorata: la totalite des minutes de recuperation de la semaine est comptee dans un seul mois
|
- exemple: S14 lundi-mardi en mars, mercredi-dimanche en avril → la S14 apparait en mars (part lun-mar) et en avril (part mer-dim)
|
||||||
|
|
||||||
## 5) Table cible
|
## 5) Table cible
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,7 @@
|
|||||||
<option value="contract_suspension">Suspension</option>
|
<option value="contract_suspension">Suspension</option>
|
||||||
<option value="rtt_payment">Paiement RTT</option>
|
<option value="rtt_payment">Paiement RTT</option>
|
||||||
<option value="fractioned_days">Jours fractionnés</option>
|
<option value="fractioned_days">Jours fractionnés</option>
|
||||||
|
<option value="paid_leave_days">Congés N-1 payés</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
@@ -241,6 +242,7 @@ const entityTypeLabel = (type: string): string => {
|
|||||||
contract_suspension: 'Suspension',
|
contract_suspension: 'Suspension',
|
||||||
rtt_payment: 'RTT',
|
rtt_payment: 'RTT',
|
||||||
fractioned_days: 'Fract.',
|
fractioned_days: 'Fract.',
|
||||||
|
paid_leave_days: 'Congés payés',
|
||||||
}
|
}
|
||||||
return map[type] ?? type
|
return map[type] ?? type
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ namespace App\Dto\Rtt;
|
|||||||
|
|
||||||
final class WeekRecoveryDetail
|
final class WeekRecoveryDetail
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* @param array<string, int> $dailyMinutes date (Y-m-d) => worked minutes
|
||||||
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
public int $overtimeMinutes = 0,
|
public int $overtimeMinutes = 0,
|
||||||
public int $base25Minutes = 0,
|
public int $base25Minutes = 0,
|
||||||
@@ -13,5 +16,6 @@ final class WeekRecoveryDetail
|
|||||||
public int $base50Minutes = 0,
|
public int $base50Minutes = 0,
|
||||||
public int $bonus50Minutes = 0,
|
public int $bonus50Minutes = 0,
|
||||||
public int $totalMinutes = 0,
|
public int $totalMinutes = 0,
|
||||||
|
public array $dailyMinutes = [],
|
||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ final readonly class RttRecoveryComputationService
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return list<array{month:int,weekNumber:int,start:DateTimeImmutable,end:DateTimeImmutable}>
|
* @return list<array{weekNumber:int,start:DateTimeImmutable,end:DateTimeImmutable}>
|
||||||
*/
|
*/
|
||||||
public function buildWeeksForExercise(DateTimeImmutable $from, DateTimeImmutable $to): array
|
public function buildWeeksForExercise(DateTimeImmutable $from, DateTimeImmutable $to): array
|
||||||
{
|
{
|
||||||
@@ -61,10 +61,7 @@ final readonly class RttRecoveryComputationService
|
|||||||
$effectiveEnd = $end > $to ? $to : $end;
|
$effectiveEnd = $end > $to ? $to : $end;
|
||||||
|
|
||||||
if ($effectiveEnd >= $effectiveStart) {
|
if ($effectiveEnd >= $effectiveStart) {
|
||||||
$saturday = $start->modify('+5 days');
|
$weeks[] = [
|
||||||
$monthAnchor = $saturday < $from ? $from : ($saturday > $to ? $to : $saturday);
|
|
||||||
$weeks[] = [
|
|
||||||
'month' => (int) $monthAnchor->format('n'),
|
|
||||||
'weekNumber' => (int) $effectiveStart->format('W'),
|
'weekNumber' => (int) $effectiveStart->format('W'),
|
||||||
'start' => $start,
|
'start' => $start,
|
||||||
'end' => $end,
|
'end' => $end,
|
||||||
@@ -82,7 +79,6 @@ final readonly class RttRecoveryComputationService
|
|||||||
$weeks = $this->buildWeeksForExercise($from, $to);
|
$weeks = $this->buildWeeksForExercise($from, $to);
|
||||||
$weekRanges = array_map(
|
$weekRanges = array_map(
|
||||||
static fn (array $week): array => [
|
static fn (array $week): array => [
|
||||||
'month' => (int) $week['month'],
|
|
||||||
'weekNumber' => (int) $week['weekNumber'],
|
'weekNumber' => (int) $week['weekNumber'],
|
||||||
'start' => $week['start'],
|
'start' => $week['start'],
|
||||||
'end' => $week['end'],
|
'end' => $week['end'],
|
||||||
@@ -108,7 +104,7 @@ final readonly class RttRecoveryComputationService
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param list<array{month:int,weekNumber:int,start:DateTimeImmutable,end:DateTimeImmutable}> $weeks
|
* @param list<array{weekNumber:int,start:DateTimeImmutable,end:DateTimeImmutable}> $weeks
|
||||||
*
|
*
|
||||||
* @return array<string, WeekRecoveryDetail>
|
* @return array<string, WeekRecoveryDetail>
|
||||||
*/
|
*/
|
||||||
@@ -189,6 +185,7 @@ final readonly class RttRecoveryComputationService
|
|||||||
}
|
}
|
||||||
|
|
||||||
$weeklyTotalMinutes = 0;
|
$weeklyTotalMinutes = 0;
|
||||||
|
$dailyWorkedMinutes = [];
|
||||||
$employeeContractsByDate = [];
|
$employeeContractsByDate = [];
|
||||||
foreach ($weekDays as $date) {
|
foreach ($weekDays as $date) {
|
||||||
$employeeContractsByDate[$date] = $contractsByDate[$employeeId][$date] ?? null;
|
$employeeContractsByDate[$date] = $contractsByDate[$employeeId][$date] ?? null;
|
||||||
@@ -198,6 +195,7 @@ final readonly class RttRecoveryComputationService
|
|||||||
$metrics = $metricsByDate[$date] ?? new WorkMetrics();
|
$metrics = $metricsByDate[$date] ?? new WorkMetrics();
|
||||||
$metrics->addCreditedMinutes($creditedByDate[$date] ?? 0);
|
$metrics->addCreditedMinutes($creditedByDate[$date] ?? 0);
|
||||||
$weeklyTotalMinutes += $metrics->totalMinutes;
|
$weeklyTotalMinutes += $metrics->totalMinutes;
|
||||||
|
$dailyWorkedMinutes[$date] = $metrics->totalMinutes;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ([] === $weekDays) {
|
if ([] === $weekDays) {
|
||||||
@@ -244,6 +242,7 @@ final readonly class RttRecoveryComputationService
|
|||||||
base50Minutes: $base50,
|
base50Minutes: $base50,
|
||||||
bonus50Minutes: $bonus50,
|
bonus50Minutes: $bonus50,
|
||||||
totalMinutes: $totalMinutes,
|
totalMinutes: $totalMinutes,
|
||||||
|
dailyMinutes: $dailyWorkedMinutes,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -95,6 +95,15 @@ final readonly class EmployeeLeaveSummaryProvider implements ProviderInterface
|
|||||||
$fractionedDays = $this->resolveFractionedDays($employee, $yearSummary['ruleCode'], $year);
|
$fractionedDays = $this->resolveFractionedDays($employee, $yearSummary['ruleCode'], $year);
|
||||||
$paidLeaveDays = $this->resolvePaidLeaveDays($employee, $yearSummary['ruleCode'], $year);
|
$paidLeaveDays = $this->resolvePaidLeaveDays($employee, $yearSummary['ruleCode'], $year);
|
||||||
|
|
||||||
|
// For forfait contracts, paid days reduce N-1 stock before taken-day attribution.
|
||||||
|
// Recompute with paidLeaveDays so taken days shift from N-1 to N when N-1 is consumed by payment.
|
||||||
|
if ($paidLeaveDays > 0.0) {
|
||||||
|
$yearSummary = $this->computeYearSummary($employee, $year, $paidLeaveDays);
|
||||||
|
if (null === $yearSummary) {
|
||||||
|
return $summary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$summary->isSupported = true;
|
$summary->isSupported = true;
|
||||||
$summary->ruleCode = $yearSummary['ruleCode'];
|
$summary->ruleCode = $yearSummary['ruleCode'];
|
||||||
$summary->acquiredDays = $yearSummary['acquiredDays'] + $fractionedDays;
|
$summary->acquiredDays = $yearSummary['acquiredDays'] + $fractionedDays;
|
||||||
@@ -107,7 +116,7 @@ final readonly class EmployeeLeaveSummaryProvider implements ProviderInterface
|
|||||||
$summary->remainingSaturdays = $yearSummary['remainingSaturdays'];
|
$summary->remainingSaturdays = $yearSummary['remainingSaturdays'];
|
||||||
$summary->previousYearAcquiredDays = $yearSummary['previousYearAcquiredDays'];
|
$summary->previousYearAcquiredDays = $yearSummary['previousYearAcquiredDays'];
|
||||||
$summary->previousYearTakenDays = $yearSummary['previousYearTakenDays'];
|
$summary->previousYearTakenDays = $yearSummary['previousYearTakenDays'];
|
||||||
$summary->previousYearRemainingDays = max(0.0, $yearSummary['previousYearRemainingDays'] - $paidLeaveDays);
|
$summary->previousYearRemainingDays = $yearSummary['previousYearRemainingDays'];
|
||||||
$summary->previousYearPaidDays = $paidLeaveDays;
|
$summary->previousYearPaidDays = $paidLeaveDays;
|
||||||
|
|
||||||
[$periodFrom, $periodTo] = $this->resolvePeriodBounds($employee, $year);
|
[$periodFrom, $periodTo] = $this->resolvePeriodBounds($employee, $year);
|
||||||
@@ -131,7 +140,7 @@ final readonly class EmployeeLeaveSummaryProvider implements ProviderInterface
|
|||||||
* previousYearRemainingDays: float
|
* previousYearRemainingDays: float
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
public function computeYearSummary(Employee $employee, int $targetYear): ?array
|
public function computeYearSummary(Employee $employee, int $targetYear, float $paidLeaveDays = 0.0): ?array
|
||||||
{
|
{
|
||||||
$firstYear = max($this->resolveFirstComputationYear($employee), $targetYear - 1);
|
$firstYear = max($this->resolveFirstComputationYear($employee), $targetYear - 1);
|
||||||
if ($targetYear < $firstYear) {
|
if ($targetYear < $firstYear) {
|
||||||
@@ -271,13 +280,15 @@ final readonly class EmployeeLeaveSummaryProvider implements ProviderInterface
|
|||||||
} else {
|
} else {
|
||||||
// Forfait: no "en cours d'acquisition" counter, all rights are in acquired.
|
// Forfait: no "en cours d'acquisition" counter, all rights are in acquired.
|
||||||
// Suspensions do not impact forfait 218 leave calculation.
|
// Suspensions do not impact forfait 218 leave calculation.
|
||||||
// Taken days are first deducted from N-1 carry, then from current year.
|
// Paid days reduce N-1 stock first, then taken days are attributed to what remains in N-1.
|
||||||
$previousYearAcquired = $carryDays;
|
$previousYearAcquired = $carryDays;
|
||||||
$takenFromPrevious = min(max(0.0, $previousYearAcquired), $takenDays);
|
$effectivePaidDays = ($year === $targetYear) ? $paidLeaveDays : 0.0;
|
||||||
$previousYearTaken = $takenFromPrevious;
|
$availableAfterPayment = max(0.0, $previousYearAcquired - $effectivePaidDays);
|
||||||
$takenFromCurrent = $takenDays - $takenFromPrevious;
|
$takenFromPrevious = min($availableAfterPayment, $takenDays);
|
||||||
|
$previousYearTaken = $takenFromPrevious;
|
||||||
|
$takenFromCurrent = $takenDays - $takenFromPrevious;
|
||||||
|
|
||||||
$previousYearRemaining = max(0.0, $previousYearAcquired - $takenFromPrevious);
|
$previousYearRemaining = max(0.0, $availableAfterPayment - $takenFromPrevious);
|
||||||
|
|
||||||
$acquiredDays = $leavePolicy['acquiredDays'];
|
$acquiredDays = $leavePolicy['acquiredDays'];
|
||||||
$accruingDays = 0.0;
|
$accruingDays = 0.0;
|
||||||
|
|||||||
@@ -71,7 +71,6 @@ final readonly class EmployeeRttSummaryProvider implements ProviderInterface
|
|||||||
$weeks = $this->rttRecoveryService->buildWeeksForExercise($periodFrom, $periodTo);
|
$weeks = $this->rttRecoveryService->buildWeeksForExercise($periodFrom, $periodTo);
|
||||||
$weekRanges = array_map(
|
$weekRanges = array_map(
|
||||||
static fn (array $week): array => [
|
static fn (array $week): array => [
|
||||||
'month' => (int) $week['month'],
|
|
||||||
'weekNumber' => (int) $week['weekNumber'],
|
'weekNumber' => (int) $week['weekNumber'],
|
||||||
'start' => $week['start'],
|
'start' => $week['start'],
|
||||||
'end' => $week['end'],
|
'end' => $week['end'],
|
||||||
@@ -118,25 +117,7 @@ final readonly class EmployeeRttSummaryProvider implements ProviderInterface
|
|||||||
$summary->rttStartDate = $this->rttStartDate;
|
$summary->rttStartDate = $this->rttStartDate;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$summary->weeks = array_map(
|
$summary->weeks = $this->buildWeekSummaries($weekRanges, $currentByWeekStart, $periodFrom, $periodTo);
|
||||||
static function (array $week) use ($currentByWeekStart) {
|
|
||||||
$detail = $currentByWeekStart[$week['start']->format('Y-m-d')] ?? new WeekRecoveryDetail();
|
|
||||||
|
|
||||||
return new EmployeeRttWeekSummary(
|
|
||||||
month: (int) $week['month'],
|
|
||||||
weekNumber: (int) $week['weekNumber'],
|
|
||||||
weekStart: $week['start']->format('Y-m-d'),
|
|
||||||
weekEnd: $week['end']->format('Y-m-d'),
|
|
||||||
overtimeMinutes: $detail->overtimeMinutes,
|
|
||||||
base25Minutes: $detail->base25Minutes,
|
|
||||||
bonus25Minutes: $detail->bonus25Minutes,
|
|
||||||
base50Minutes: $detail->base50Minutes,
|
|
||||||
bonus50Minutes: $detail->bonus50Minutes,
|
|
||||||
totalMinutes: $detail->totalMinutes,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
$weekRanges
|
|
||||||
);
|
|
||||||
|
|
||||||
// Post-process: distribute deficit weeks across cumulative balance (50% first, then 25%)
|
// Post-process: distribute deficit weeks across cumulative balance (50% first, then 25%)
|
||||||
$cumulative50 = $carry->base50Minutes + $carry->bonus50Minutes;
|
$cumulative50 = $carry->base50Minutes + $carry->bonus50Minutes;
|
||||||
@@ -269,4 +250,77 @@ final readonly class EmployeeRttSummaryProvider implements ProviderInterface
|
|||||||
|
|
||||||
return $weekEnd;
|
return $weekEnd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build week summaries, splitting weeks that span two months into two entries
|
||||||
|
* with values distributed proportionally based on daily worked minutes.
|
||||||
|
*
|
||||||
|
* @param list<array{weekNumber:int,start:DateTimeImmutable,end:DateTimeImmutable}> $weekRanges
|
||||||
|
* @param array<string, WeekRecoveryDetail> $recoveryByWeek
|
||||||
|
*
|
||||||
|
* @return list<EmployeeRttWeekSummary>
|
||||||
|
*/
|
||||||
|
private function buildWeekSummaries(array $weekRanges, array $recoveryByWeek, DateTimeImmutable $periodFrom, DateTimeImmutable $periodTo): array
|
||||||
|
{
|
||||||
|
$result = [];
|
||||||
|
|
||||||
|
foreach ($weekRanges as $week) {
|
||||||
|
$weekStart = $week['start'];
|
||||||
|
$weekEnd = $week['end'];
|
||||||
|
$weekKey = $weekStart->format('Y-m-d');
|
||||||
|
$detail = $recoveryByWeek[$weekKey] ?? new WeekRecoveryDetail();
|
||||||
|
|
||||||
|
$effectiveStart = $weekStart < $periodFrom ? $periodFrom : $weekStart;
|
||||||
|
$effectiveEnd = $weekEnd > $periodTo ? $periodTo : $weekEnd;
|
||||||
|
|
||||||
|
$startMonth = (int) $effectiveStart->format('n');
|
||||||
|
$endMonth = (int) $effectiveEnd->format('n');
|
||||||
|
|
||||||
|
if ($startMonth === $endMonth) {
|
||||||
|
$result[] = new EmployeeRttWeekSummary(
|
||||||
|
month: $startMonth,
|
||||||
|
weekNumber: (int) $week['weekNumber'],
|
||||||
|
weekStart: $weekStart->format('Y-m-d'),
|
||||||
|
weekEnd: $weekEnd->format('Y-m-d'),
|
||||||
|
overtimeMinutes: $detail->overtimeMinutes,
|
||||||
|
base25Minutes: $detail->base25Minutes,
|
||||||
|
bonus25Minutes: $detail->bonus25Minutes,
|
||||||
|
base50Minutes: $detail->base50Minutes,
|
||||||
|
bonus50Minutes: $detail->bonus50Minutes,
|
||||||
|
totalMinutes: $detail->totalMinutes,
|
||||||
|
);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Week spans two months — split proportionally by daily worked minutes
|
||||||
|
$monthMinutes = [];
|
||||||
|
foreach ($detail->dailyMinutes as $date => $mins) {
|
||||||
|
$m = (int) new DateTimeImmutable($date)->format('n');
|
||||||
|
$monthMinutes[$m] = ($monthMinutes[$m] ?? 0) + $mins;
|
||||||
|
}
|
||||||
|
|
||||||
|
$totalWorked = array_sum($monthMinutes);
|
||||||
|
|
||||||
|
foreach ([$startMonth, $endMonth] as $month) {
|
||||||
|
$portion = $monthMinutes[$month] ?? 0;
|
||||||
|
$ratio = $totalWorked > 0 ? $portion / $totalWorked : 0.0;
|
||||||
|
|
||||||
|
$result[] = new EmployeeRttWeekSummary(
|
||||||
|
month: $month,
|
||||||
|
weekNumber: (int) $week['weekNumber'],
|
||||||
|
weekStart: $weekStart->format('Y-m-d'),
|
||||||
|
weekEnd: $weekEnd->format('Y-m-d'),
|
||||||
|
overtimeMinutes: (int) round($detail->overtimeMinutes * $ratio),
|
||||||
|
base25Minutes: (int) round($detail->base25Minutes * $ratio),
|
||||||
|
bonus25Minutes: (int) round($detail->bonus25Minutes * $ratio),
|
||||||
|
base50Minutes: (int) round($detail->base50Minutes * $ratio),
|
||||||
|
bonus50Minutes: (int) round($detail->bonus50Minutes * $ratio),
|
||||||
|
totalMinutes: (int) round($detail->totalMinutes * $ratio),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user