Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
82 / 82
100.00% covered (success)
100.00%
4 / 4
CRAP
100.00% covered (success)
100.00%
1 / 1
ShiftsController
100.00% covered (success)
100.00%
82 / 82
100.00% covered (success)
100.00%
4 / 4
6
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 random
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
2
 getNextFreeShifts
100.00% covered (success)
100.00%
58 / 58
100.00% covered (success)
100.00%
1 / 1
2
 queryShiftEntries
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare(strict_types=1);
4
5namespace Engelsystem\Controllers;
6
7use Engelsystem\Helpers\Authenticator;
8use Engelsystem\Helpers\Carbon;
9use Engelsystem\Http\Redirector;
10use Engelsystem\Http\Response;
11use Engelsystem\Http\UrlGeneratorInterface;
12use Engelsystem\Models\Shifts\Shift;
13use Engelsystem\Models\Shifts\ShiftEntry;
14use Engelsystem\Models\User\User;
15use Illuminate\Contracts\Database\Query\Builder as QueryBuilder;
16use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
17use Illuminate\Database\Eloquent\Collection as DbCollection;
18use Illuminate\Database\Query\Builder;
19use Illuminate\Database\Query\JoinClause;
20use Illuminate\Support\Collection;
21
22class ShiftsController extends BaseController
23{
24    use HasUserNotifications;
25
26    /** @var string[] */
27    protected array $permissions = [
28        'user_shifts',
29    ];
30
31    public function __construct(
32        protected Authenticator $auth,
33        protected Redirector $redirect,
34        protected UrlGeneratorInterface $url,
35    ) {
36    }
37
38    public function random(): Response
39    {
40        $user = $this->auth->user();
41        $nextFreeShifts = $this->getNextFreeShifts($user);
42
43        if ($nextFreeShifts->isEmpty()) {
44            $this->addNotification('notification.shift.no_next_found', NotificationType::WARNING);
45            return $this->redirect->to($this->url->to('/shifts'));
46        }
47
48        /** @var Shift $randomShift */
49        $randomShift = $nextFreeShifts
50            ->collect()
51            // Prefer soon starting shifts
52            ->groupBy('start')
53            // Get first starting shifts
54            ->first()
55            // Select one of them at random
56            ->random();
57
58        return $this->redirect->to($this->url->to('/shifts', ['action' => 'view', 'shift_id' => $randomShift->id]));
59    }
60
61    protected function getNextFreeShifts(User $user): Collection | DbCollection
62    {
63        $angelTypes = $user
64            ->userAngelTypes()
65            ->select('angel_types.id')
66            ->whereNested(function (Builder $query): void {
67                $query
68                    ->where('angel_types.restricted', false)
69                    ->orWhereNot('confirm_user_id', false);
70            })
71            ->pluck('id');
72        /** @var ShiftEntry[]|DbCollection $shiftEntries */
73        $shiftEntries = $user->shiftEntries()->with('shift')->get();
74
75        $freeShifts = Shift::query()
76            ->select('shifts.*')
77            // Load needed from shift if no schedule configured, else from room
78            ->leftJoin('schedule_shift', 'schedule_shift.shift_id', 'shifts.id')
79            ->leftJoin('schedules', 'schedules.id', 'schedule_shift.schedule_id')
80
81            // From shift
82            ->leftJoin('needed_angel_types', function (JoinClause $query): void {
83                $query->on('needed_angel_types.shift_id', 'shifts.id')
84                    ->whereNull('schedule_shift.shift_id');
85            })
86            // Via schedule shift type
87            ->leftJoin('needed_angel_types AS nast', function (JoinClause $query): void {
88                $query->on('nast.shift_type_id', 'shifts.shift_type_id')
89                    ->whereNotNull('schedule_shift.shift_id')
90                    ->where('schedules.needed_from_shift_type', true);
91            })
92            // Via schedule location
93            ->leftJoin('needed_angel_types AS nas', function (JoinClause $query): void {
94                $query->on('nas.location_id', 'shifts.location_id')
95                    ->whereNotNull('schedule_shift.shift_id')
96                    ->where('schedules.needed_from_shift_type', false);
97            })
98
99            // Not already signed in
100            ->whereNotIn('shifts.id', $shiftEntries->pluck('shift_id'))
101            // Same angel types
102            ->where(function (EloquentBuilder $query) use ($angelTypes): void {
103                $query
104                    ->whereIn('needed_angel_types.angel_type_id', $angelTypes)
105                    ->orWhereIn('nast.angel_type_id', $angelTypes)
106                    ->orWhereIn('nas.angel_type_id', $angelTypes);
107            })
108            // Starts soon
109            ->where('shifts.start', '>', Carbon::now())
110            // Where help needed
111            ->where(function (Builder $query): void {
112                $query
113                    ->from('shift_entries')
114                    ->selectRaw('COUNT(*)')
115                    ->where(fn(Builder $query) => $this->queryShiftEntries($query));
116            }, '<', Shift::query()->raw('COALESCE(needed_angel_types.count, nast.count, nas.count)'))
117            ->limit(10)
118            ->orderBy('start');
119
120        foreach ($shiftEntries as $entry) {
121            $freeShifts->where(function (QueryBuilder $query) use ($entry): void {
122                $query->where('end', '<=', $entry->shift->start);
123                $query->orWhere('start', '>=', $entry->shift->end);
124            });
125        }
126
127        return $freeShifts->get();
128    }
129
130    protected function queryShiftEntries(Builder $query): void
131    {
132        $query->select('id')
133            ->from('shift_entries')
134            ->where('shift_entries.shift_id', $query->raw('shifts.id'))
135            ->where(function (Builder $query): void {
136                $query->where('shift_entries.angel_type_id', $query->raw('needed_angel_types.angel_type_id'))
137                ->orWhere('shift_entries.angel_type_id', $query->raw('nas.angel_type_id'))
138                ->orWhere('shift_entries.angel_type_id', $query->raw('nast.angel_type_id'));
139            })
140            ->groupBy(['shift_entries.shift_id', 'shift_entries.angel_type_id']);
141    }
142}