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 | } |