Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
112 / 112
100.00% covered (success)
100.00%
6 / 6
CRAP
100.00% covered (success)
100.00%
1 / 1
ShiftTypesController
100.00% covered (success)
100.00%
112 / 112
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%
35 / 35
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                'tags',
90                'shiftEntries.user.personalData',
91                'shiftEntries.user.state',
92                'shiftEntries.angelType',
93                'shiftType.neededAngelTypes.angelType',
94                'location.neededAngelTypes.angelType',
95            ])
96            ->whereDate('start', $day)
97            ->orderBy('start')
98            ->get();
99
100        return $this->response->withView(
101            'admin/shifttypes/view',
102            [
103                'shifttype' => $shiftType,
104                'is_view' => true,
105                'shifts_active' => $request->has('shifts') || $request->get('day'),
106                'days' => $days,
107                'selected_day' => $day,
108                'shifts' => $shifts,
109            ]
110        );
111    }
112
113    public function save(Request $request): Response
114    {
115        $shiftTypeId = (int) $request->getAttribute('shift_type_id');
116
117        /** @var ShiftType $shiftType */
118        $shiftType = $this->shiftType->findOrNew($shiftTypeId);
119
120        if ($request->request->has('delete')) {
121            return $this->delete($request);
122        }
123
124        /** @var Collection|AngelType[] $angelTypes */
125        $angelTypes = AngelType::all();
126        $validation = [];
127        foreach ($angelTypes as $angelType) {
128            $validation['angel_type_' . $angelType->id] = 'optional|int';
129        }
130
131        $data = $this->validate(
132            $request,
133            [
134                'name' => 'required|max:255',
135                'description' => 'optional',
136                'signup_advance_hours' => 'optional|float',
137            ] + $validation
138        );
139
140        if (ShiftType::whereName($data['name'])->where('id', '!=', $shiftType->id)->exists()) {
141            throw new ValidationException((new Validator())->addErrors(['name' => ['validation.name.exists']]));
142        }
143
144        $shiftType->name = $data['name'];
145        $shiftType->description = $data['description'] ?? '';
146        $shiftType->signup_advance_hours = $data['signup_advance_hours'] ?: null;
147
148        $shiftType->save();
149        $shiftType->neededAngelTypes()->delete();
150
151        // Associate angel types with the shift type
152        $angelsInfo = '';
153        foreach ($angelTypes as $angelType) {
154            $count = $data['angel_type_' . $angelType->id];
155            if (!$count) {
156                continue;
157            }
158
159            $neededAngelType = new NeededAngelType();
160
161            $neededAngelType->shiftType()->associate($shiftType);
162            $neededAngelType->angelType()->associate($angelType);
163
164            $neededAngelType->count = $data['angel_type_' . $angelType->id];
165
166            $neededAngelType->save();
167
168            $angelsInfo .= sprintf(', %s: %s', $angelType->name, $count);
169        }
170
171        $this->log->info(
172            'Saved shift type "{name}" ({id}): {description}, {signup_advance_hours}, {angels}',
173            [
174                'id' => $shiftType->id,
175                'name' => $shiftType->name,
176                'description' => $shiftType->description,
177                'signup_advance_hours' => $shiftType->signup_advance_hours,
178                'angels' => $angelsInfo,
179            ]
180        );
181
182        $this->addNotification('shifttype.edit.success');
183
184        return $this->redirect->to('/admin/shifttypes');
185    }
186
187    public function delete(Request $request): Response
188    {
189        $data = $this->validate($request, [
190            'id' => 'required|int',
191            'delete' => 'checked',
192        ]);
193
194        $shiftType = $this->shiftType->findOrFail($data['id']);
195
196        $shifts = $shiftType->shifts;
197        foreach ($shifts as $shift) {
198            event('shift.deleting', ['shift' => $shift]);
199        }
200        $shiftType->delete();
201
202        $this->log->info('Deleted shift type {name} ({id})', ['name' => $shiftType->name, 'id' => $shiftType->id]);
203        $this->addNotification('shifttype.delete.success');
204
205        return $this->redirect->to('/admin/shifttypes');
206    }
207}