Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
140 / 140
100.00% covered (success)
100.00%
6 / 6
CRAP
100.00% covered (success)
100.00%
1 / 1
ShiftsController
100.00% covered (success)
100.00%
140 / 140
100.00% covered (success)
100.00%
6 / 6
19
100.00% covered (success)
100.00%
1 / 1
 entriesByAngeltype
100.00% covered (success)
100.00%
29 / 29
100.00% covered (success)
100.00%
1 / 1
1
 entriesByLocation
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
1 / 1
1
 entriesByShiftType
100.00% covered (success)
100.00%
20 / 20
100.00% covered (success)
100.00%
1 / 1
1
 entriesByUser
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 shiftEntriesResponse
100.00% covered (success)
100.00%
39 / 39
100.00% covered (success)
100.00%
1 / 1
8
 getNeededAngelTypes
100.00% covered (success)
100.00%
23 / 23
100.00% covered (success)
100.00%
1 / 1
7
1<?php
2
3declare(strict_types=1);
4
5namespace Engelsystem\Controllers\Api;
6
7use Engelsystem\Controllers\Api\Resources\AngelTypeResource;
8use Engelsystem\Controllers\Api\Resources\LocationResource;
9use Engelsystem\Controllers\Api\Resources\ShiftWithEntriesResource;
10use Engelsystem\Controllers\Api\Resources\UserResource;
11use Engelsystem\Http\Request;
12use Engelsystem\Http\Response;
13use Engelsystem\Models\AngelType;
14use Engelsystem\Models\Location;
15use Engelsystem\Models\Shifts\NeededAngelType;
16use Engelsystem\Models\Shifts\Shift;
17use Engelsystem\Models\Shifts\ShiftEntry;
18use Engelsystem\Models\Shifts\ShiftType;
19use Illuminate\Contracts\Database\Query\Builder as BuilderContract;
20use Illuminate\Database\Eloquent\Collection;
21
22class ShiftsController extends ApiController
23{
24    use UsesAuth;
25
26    public function entriesByAngeltype(Request $request): Response
27    {
28        $id = (int) $request->getAttribute('angeltype_id');
29        /** @var AngelType $angelType */
30        $angelType = AngelType::findOrFail($id);
31
32        // From users assigned to shifts
33        $shiftsByEntries = Shift::query()
34            ->join('shift_entries', 'shift_entries.shift_id', 'shifts.id')
35            ->where('angel_type_id', $angelType->id)
36            ->select('shifts.*');
37
38        // Needed by a shift directly
39        $shiftsByShift = Shift::query()
40            ->join('needed_angel_types', 'needed_angel_types.shift_id', 'shifts.id')
41            ->where('needed_angel_types.angel_type_id', $angelType->id)
42            ->select('shifts.*');
43
44        // Needed by connected schedule via shift location
45        $shiftsByScheduleLocation = Shift::query()
46            ->join('schedule_shift', 'schedule_shift.shift_id', 'shifts.id')
47            ->join('schedules', 'schedules.id', 'schedule_shift.schedule_id')
48            ->where('schedules.needed_from_shift_type', false)
49            ->join('needed_angel_types', 'needed_angel_types.location_id', 'shifts.location_id')
50            ->where('needed_angel_types.angel_type_id', $angelType->id)
51            ->select('shifts.*');
52
53        // Needed by connected schedule via schedule shift type
54        $shiftsByScheduleShiftType = Shift::query()
55            ->join('schedule_shift', 'schedule_shift.shift_id', 'shifts.id')
56            ->join('schedules', 'schedules.id', 'schedule_shift.schedule_id')
57            ->where('schedules.needed_from_shift_type', true)
58            ->join('needed_angel_types', 'needed_angel_types.shift_type_id', 'schedules.shift_type')
59            ->where('needed_angel_types.angel_type_id', $angelType->id)
60            ->select('shifts.*');
61
62        $shifts = $shiftsByShift
63            ->union($shiftsByEntries)
64            ->union($shiftsByScheduleLocation)
65            ->union($shiftsByScheduleShiftType);
66
67        return $this->shiftEntriesResponse($shifts);
68    }
69
70    public function entriesByLocation(Request $request): Response
71    {
72        $locationId = (int) $request->getAttribute('location_id');
73        /** @var Location $location */
74        $location = Location::findOrFail($locationId);
75
76        // Needed by a shift directly
77        $shiftsByShift = $location->shifts();
78
79        // Needed by connected schedule via shift location
80        $shiftByScheduleLocation = Shift::query()
81            ->where('shifts.location_id', $location->id)
82            ->join('schedule_shift', 'schedule_shift.shift_id', 'shifts.id')
83            ->join('schedules', 'schedules.id', 'schedule_shift.schedule_id')
84            ->where('schedules.needed_from_shift_type', false)
85            ->join('needed_angel_types', 'needed_angel_types.location_id', 'shifts.location_id')
86            ->select('shifts.*');
87
88        // Needed by connected schedule via schedule shift type
89        $shiftsByScheduleShiftType = Shift::query()
90            ->where('shifts.location_id', $location->id)
91            ->join('schedule_shift', 'schedule_shift.shift_id', 'shifts.id')
92            ->join('schedules', 'schedules.id', 'schedule_shift.schedule_id')
93            ->where('schedules.needed_from_shift_type', true)
94            ->join('needed_angel_types', 'needed_angel_types.shift_type_id', 'schedules.shift_type')
95            ->select('shifts.*');
96
97        $shifts = $shiftsByShift
98            ->union($shiftByScheduleLocation)
99            ->union($shiftsByScheduleShiftType);
100
101        return $this->shiftEntriesResponse($shifts);
102    }
103
104    public function entriesByShiftType(Request $request): Response
105    {
106        $shiftTypeId = (int) $request->getAttribute('shifttype_id');
107        /** @var ShiftType $shiftType */
108        $shiftType = ShiftType::findOrFail($shiftTypeId);
109
110        // Needed by a shift directly
111        $shiftsByShift = $shiftType->shifts();
112
113        // Needed by connected schedule via shift location
114        $shiftsByScheduleLocation = Shift::query()
115            ->join('schedule_shift', 'schedule_shift.shift_id', 'shifts.id')
116            ->join('schedules', 'schedules.id', 'schedule_shift.schedule_id')
117            ->where('schedules.needed_from_shift_type', false)
118            ->where('schedules.shift_type', $shiftType->id)
119            ->join('needed_angel_types', 'needed_angel_types.location_id', 'shifts.location_id')
120            ->select('shifts.*');
121
122        // Needed by connected schedule via schedule shift type
123        $shiftsByScheduleShiftType = Shift::query()
124            ->join('schedule_shift', 'schedule_shift.shift_id', 'shifts.id')
125            ->join('schedules', 'schedules.id', 'schedule_shift.schedule_id')
126            ->where('schedules.needed_from_shift_type', true)
127            ->where('schedules.shift_type', $shiftType->id)
128            ->select('shifts.*');
129
130        $shifts = $shiftsByShift
131            ->union($shiftsByScheduleLocation)
132            ->union($shiftsByScheduleShiftType);
133
134        return $this->shiftEntriesResponse($shifts);
135    }
136
137    public function entriesByUser(Request $request): Response
138    {
139        $id = $request->getAttribute('user_id');
140        $user = $this->getUser($id);
141
142        $shifts = Shift::query()
143            ->join('shift_entries', 'shift_entries.shift_id', 'shifts.id')
144            ->where('shift_entries.user_id', $user->id)
145            ->groupBy('shifts.id')
146            ->select('shifts.*');
147
148        return $this->shiftEntriesResponse($shifts);
149    }
150
151    protected function shiftEntriesResponse(BuilderContract $shifts): Response
152    {
153        $shifts = $shifts
154            ->with([
155                'neededAngelTypes.angelType',
156                'location.neededAngelTypes.angelType',
157                'shiftEntries.angelType',
158                'shiftEntries.user.contact',
159                'shiftEntries.user.personalData',
160                'shiftType',
161                'scheduleShift',
162                'schedule.shiftType.neededAngelTypes.angelType',
163            ])
164            ->orderBy('start')
165            ->get();
166        /** @var Shift[]|Collection $shifts */
167
168        $shiftEntries = [];
169        // Blob of not-optimized mediocre pseudo-serialization
170        foreach ($shifts as $shift) {
171            // Get all needed/used angel types
172            /** @var Collection|NeededAngelType[] $neededAngelTypes */
173            $neededAngelTypes = $this->getNeededAngelTypes($shift);
174
175            if ($neededAngelTypes->isEmpty()) {
176                continue;
177            }
178
179            $angelTypes = new Collection();
180            foreach ($neededAngelTypes as $neededAngelType) {
181                $entries = $neededAngelType->entries ?: new Collection();
182
183                // Skip empty entries
184                if ($neededAngelType->count <= 0 && $entries->isEmpty()) {
185                    continue;
186                }
187
188                $entries = $entries->map(fn(ShiftEntry $entry) => [
189                    'user' => UserResource::toIdentifierArray($entry->user),
190                    'freeloaded_by' => $entry->freeloaded_by
191                        ? UserResource::toIdentifierArray($entry->freeloadedBy)
192                        : null,
193                ]);
194                $angelTypeData = AngelTypeResource::toIdentifierArray($neededAngelType->angelType);
195                $angelTypes[] = new Collection([
196                    'angel_type' => $angelTypeData,
197                    'needs' => $neededAngelType->count,
198                    'entries' => $entries,
199                ]);
200            }
201
202            $locationData = new LocationResource($shift->location);
203            $shiftEntries[] = (new ShiftWithEntriesResource($shift))->toArray($locationData, $angelTypes);
204        }
205
206        $data = ['data' => $shiftEntries];
207        return $this->response
208            ->withContent(json_encode($data));
209    }
210
211    /**
212     * Collect all needed angel types
213     */
214    protected function getNeededAngelTypes(Shift $shift): Collection
215    {
216        $neededAngelTypes = new Collection();
217        if (!$shift->schedule) {
218            // Get from shift
219            $neededAngelTypes = $shift->neededAngelTypes;
220        } elseif ($shift->schedule->needed_from_shift_type) {
221            // Load instead from shift type
222            $neededAngelTypes = $shift->schedule->shiftType->neededAngelTypes;
223        } elseif (!$shift->schedule->needed_from_shift_type) {
224            // Load instead from location
225            $neededAngelTypes = $shift->location->neededAngelTypes;
226        }
227
228        // Create new instances of needed angel types to allow extension with entries per shift
229        $neededAngelTypes = $neededAngelTypes->map(function ($value) {
230            return clone $value;
231        });
232
233        // Add entries and additional angel types from manually added users
234        foreach ($shift->shiftEntries as $entry) {
235            // Ensure that angel type exists in list; add it if not
236            $neededAngelType = $neededAngelTypes->where('angel_type_id', $entry->angelType->id)->first();
237            if (!$neededAngelType) {
238                $neededAngelType = new NeededAngelType([
239                    'shift_id' => $shift->id,
240                    'angel_type_id' => $entry->angelType->id,
241                    'count' => 0,
242                ]);
243                $neededAngelTypes[] = $neededAngelType;
244            }
245
246            // Initialize entries attribute for manually added users
247            if (!isset($neededAngelType->entries)) {
248                $neededAngelType->entries = new Collection();
249            }
250
251            // Add entries to needed angel type
252            $neededAngelType->entries[] = $entry;
253        }
254
255        return $neededAngelTypes;
256    }
257}