Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
100.00% |
82 / 82 |
|
100.00% |
4 / 4 |
CRAP | |
100.00% |
1 / 1 |
ShiftsController | |
100.00% |
82 / 82 |
|
100.00% |
4 / 4 |
6 | |
100.00% |
1 / 1 |
__construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
random | |
100.00% |
14 / 14 |
|
100.00% |
1 / 1 |
2 | |||
getNextFreeShifts | |
100.00% |
58 / 58 |
|
100.00% |
1 / 1 |
2 | |||
queryShiftEntries | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
1 |
1 | <?php |
2 | |
3 | declare(strict_types=1); |
4 | |
5 | namespace Engelsystem\Controllers; |
6 | |
7 | use Engelsystem\Helpers\Authenticator; |
8 | use Engelsystem\Helpers\Carbon; |
9 | use Engelsystem\Http\Redirector; |
10 | use Engelsystem\Http\Response; |
11 | use Engelsystem\Http\UrlGeneratorInterface; |
12 | use Engelsystem\Models\Shifts\Shift; |
13 | use Engelsystem\Models\Shifts\ShiftEntry; |
14 | use Engelsystem\Models\User\User; |
15 | use Illuminate\Contracts\Database\Query\Builder as QueryBuilder; |
16 | use Illuminate\Database\Eloquent\Builder as EloquentBuilder; |
17 | use Illuminate\Database\Eloquent\Collection as DbCollection; |
18 | use Illuminate\Database\Query\Builder; |
19 | use Illuminate\Database\Query\JoinClause; |
20 | use Illuminate\Support\Collection; |
21 | |
22 | class 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 | } |