API: Show needed/added users by angeltype in shifts

This commit is contained in:
Igor Scheller 2023-07-29 21:37:46 +02:00 committed by Michael Weimann
parent ea93e27a9d
commit ef3bd7c319
3 changed files with 137 additions and 28 deletions

View File

@ -167,10 +167,6 @@ components:
format: date-time format: date-time
description: DateTime in ISO-8601 format description: DateTime in ISO-8601 format
example: 2023-05-13T16:00:00.000000Z23 example: 2023-05-13T16:00:00.000000Z23
entries:
type: array
items:
$ref: '#/components/schemas/ShiftEntry'
room: room:
$ref: '#/components/schemas/Room' $ref: '#/components/schemas/Room'
shift_type: shift_type:
@ -187,6 +183,11 @@ components:
format: date-time format: date-time
description: DateTime in ISO-8601 format description: DateTime in ISO-8601 format
example: 2023-05-13T23:00:00.000000Z example: 2023-05-13T23:00:00.000000Z
entries:
type: array
description: Can be empty (for example on Schedule import of unused room)
items:
$ref: '#/components/schemas/ShiftEntry'
url: url:
type: string type: string
example: https://example.com/shifts/42 example: https://example.com/shifts/42
@ -196,22 +197,31 @@ components:
- description - description
- start - start
- end - end
- entries
- room - room
- shift_type - shift_type
- created_at - created_at
- updated_at - updated_at
- entries
- url - url
ShiftEntry: ShiftEntry:
type: object type: object
properties: properties:
user: users:
$ref: '#/components/schemas/User' type: array
items:
$ref: '#/components/schemas/User'
type: type:
$ref: '#/components/schemas/AngelType' $ref: '#/components/schemas/AngelType'
needs:
type: integer
description: |
Number of users needed for the shift.
Can be more than users count when not full, less when overbooked or 0 when additional users have been added.
example: 3
required: required:
- user - users
- type - type
- needs
ShiftType: ShiftType:
type: object type: object
properties: properties:

View File

@ -7,6 +7,9 @@ namespace Engelsystem\Controllers\Api;
use Engelsystem\Http\Request; use Engelsystem\Http\Request;
use Engelsystem\Http\Response; use Engelsystem\Http\Response;
use Engelsystem\Models\Room; use Engelsystem\Models\Room;
use Engelsystem\Models\Shifts\NeededAngelType;
use Engelsystem\Models\Shifts\Shift;
use Illuminate\Database\Eloquent\Collection;
class ShiftsController extends ApiController class ShiftsController extends ApiController
{ {
@ -15,40 +18,55 @@ class ShiftsController extends ApiController
$roomId = (int) $request->getAttribute('room_id'); $roomId = (int) $request->getAttribute('room_id');
/** @var Room $room */ /** @var Room $room */
$room = Room::findOrFail($roomId); $room = Room::findOrFail($roomId);
/** @var Shift[]|Collection $shifts */
$shifts = $room->shifts() $shifts = $room->shifts()
->with([ ->with([
'neededAngelTypes.angelType',
'room',
'shiftEntries.angelType', 'shiftEntries.angelType',
'shiftEntries.user.contact', 'shiftEntries.user.contact',
'shiftEntries.user.personalData', 'shiftEntries.user.personalData',
'shiftType', 'shiftType',
]) ])
->orderBy('start')
->get(); ->get();
$shiftEntries = [];
$shiftEntries = [];
// Blob of not-optimized mediocre pseudo-serialization // Blob of not-optimized mediocre pseudo-serialization
foreach ($shifts as $shift) { foreach ($shifts as $shift) {
$entries = []; // Get all needed/used angel types
foreach ($shift->shiftEntries as $entry) { $neededAngelTypes = $this->getNeededAngelTypes($shift);
$user = $entry->user;
$userData = [
'id' => $user->id,
'name' => $user->name,
'first_name' => $user->personalData->first_name,
'last_name' => $user->personalData->last_name,
'pronoun' => $user->personalData->pronoun,
'contact' => $user->contact->only(['dect', 'mobile']),
'url' => $this->url->to('/users', ['action' => 'view', 'user_id' => $user->id]),
];
$angelTypeData = $entry->angelType->only(['id', 'name']); $entries = new Collection();
foreach ($neededAngelTypes as $neededAngelType) {
$users = [];
foreach ($neededAngelType->users ?? [] as $user) {
$users[] = [
'id' => $user->id,
'name' => $user->name,
'first_name' => $user->personalData->first_name,
'last_name' => $user->personalData->last_name,
'pronoun' => $user->personalData->pronoun,
'contact' => $user->contact->only(['dect', 'mobile']),
'url' => $this->url->to('/users', ['action' => 'view', 'user_id' => $user->id]),
];
}
// Skip empty entries
if ($neededAngelType->count <= 0 && empty($users)) {
continue;
}
$angelTypeData = $neededAngelType->angelType->only(['id', 'name', 'description']);
$angelTypeData['url'] = $this->url->to( $angelTypeData['url'] = $this->url->to(
'/angeltypes', '/angeltypes',
['action' => 'view', 'angeltype_id' => $entry->angelType->id] ['action' => 'view', 'angeltype_id' => $neededAngelType->angelType->id]
); );
$entries[] = [ $entries[] = [
'user' => $userData, 'users' => $users,
'type' => $angelTypeData, 'type' => $angelTypeData,
'needs' => $neededAngelType->count,
]; ];
} }
@ -61,11 +79,11 @@ class ShiftsController extends ApiController
'description' => $shift->description, 'description' => $shift->description,
'start' => $shift->start, 'start' => $shift->start,
'end' => $shift->end, 'end' => $shift->end,
'entries' => $entries,
'room' => $roomData, 'room' => $roomData,
'shift_type' => $shift->shiftType->only(['id', 'name', 'description']), 'shift_type' => $shift->shiftType->only(['id', 'name', 'description']),
'created_at' => $shift->created_at, 'created_at' => $shift->created_at,
'updated_at' => $shift->updated_at, 'updated_at' => $shift->updated_at,
'entries' => $entries,
'url' => $this->url->to('/shifts', ['action' => 'view', 'shift_id' => $shift->id]), 'url' => $this->url->to('/shifts', ['action' => 'view', 'shift_id' => $shift->id]),
]; ];
} }
@ -74,4 +92,49 @@ class ShiftsController extends ApiController
return $this->response return $this->response
->withContent(json_encode($data)); ->withContent(json_encode($data));
} }
/**
* Collect all needed angeltypes
*/
protected function getNeededAngelTypes(Shift $shift): Collection
{
// From shift
$neededAngelTypes = $shift->neededAngelTypes;
// Add from room
foreach ($shift->room->neededAngelTypes as $neededAngelType) {
/** @var NeededAngelType $existingNeededAngelType */
$existingNeededAngelType = $neededAngelTypes
->where('angel_type_id', $neededAngelType->angel_type_id)
->first();
if (!$existingNeededAngelType) {
$neededAngelTypes[] = clone $neededAngelType;
continue;
}
$existingNeededAngelType->room_id = $shift->room->id;
$existingNeededAngelType->count += $neededAngelType->count;
}
// Add needed angeltypes from additionally added users
foreach ($shift->shiftEntries as $entry) {
$neededAngelType = $neededAngelTypes->where('angel_type_id', $entry->angelType->id)->first();
if (!$neededAngelType) {
$neededAngelType = new NeededAngelType([
'shift_id' => $shift->id,
'angel_type_id' => $entry->angelType->id,
'count' => 0,
]);
$neededAngelTypes[] = $neededAngelType;
}
// Add users to entries
$neededAngelType->users = isset($neededAngelType->users)
? $neededAngelType->users
: new Collection();
$neededAngelType->users[] = $entry->user;
}
return $neededAngelTypes;
}
} }

View File

@ -8,16 +8,19 @@ use Engelsystem\Controllers\Api\ShiftsController;
use Engelsystem\Http\Request; use Engelsystem\Http\Request;
use Engelsystem\Http\Response; use Engelsystem\Http\Response;
use Engelsystem\Models\Room; use Engelsystem\Models\Room;
use Engelsystem\Models\Shifts\NeededAngelType;
use Engelsystem\Models\Shifts\Shift; use Engelsystem\Models\Shifts\Shift;
use Engelsystem\Models\Shifts\ShiftEntry; use Engelsystem\Models\Shifts\ShiftEntry;
use Engelsystem\Models\User\Contact; use Engelsystem\Models\User\Contact;
use Engelsystem\Models\User\PersonalData; use Engelsystem\Models\User\PersonalData;
use Engelsystem\Models\User\User; use Engelsystem\Models\User\User;
use Illuminate\Database\Eloquent\Collection;
class ShiftsControllerTest extends ApiBaseControllerTest class ShiftsControllerTest extends ApiBaseControllerTest
{ {
/** /**
* @covers \Engelsystem\Controllers\Api\ShiftsController::entriesByRoom * @covers \Engelsystem\Controllers\Api\ShiftsController::entriesByRoom
* @covers \Engelsystem\Controllers\Api\ShiftsController::getNeededAngelTypes
*/ */
public function testEntriesByRoom(): void public function testEntriesByRoom(): void
{ {
@ -26,9 +29,42 @@ class ShiftsControllerTest extends ApiBaseControllerTest
/** @var Room $room */ /** @var Room $room */
$room = Room::factory()->create(); $room = Room::factory()->create();
Shift::factory(3) // Shifts
->has(ShiftEntry::factory(2), 'shiftEntries') /** @var Collection|Shift[] $shifts */
$shifts = Shift::factory(2)
->create(['room_id' => $room->id]); ->create(['room_id' => $room->id]);
$shiftA = $shifts[0];
// "Empty" entry to be skipped
NeededAngelType::factory(1)->create(['room_id' => null, 'shift_id' => $shiftA->id, 'count' => 0]);
// Needed entry by shift
/** @var NeededAngelType $byShift */
$byShift = NeededAngelType::factory(2)
->create(['room_id' => null, 'shift_id' => $shiftA->id, 'count' => 2])
->first();
// Needed entry by room
/** @var NeededAngelType $byRoom */
$byRoom = NeededAngelType::factory(1)
->create(['room_id' => $room->id, 'shift_id' => null, 'count' => 3])
->first();
// Added by both
NeededAngelType::factory(1)
->create([
'room_id' => $room->id, 'shift_id' => null, 'angel_type_id' => $byShift->angel_type_id, 'count' => 3,
])
->first();
// By shift
ShiftEntry::factory(2)->create(['shift_id' => $shiftA->id, 'angel_type_id' => $byShift->angel_type_id]);
// By room
ShiftEntry::factory(1)->create(['shift_id' => $shiftA->id, 'angel_type_id' => $byRoom->angel_type_id]);
// Additional (not required by shift nor room)
ShiftEntry::factory(1)->create(['shift_id' => $shiftA->id]);
foreach (User::all() as $user) { foreach (User::all() as $user) {
// Generate user data // Generate user data
@ -50,6 +86,6 @@ class ShiftsControllerTest extends ApiBaseControllerTest
$data = json_decode($response->getContent(), true); $data = json_decode($response->getContent(), true);
$this->assertArrayHasKey('data', $data); $this->assertArrayHasKey('data', $data);
$this->assertCount(3, $data['data']); $this->assertCount(2, $data['data']);
} }
} }