Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 210
0.00% covered (danger)
0.00%
0 / 38
CRAP
0.00% covered (danger)
0.00%
0 / 3
LaborCostCalculator
0.00% covered (danger)
0.00%
0 / 165
0.00% covered (danger)
0.00%
0 / 9
702
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 calculateWeekCost
0.00% covered (danger)
0.00%
0 / 69
0.00% covered (danger)
0.00%
0 / 1
56
 calculateShiftCost
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
2
 getEffectivePayRate
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
6
 setPayRate
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 getPayRateHistory
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 calculateDailyHoursFromShifts
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
12
 calculateOvertimeFromScheduledHours
0.00% covered (danger)
0.00%
0 / 27
0.00% covered (danger)
0.00%
0 / 1
30
 calculateDailyOvertimeSimple
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
30
LaborCostResult
0.00% covered (danger)
0.00%
0 / 28
0.00% covered (danger)
0.00%
0 / 18
342
0.00% covered (danger)
0.00%
0 / 1
 setWeekStart
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getWeekStart
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setRegularHours
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getRegularHours
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setOvertimeHours
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getOvertimeHours
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setDoubletimeHours
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getDoubletimeHours
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setRegularCost
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getRegularCost
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setOvertimeCost
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getOvertimeCost
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setDoubletimeCost
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getDoubletimeCost
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getTotalCost
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 addEmployeeBreakdown
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getEmployeeBreakdown
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 toArray
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
2
ShiftCostResult
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 11
132
0.00% covered (danger)
0.00%
0 / 1
 setShiftId
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getShiftId
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setEmployeeId
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getEmployeeId
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setHours
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getHours
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setHourlyRate
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getHourlyRate
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setEstimatedCost
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getEstimatedCost
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 toArray
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace BuyerKiosk\Scheduling\Services;
4
5use BuyerKiosk\Scheduling\Models\Shift;
6use BuyerKiosk\Scheduling\Repositories\ShiftRepository;
7use BuyerKiosk\Scheduling\Repositories\TimePunchRepository;
8use DateTime;
9use PDO;
10
11/**
12 * LaborCostCalculator
13 *
14 * Calculates labor costs including regular, overtime, and doubletime pay.
15 * Integrates with OvertimeCalculator for accurate cost projections.
16 *
17 * @package BuyerKiosk\Scheduling\Services
18 */
19class LaborCostCalculator
20{
21    private PDO $centralDb;
22    private ShiftRepository $shiftRepository;
23    private TimePunchRepository $punchRepository;
24    private OvertimeCalculator $overtimeCalculator;
25
26    public function __construct(
27        PDO $centralDb,
28        ShiftRepository $shiftRepository,
29        TimePunchRepository $punchRepository,
30        OvertimeCalculator $overtimeCalculator
31    ) {
32        $this->centralDb = $centralDb;
33        $this->shiftRepository = $shiftRepository;
34        $this->punchRepository = $punchRepository;
35        $this->overtimeCalculator = $overtimeCalculator;
36    }
37
38    /**
39     * Calculate labor cost for a week (scheduled shifts)
40     *
41     * @param string $typeNum Store type number
42     * @param DateTime $weekStart Start of the week
43     * @param string $storeTimezone Store timezone
44     * @return LaborCostResult
45     */
46    public function calculateWeekCost(
47        string $typeNum,
48        DateTime $weekStart,
49        string $storeTimezone = 'America/Los_Angeles'
50    ): LaborCostResult {
51        $weekEnd = (clone $weekStart)->modify('+7 days');
52
53        // Load overtime rules
54        $rules = $this->overtimeCalculator->loadRulesForStore($typeNum, $weekStart);
55        $ruleJson = $rules['ruleJson'] ?? [];
56
57        // Get shifts for the week
58        $shifts = $this->shiftRepository->findByDateRange($weekStart, $weekEnd);
59
60        // Group shifts by employee
61        $shiftsByEmployee = [];
62        foreach ($shifts as $shift) {
63            $empId = $shift->getEmployeeId();
64            if (!isset($shiftsByEmployee[$empId])) {
65                $shiftsByEmployee[$empId] = [];
66            }
67            $shiftsByEmployee[$empId][] = $shift;
68        }
69
70        $result = new LaborCostResult();
71        $result->setWeekStart($weekStart);
72
73        $totalRegularHours = 0;
74        $totalOvertimeHours = 0;
75        $totalDoubletimeHours = 0;
76        $totalRegularCost = 0;
77        $totalOvertimeCost = 0;
78        $totalDoubletimeCost = 0;
79
80        $overtimeMultiplier = $ruleJson['overtimeMultiplier'] ?? 1.5;
81        $doubletimeMultiplier = $ruleJson['doubletimeMultiplier'] ?? 2.0;
82
83        foreach ($shiftsByEmployee as $employeeId => $employeeShifts) {
84            // Get effective pay rate for this employee
85            $payRate = $this->getEffectivePayRate((int)$employeeId, $typeNum, $weekStart);
86
87            // Calculate total scheduled hours per day
88            $dailyHours = $this->calculateDailyHoursFromShifts($employeeShifts, $storeTimezone);
89
90            // Apply overtime rules to daily hours
91            $overtimeResult = $this->calculateOvertimeFromScheduledHours(
92                $dailyHours,
93                $rules,
94                (int)$employeeId,
95                $weekStart
96            );
97
98            $regular = $overtimeResult['regular'];
99            $overtime = $overtimeResult['overtime'];
100            $doubletime = $overtimeResult['doubletime'];
101
102            $regularCost = $regular * $payRate;
103            $overtimeCost = $overtime * $payRate * $overtimeMultiplier;
104            $doubletimeCost = $doubletime * $payRate * $doubletimeMultiplier;
105
106            $totalRegularHours += $regular;
107            $totalOvertimeHours += $overtime;
108            $totalDoubletimeHours += $doubletime;
109            $totalRegularCost += $regularCost;
110            $totalOvertimeCost += $overtimeCost;
111            $totalDoubletimeCost += $doubletimeCost;
112
113            // Build warnings for this employee
114            $warnings = [];
115            if ($overtime > 0) {
116                $warnings[] = sprintf('%.1f hours overtime', $overtime);
117            }
118            if ($doubletime > 0) {
119                $warnings[] = sprintf('%.1f hours doubletime', $doubletime);
120            }
121
122            $employeeName = null;
123            if (count($employeeShifts) > 0) {
124                $employeeName = $employeeShifts[0]->getEmployeeName();
125            }
126
127            $result->addEmployeeBreakdown((int)$employeeId, [
128                'employeeId' => (int)$employeeId,
129                'name' => $employeeName,
130                'hourlyRate' => $payRate,
131                'regularHours' => $regular,
132                'overtimeHours' => $overtime,
133                'doubletimeHours' => $doubletime,
134                'regularCost' => round($regularCost, 2),
135                'overtimeCost' => round($overtimeCost, 2),
136                'doubletimeCost' => round($doubletimeCost, 2),
137                'totalCost' => round($regularCost + $overtimeCost + $doubletimeCost, 2),
138                'warnings' => $warnings,
139            ]);
140        }
141
142        $result->setRegularHours($totalRegularHours);
143        $result->setOvertimeHours($totalOvertimeHours);
144        $result->setDoubletimeHours($totalDoubletimeHours);
145        $result->setRegularCost(round($totalRegularCost, 2));
146        $result->setOvertimeCost(round($totalOvertimeCost, 2));
147        $result->setDoubletimeCost(round($totalDoubletimeCost, 2));
148
149        return $result;
150    }
151
152    /**
153     * Calculate cost for a single shift
154     *
155     * @param Shift $shift The shift
156     * @param string $typeNum Store type number
157     * @param DateTime $weekStart Week start for context
158     * @return ShiftCostResult
159     */
160    public function calculateShiftCost(
161        Shift $shift,
162        string $typeNum,
163        DateTime $weekStart
164    ): ShiftCostResult {
165        $payRate = $this->getEffectivePayRate($shift->getEmployeeId(), $typeNum, $weekStart);
166        $hours = $shift->getDurationHours();
167
168        // For a single shift, assume regular pay
169        // (actual overtime depends on total week - this is for quick estimates)
170        $cost = $hours * $payRate;
171
172        $result = new ShiftCostResult();
173        $result->setShiftId($shift->getShiftId());
174        $result->setEmployeeId($shift->getEmployeeId());
175        $result->setHours($hours);
176        $result->setHourlyRate($payRate);
177        $result->setEstimatedCost(round($cost, 2));
178
179        return $result;
180    }
181
182    /**
183     * Get effective pay rate for an employee at a store on a date
184     *
185     * @param int $userId User ID (from employees table, maps to users)
186     * @param string $typeNum Store type number
187     * @param DateTime $date Effective date
188     * @return float Hourly rate
189     */
190    public function getEffectivePayRate(int $userId, string $typeNum, DateTime $date): float
191    {
192        // Look up from userPayRates table in central DB
193        $stmt = $this->centralDb->prepare("
194            SELECT hourlyRate
195            FROM userPayRates
196            WHERE userId = :userId
197              AND typeNum = :typeNum
198              AND effectiveAt <= :date
199            ORDER BY effectiveAt DESC
200            LIMIT 1
201        ");
202        $stmt->bindValue(':userId', $userId, PDO::PARAM_INT);
203        $stmt->bindValue(':typeNum', $typeNum);
204        $stmt->bindValue(':date', $date->format('Y-m-d H:i:s'));
205        $stmt->execute();
206
207        $row = $stmt->fetch(PDO::FETCH_ASSOC);
208
209        if ($row) {
210            return (float)$row['hourlyRate'];
211        }
212
213        // Fallback: return a default minimum wage if no rate set
214        // This should trigger a warning in the UI
215        return 15.00; // Default placeholder
216    }
217
218    /**
219     * Set a new pay rate for an employee
220     *
221     * @param int $userId User ID
222     * @param string $typeNum Store type number
223     * @param float $hourlyRate New hourly rate
224     * @param DateTime $effectiveDate When the rate takes effect (UTC)
225     * @param int $setByUserId User who set the rate
226     * @return int Pay rate record ID
227     */
228    public function setPayRate(
229        int $userId,
230        string $typeNum,
231        float $hourlyRate,
232        DateTime $effectiveDate,
233        int $setByUserId
234    ): int {
235        $stmt = $this->centralDb->prepare("
236            INSERT INTO userPayRates (userId, typeNum, hourlyRate, effectiveAt, createdByUserId)
237            VALUES (:userId, :typeNum, :hourlyRate, :effectiveAt, :createdByUserId)
238        ");
239        $stmt->bindValue(':userId', $userId, PDO::PARAM_INT);
240        $stmt->bindValue(':typeNum', $typeNum);
241        $stmt->bindValue(':hourlyRate', $hourlyRate);
242        $stmt->bindValue(':effectiveAt', $effectiveDate->format('Y-m-d H:i:s'));
243        $stmt->bindValue(':createdByUserId', $setByUserId, PDO::PARAM_INT);
244        $stmt->execute();
245
246        return (int)$this->centralDb->lastInsertId();
247    }
248
249    /**
250     * Get pay rate history for an employee at a store
251     *
252     * @param int $userId User ID
253     * @param string $typeNum Store type number
254     * @return array List of pay rate records
255     */
256    public function getPayRateHistory(int $userId, string $typeNum): array
257    {
258        $stmt = $this->centralDb->prepare("
259            SELECT
260                pr.*,
261                CONCAT(u.first_name, ' ', u.last_name) as setByName
262            FROM userPayRates pr
263            LEFT JOIN users u ON pr.createdByUserId = u.id
264            WHERE pr.userId = :userId
265              AND pr.typeNum = :typeNum
266            ORDER BY pr.effectiveAt DESC
267        ");
268        $stmt->bindValue(':userId', $userId, PDO::PARAM_INT);
269        $stmt->bindValue(':typeNum', $typeNum);
270        $stmt->execute();
271
272        return $stmt->fetchAll(PDO::FETCH_ASSOC);
273    }
274
275    /**
276     * Calculate daily hours from shifts
277     *
278     * @param Shift[] $shifts Array of shifts
279     * @param string $timezone Store timezone
280     * @return array<string, float> Date => hours
281     */
282    private function calculateDailyHoursFromShifts(array $shifts, string $timezone): array
283    {
284        $tz = new \DateTimeZone($timezone);
285        $dailyHours = [];
286
287        foreach ($shifts as $shift) {
288            $localStart = clone $shift->getShiftStart();
289            $localStart->setTimezone($tz);
290            $dateStr = $localStart->format('Y-m-d');
291
292            if (!isset($dailyHours[$dateStr])) {
293                $dailyHours[$dateStr] = 0;
294            }
295
296            $dailyHours[$dateStr] += $shift->getDurationHours();
297        }
298
299        return $dailyHours;
300    }
301
302    /**
303     * Calculate overtime from scheduled hours (not punches)
304     *
305     * @param array<string, float> $dailyHours Daily hours
306     * @param array $rules Overtime rules
307     * @param int $employeeId Employee ID
308     * @param DateTime $weekStart Week start
309     * @return array{regular: float, overtime: float, doubletime: float}
310     */
311    private function calculateOvertimeFromScheduledHours(
312        array $dailyHours,
313        array $rules,
314        int $employeeId,
315        DateTime $weekStart
316    ): array {
317        $ruleJson = $rules['ruleJson'] ?? [];
318        $dailyRules = $ruleJson['dailyRules'] ?? [];
319        $weeklyOvertimeThreshold = $ruleJson['weeklyOvertimeThresholdHours'] ?? 40;
320        $weeklyCountableHoursPerDayCap = $ruleJson['weeklyCountableHoursPerDayCap'] ?? null;
321
322        $totalRegular = 0;
323        $totalOvertime = 0;
324        $totalDoubletime = 0;
325        $countableForWeekly = 0;
326
327        // Sort daily hours by date
328        ksort($dailyHours);
329
330        foreach ($dailyHours as $date => $hours) {
331            // Calculate daily overtime
332            $dailyResult = $this->calculateDailyOvertimeSimple($hours, $dailyRules);
333
334            $totalRegular += $dailyResult['regular'];
335            $totalOvertime += $dailyResult['overtime'];
336            $totalDoubletime += $dailyResult['doubletime'];
337
338            // Calculate countable hours for weekly
339            $countable = $dailyResult['regular'];
340            if ($weeklyCountableHoursPerDayCap !== null) {
341                $countable = min($countable, $weeklyCountableHoursPerDayCap);
342            }
343            $countableForWeekly += $countable;
344        }
345
346        // Calculate weekly overtime
347        if ($weeklyOvertimeThreshold !== null && $countableForWeekly > $weeklyOvertimeThreshold) {
348            $weeklyOT = $countableForWeekly - $weeklyOvertimeThreshold;
349            $totalOvertime += $weeklyOT;
350            $totalRegular -= $weeklyOT;
351        }
352
353        return [
354            'regular' => max(0, $totalRegular),
355            'overtime' => $totalOvertime,
356            'doubletime' => $totalDoubletime,
357        ];
358    }
359
360    /**
361     * Simple daily overtime calculation
362     *
363     * @param float $hours Hours worked
364     * @param array $dailyRules Daily overtime rules
365     * @return array{regular: float, overtime: float, doubletime: float}
366     */
367    private function calculateDailyOvertimeSimple(float $hours, array $dailyRules): array
368    {
369        $result = [
370            'regular' => $hours,
371            'overtime' => 0,
372            'doubletime' => 0,
373        ];
374
375        if (empty($dailyRules)) {
376            return $result;
377        }
378
379        // Sort by threshold descending
380        usort($dailyRules, fn($a, $b) => ($b['thresholdHours'] ?? 0) <=> ($a['thresholdHours'] ?? 0));
381
382        $remainingHours = $hours;
383        foreach ($dailyRules as $rule) {
384            $threshold = $rule['thresholdHours'] ?? 8;
385            $multiplier = $rule['multiplier'] ?? 1.5;
386
387            if ($remainingHours > $threshold) {
388                $hoursAtRate = $remainingHours - $threshold;
389                $remainingHours = $threshold;
390
391                if ($multiplier >= 2.0) {
392                    $result['doubletime'] += $hoursAtRate;
393                } else {
394                    $result['overtime'] += $hoursAtRate;
395                }
396            }
397        }
398
399        $result['regular'] = $remainingHours;
400        return $result;
401    }
402}
403
404/**
405 * Result object for week labor cost calculations
406 */
407class LaborCostResult
408{
409    private DateTime $weekStart;
410    private float $regularHours = 0;
411    private float $overtimeHours = 0;
412    private float $doubletimeHours = 0;
413    private float $regularCost = 0;
414    private float $overtimeCost = 0;
415    private float $doubletimeCost = 0;
416    /** @var array<int, array> */
417    private array $employeeBreakdown = [];
418
419    public function setWeekStart(DateTime $weekStart): void
420    {
421        $this->weekStart = $weekStart;
422    }
423
424    public function getWeekStart(): DateTime
425    {
426        return $this->weekStart;
427    }
428
429    public function setRegularHours(float $hours): void
430    {
431        $this->regularHours = $hours;
432    }
433
434    public function getRegularHours(): float
435    {
436        return $this->regularHours;
437    }
438
439    public function setOvertimeHours(float $hours): void
440    {
441        $this->overtimeHours = $hours;
442    }
443
444    public function getOvertimeHours(): float
445    {
446        return $this->overtimeHours;
447    }
448
449    public function setDoubletimeHours(float $hours): void
450    {
451        $this->doubletimeHours = $hours;
452    }
453
454    public function getDoubletimeHours(): float
455    {
456        return $this->doubletimeHours;
457    }
458
459    public function setRegularCost(float $cost): void
460    {
461        $this->regularCost = $cost;
462    }
463
464    public function getRegularCost(): float
465    {
466        return $this->regularCost;
467    }
468
469    public function setOvertimeCost(float $cost): void
470    {
471        $this->overtimeCost = $cost;
472    }
473
474    public function getOvertimeCost(): float
475    {
476        return $this->overtimeCost;
477    }
478
479    public function setDoubletimeCost(float $cost): void
480    {
481        $this->doubletimeCost = $cost;
482    }
483
484    public function getDoubletimeCost(): float
485    {
486        return $this->doubletimeCost;
487    }
488
489    public function getTotalCost(): float
490    {
491        return $this->regularCost + $this->overtimeCost + $this->doubletimeCost;
492    }
493
494    public function addEmployeeBreakdown(int $employeeId, array $data): void
495    {
496        $this->employeeBreakdown[$employeeId] = $data;
497    }
498
499    /**
500     * @return array<int, array>
501     */
502    public function getEmployeeBreakdown(): array
503    {
504        return $this->employeeBreakdown;
505    }
506
507    public function toArray(): array
508    {
509        return [
510            'weekStart' => $this->weekStart->format('Y-m-d'),
511            'regularHours' => $this->regularHours,
512            'overtimeHours' => $this->overtimeHours,
513            'doubletimeHours' => $this->doubletimeHours,
514            'regularCost' => $this->regularCost,
515            'overtimeCost' => $this->overtimeCost,
516            'doubletimeCost' => $this->doubletimeCost,
517            'totalCost' => $this->getTotalCost(),
518            'employeeBreakdown' => array_values($this->employeeBreakdown),
519        ];
520    }
521}
522
523/**
524 * Result object for single shift cost calculations
525 */
526class ShiftCostResult
527{
528    private ?int $shiftId = null;
529    private int $employeeId;
530    private float $hours;
531    private float $hourlyRate;
532    private float $estimatedCost;
533
534    public function setShiftId(?int $shiftId): void
535    {
536        $this->shiftId = $shiftId;
537    }
538
539    public function getShiftId(): ?int
540    {
541        return $this->shiftId;
542    }
543
544    public function setEmployeeId(int $employeeId): void
545    {
546        $this->employeeId = $employeeId;
547    }
548
549    public function getEmployeeId(): int
550    {
551        return $this->employeeId;
552    }
553
554    public function setHours(float $hours): void
555    {
556        $this->hours = $hours;
557    }
558
559    public function getHours(): float
560    {
561        return $this->hours;
562    }
563
564    public function setHourlyRate(float $rate): void
565    {
566        $this->hourlyRate = $rate;
567    }
568
569    public function getHourlyRate(): float
570    {
571        return $this->hourlyRate;
572    }
573
574    public function setEstimatedCost(float $cost): void
575    {
576        $this->estimatedCost = $cost;
577    }
578
579    public function getEstimatedCost(): float
580    {
581        return $this->estimatedCost;
582    }
583
584    public function toArray(): array
585    {
586        return [
587            'shiftId' => $this->shiftId,
588            'employeeId' => $this->employeeId,
589            'hours' => $this->hours,
590            'hourlyRate' => $this->hourlyRate,
591            'estimatedCost' => $this->estimatedCost,
592        ];
593    }
594}