Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
111 / 111
100.00% covered (success)
100.00%
6 / 6
CRAP
100.00% covered (success)
100.00%
1 / 1
ShiftTypesController
100.00% covered (success)
100.00%
111 / 111
100.00% covered (success)
100.00%
6 / 6
15
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
 index
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 edit
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
1
 view
100.00% covered (success)
100.00%
34 / 34
100.00% covered (success)
100.00%
1 / 1
3
 save
100.00% covered (success)
100.00%
46 / 46
100.00% covered (success)
100.00%
1 / 1
7
 delete
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3declare(strict_types=1);
4
5namespace Engelsystem\Controllers\Admin;
6
7use Engelsystem\Controllers\BaseController;
8use Engelsystem\Controllers\HasUserNotifications;
9use Engelsystem\Http\Exceptions\ValidationException;
10use Engelsystem\Http\Redirector;
11use Engelsystem\Http\Request;
12use Engelsystem\Http\Response;
13use Engelsystem\Http\Validation\Validator;
14use Engelsystem\Models\AngelType;
15use Engelsystem\Models\Shifts\NeededAngelType;
16use Engelsystem\Models\Shifts\ShiftType;
17use Illuminate\Database\Eloquent\Collection;
18use Psr\Log\LoggerInterface;
19
20class ShiftTypesController extends BaseController
21{
22    use HasUserNotifications;
23
24    /** @var array<string> */
25    protected array $permissions = [
26        'shifttypes.view',
27        'edit' => 'shifttypes.edit',
28        'delete' => 'shifttypes.edit',
29        'save' => 'shifttypes.edit',
30    ];
31
32    public function __construct(
33        protected LoggerInterface $log,
34        protected ShiftType $shiftType,
35        protected Redirector $redirect,
36        protected Response $response
37    ) {
38    }
39
40    public function index(): Response
41    {
42        $shiftTypes = $this->shiftType
43            ->get()
44            ->sortBy('name', SORT_NATURAL | SORT_FLAG_CASE);
45
46        return $this->response->withView(
47            'admin/shifttypes/index',
48            ['shifttypes' => $shiftTypes, 'is_index' => true]
49        );
50    }
51
52    public function edit(Request $request): Response
53    {
54        $shiftTypeId = (int) $request->getAttribute('shift_type_id');
55
56        $shiftType = $this->shiftType->find($shiftTypeId);
57        $angeltypes = AngelType::all()
58            ->sortBy('name');
59
60        return $this->response->withView(
61            'admin/shifttypes/edit',
62            [
63                'shifttype' => $shiftType,
64                'angel_types' => $angeltypes,
65            ]
66        );
67    }
68
69    public function view(Request $request): Response
70    {
71        $shiftTypeId = (int) $request->getAttribute('shift_type_id');
72        /** @var ShiftType $shiftType */
73        $shiftType = $this->shiftType->findOrFail($shiftTypeId);
74
75        $days = $shiftType->shifts()
76            ->scopes('needsUsers')
77            ->selectRaw('DATE(start) AS date')
78            ->orderBy('date')
79            ->groupBy('date')
80            ->pluck('date');
81
82        $day = $request->get('day');
83        $day = $days->contains($day) ? $day : $days->first();
84
85        $shifts = $shiftType->shifts()
86            ->with([
87                'neededAngelTypes.angelType',
88                'schedule',
89                'shiftEntries.user.personalData',
90                'shiftEntries.user.state',
91                'shiftEntries.angelType',
92                'shiftType.neededAngelTypes.angelType',
93                'location.neededAngelTypes.angelType',
94            ])
95            ->whereDate('start', $day)
96            ->orderBy('start')
97            ->get();
98
99        return $this->response->withView(
100            'admin/shifttypes/view',
101            [
102                'shifttype' => $shiftType,
103                'is_view' => true,
104                'shifts_active' => $request->has('shifts') || $request->get('day'),
105                'days' => $days,
106                'selected_day' => $day,
107                'shifts' => $shifts,
108            ]
109        );
110    }
111
112    public function save(Request $request): Response
113    {
114        $shiftTypeId = (int) $request->getAttribute('shift_type_id');
115
116        /** @var ShiftType $shiftType */
117        $shiftType = $this->shiftType->findOrNew($shiftTypeId);
118
119        if ($request->request->has('delete')) {
120            return $this->delete($request);
121        }
122
123        /** @var Collection|AngelType[] $angelTypes */
124        $angelTypes = AngelType::all();
125        $validation = [];
126        foreach ($angelTypes as $angelType) {
127            $validation['angel_type_' . $angelType->id] = 'optional|int';
128        }
129
130        $data = $this->validate(
131            $request,
132            [
133                'name' => 'required|max:255',
134                'description' => 'optional',
135                'signup_advance_hours' => 'optional|float',
136            ] + $validation
137        );
138
139        if (ShiftType::whereName($data['name'])->where('id', '!=', $shiftType->id)->exists()) {
140            throw new ValidationException((new Validator())->addErrors(['name' => ['validation.name.exists']]));
141        }
142
143        $shiftType->name = $data['name'];
144        $shiftType->description = $data['description'] ?? '';
145        $shiftType->signup_advance_hours = $data['signup_advance_hours'] ?: null;
146
147        $shiftType->save();
148        $shiftType->neededAngelTypes()->delete();
149
150        // Associate angel types with the shift type
151        $angelsInfo = '';
152        foreach ($angelTypes as $angelType) {
153            $count = $data['angel_type_' . $angelType->id];
154            if (!$count) {
155                continue;
156            }
157
158            $neededAngelType = new NeededAngelType();
159
160            $neededAngelType->shiftType()->associate($shiftType);
161            $neededAngelType->angelType()->associate($angelType);
162
163            $neededAngelType->count = $data['angel_type_' . $angelType->id];
164
165            $neededAngelType->save();
166
167            $angelsInfo .= sprintf(', %s: %s', $angelType->name, $count);
168        }
169
170        $this->log->info(
171            'Saved shift type "{name}" ({id}): {description}, {signup_advance_hours}, {angels}',
172            [
173                'id' => $shiftType->id,
174                'name' => $shiftType->name,
175                'description' => $shiftType->description,
176                'signup_advance_hours' => $shiftType->signup_advance_hours,
177                'angels' => $angelsInfo,
178            ]
179        );
180
181        $this->addNotification('shifttype.edit.success');
182
183        return $this->redirect->to('/admin/shifttypes');
184    }
185
186    public function delete(Request $request): Response
187    {
188        $data = $this->validate($request, [
189            'id' => 'required|int',
190            'delete' => 'checked',
191        ]);
192
193        $shiftType = $this->shiftType->findOrFail($data['id']);
194
195        $shifts = $shiftType->shifts;
196        foreach ($shifts as $shift) {
197            event('shift.deleting', ['shift' => $shift]);
198        }
199        $shiftType->delete();
200
201        $this->log->info('Deleted shift type {name} ({id})', ['name' => $shiftType->name, 'id' => $shiftType->id]);
202        $this->addNotification('shifttype.delete.success');
203
204        return $this->redirect->to('/admin/shifttypes');
205    }
206}