Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 80
0.00% covered (danger)
0.00%
0 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
PickingOrderCalculateTotalExecutionTimeService
0.00% covered (danger)
0.00%
0 / 80
0.00% covered (danger)
0.00%
0 / 5
812
0.00% covered (danger)
0.00%
0 / 1
 calculate
0.00% covered (danger)
0.00%
0 / 40
0.00% covered (danger)
0.00%
0 / 1
30
 resolveDateRange
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
42
 parseDateRange
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
56
 toCarbonInstance
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
30
 clampInterval
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
30
1<?php
2
3namespace Domain\PickingOrder\Services;
4
5use App\Events\ServiceProcessed;
6use App\Models\PickingOrder\PickingOrder;
7use App\Models\PickingOrder\PickingOrderPause;
8use App\Models\User;
9use Carbon\Carbon;
10use Domain\PickingOrder\Enums\PickingOrderSearchDateEnum;
11use Illuminate\Http\Request;
12
13class PickingOrderCalculateTotalExecutionTimeService
14{
15    /**
16     * @param PickingOrder $pickingOrder
17     * @param User $user
18     * @return string
19     *
20     */
21    public function calculate(PickingOrder $pickingOrder, User $user, Request $request): string
22    {
23        if (!$pickingOrder->getAttribute('start_date')) {
24            return '00:00';
25        }
26
27        [$rangeStart, $rangeEnd] = $this->resolveDateRange($request);
28        $now = now();
29
30        $executionInterval = $this->clampInterval(
31            $pickingOrder->getAttribute('start_date')->copy(),
32            $pickingOrder->getAttribute('end_date')?->copy() ?? $now->copy(),
33            $rangeStart,
34            $rangeEnd
35        );
36
37        if (!$executionInterval) {
38            ServiceProcessed::dispatch(self::class, $user);
39            return '00:00:00';
40        }
41
42        [$startDate, $endDate] = $executionInterval;
43        $startDate->setSecond(00);
44        $endDate->setSecond(00);
45
46        $totalIntervalTime = $startDate->diffInSeconds($endDate);
47
48        $totalPauseTime = $pickingOrder->pauses->reduce(function ($total, PickingOrderPause $pause) use ($startDate, $endDate, $now) {
49            $pauseStart = $pause->getAttribute('start_date');
50
51            if (!$pauseStart) {
52                return $total;
53            }
54
55            $pauseInterval = $this->clampInterval(
56                $pauseStart->copy(),
57                $pause->getAttribute('end_date')?->copy() ?? $now->copy(),
58                $startDate,
59                $endDate
60            );
61
62            if (!$pauseInterval) {
63                return $total;
64            }
65
66            [$pauseStartDate, $pauseEndDate] = $pauseInterval;
67            $pauseStartDate->setSecond(00);
68            $pauseEndDate->setSecond(00);
69
70            return $total + $pauseStartDate->diffInSeconds($pauseEndDate);
71        }, 0);
72
73        $effectiveTime = max($totalIntervalTime - $totalPauseTime, 0);
74
75        $hours = floor($effectiveTime / 3600);
76        $minutes = floor(($effectiveTime % 3600) / 60);
77        $seconds = $effectiveTime % 60;
78
79        ServiceProcessed::dispatch(self::class, $user);
80
81        return sprintf('%02d:%02d:%02d', $hours, $minutes, $seconds);
82    }
83
84    /**
85     * @return Carbon[]
86     */
87    private function resolveDateRange(Request $request): array
88    {
89        foreach (['start_date', 'end_date', 'created_date'] as $field) {
90            $range = $this->parseDateRange($request->input($field));
91            if ($range) {
92                return $range;
93            }
94        }
95
96        $dateRangePreset = $request->input('date_range');
97
98        if ($dateRangePreset !== null && $dateRangePreset !== '') {
99            $enum = PickingOrderSearchDateEnum::tryFrom((int)$dateRangePreset);
100
101            if ($enum) {
102                $endDate = now()->copy()->endOfDay();
103                $startDate = now()->copy()->subDays($enum->value)->startOfDay();
104
105                return [$startDate, $endDate];
106            }
107        }
108
109        $now = now();
110
111        return [$now->copy()->startOfDay(), $now->copy()];
112    }
113
114    /**
115     * @param mixed $range
116     * @return array<int, Carbon>|null
117     */
118    private function parseDateRange(mixed $range): ?array
119    {
120        if (!$range || !is_array($range) || count($range) < 2) {
121            return null;
122        }
123
124        $startDate = $this->toCarbonInstance($range[0]);
125        $endDate = $this->toCarbonInstance($range[1]);
126
127        if (!$startDate || !$endDate) {
128            return null;
129        }
130
131        if ($startDate->greaterThan($endDate)) {
132            [$startDate, $endDate] = [$endDate, $startDate];
133        }
134
135        return [$startDate->startOfDay(), $endDate->endOfDay()];
136    }
137
138    private function toCarbonInstance(mixed $value): ?Carbon
139    {
140        if ($value instanceof Carbon) {
141            return $value->copy();
142        }
143
144        if (is_string($value) && $value !== '') {
145            try {
146                return Carbon::createFromFormat('d/m/Y', $value);
147            } catch (\Throwable) {
148                return null;
149            }
150        }
151
152        return null;
153    }
154
155    private function clampInterval(Carbon $intervalStart, Carbon $intervalEnd, Carbon $rangeStart, Carbon $rangeEnd): ?array
156    {
157        $start = $intervalStart->copy();
158        $end = $intervalEnd->copy();
159        $windowStart = $rangeStart->copy();
160        $windowEnd = $rangeEnd->copy();
161
162        if ($end->lessThan($start)) {
163            [$start, $end] = [$end, $start];
164        }
165
166        $clampedStart = $start->greaterThan($windowStart) ? $start : $windowStart;
167        $clampedEnd = $end->lessThan($windowEnd) ? $end : $windowEnd;
168
169        if ($clampedEnd->lessThanOrEqualTo($clampedStart)) {
170            return null;
171        }
172
173        return [$clampedStart, $clampedEnd];
174    }
175}