Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
141 / 141
100.00% covered (success)
100.00%
6 / 6
CRAP
100.00% covered (success)
100.00%
1 / 1
ShiftsController
100.00% covered (success)
100.00%
141 / 141
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%
40 / 40
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                    'id' => $entry->id,
190                    'user' => UserResource::toIdentifierArray($entry->user),
191                    'freeloaded_by' => $entry->freeloaded_by
192                        ? UserResource::toIdentifierArray($entry->freeloadedBy)
193                        : null,
194                ]);
195                $angelTypeData = AngelTypeResource::toIdentifierArray($neededAngelType->angelType);
196                $angelTypes[] = new Collection([
197                    'angel_type' => $angelTypeData,
198                    'needs' => $neededAngelType->count,
199                    'entries' => $entries,
200                ]);
201            }
202
203            $locationData = new LocationResource($shift->location);
204            $shiftEntries[] = (new ShiftWithEntriesResource($shift))->toArray($locationData, $angelTypes);
205        }
206
207        $data = ['data' => $shiftEntries];
208        return $this->response
209            ->withContent(json_encode($data));
210    }
211
212    /**
213     * Collect all needed angel types
214     */
215    protected function getNeededAngelTypes(Shift $shift): Collection
216    {
217        $neededAngelTypes = new Collection();
218        if (!$shift->schedule) {
219            // Get from shift
220            $neededAngelTypes = $shift->neededAngelTypes;
221        } elseif ($shift->schedule->needed_from_shift_type) {
222            // Load instead from shift type
223            $neededAngelTypes = $shift->schedule->shiftType->neededAngelTypes;
224        } elseif (!$shift->schedule->needed_from_shift_type) {
225            // Load instead from location
226            $neededAngelTypes = $shift->location->neededAngelTypes;
227        }
228
229        // Create new instances of needed angel types to allow extension with entries per shift
230        $neededAngelTypes = $neededAngelTypes->map(function ($value) {
231            return clone $value;
232        });
233
234        // Add entries and additional angel types from manually added users
235        foreach ($shift->shiftEntries as $entry) {
236            // Ensure that angel type exists in list; add it if not
237            $neededAngelType = $neededAngelTypes->where('angel_type_id', $entry->angelType->id)->first();
238            if (!$neededAngelType) {
239                $neededAngelType = new NeededAngelType([
240                    'shift_id' => $shift->id,
241                    'angel_type_id' => $entry->angelType->id,
242                    'count' => 0,
243                ]);
244                $neededAngelTypes[] = $neededAngelType;
245            }
246
247            // Initialize entries attribute for manually added users
248            if (!isset($neededAngelType->entries)) {
249                $neededAngelType->entries = new Collection();
250            }
251
252            // Add entries to needed angel type
253            $neededAngelType->entries[] = $entry;
254        }
255
256        return $neededAngelTypes;
257    }
258}