feat(api): add upcoming_shift to portal me endpoint
Query next approved shift assignment with future time slot, ordered by date and start time, and return formatted shift data in the portal me response for the dashboard "Komende shift" card. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
269
api/tests/Feature/Api/V1/PortalMeUpcomingShiftTest.php
Normal file
269
api/tests/Feature/Api/V1/PortalMeUpcomingShiftTest.php
Normal file
@@ -0,0 +1,269 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Feature\Api\V1;
|
||||
|
||||
use App\Enums\ShiftAssignmentStatus;
|
||||
use App\Models\CrowdType;
|
||||
use App\Models\Event;
|
||||
use App\Models\FestivalSection;
|
||||
use App\Models\Location;
|
||||
use App\Models\Organisation;
|
||||
use App\Models\Person;
|
||||
use App\Models\Shift;
|
||||
use App\Models\ShiftAssignment;
|
||||
use App\Models\TimeSlot;
|
||||
use App\Models\User;
|
||||
use Database\Seeders\RoleSeeder;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Laravel\Sanctum\Sanctum;
|
||||
use Tests\TestCase;
|
||||
|
||||
class PortalMeUpcomingShiftTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
private Organisation $organisation;
|
||||
private Event $event;
|
||||
private CrowdType $volunteerCrowdType;
|
||||
private User $user;
|
||||
private Person $person;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->seed(RoleSeeder::class);
|
||||
|
||||
$this->organisation = Organisation::factory()->create();
|
||||
$this->volunteerCrowdType = CrowdType::factory()->systemType('VOLUNTEER')->create([
|
||||
'organisation_id' => $this->organisation->id,
|
||||
]);
|
||||
$this->event = Event::factory()->create([
|
||||
'organisation_id' => $this->organisation->id,
|
||||
'status' => 'published',
|
||||
]);
|
||||
$this->user = User::factory()->create();
|
||||
$this->person = Person::factory()->approved()->create([
|
||||
'event_id' => $this->event->id,
|
||||
'crowd_type_id' => $this->volunteerCrowdType->id,
|
||||
'user_id' => $this->user->id,
|
||||
'email' => $this->user->email,
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_portal_me_returns_upcoming_shift_for_approved_person(): void
|
||||
{
|
||||
$futureDate = now()->addDays(7)->toDateString();
|
||||
|
||||
$timeSlot = TimeSlot::factory()->create([
|
||||
'event_id' => $this->event->id,
|
||||
'date' => $futureDate,
|
||||
'start_time' => '18:00:00',
|
||||
'end_time' => '02:00:00',
|
||||
]);
|
||||
|
||||
$section = FestivalSection::factory()->create([
|
||||
'event_id' => $this->event->id,
|
||||
'name' => 'Bar Hoofdpodium',
|
||||
]);
|
||||
|
||||
$location = Location::factory()->create([
|
||||
'event_id' => $this->event->id,
|
||||
'name' => 'Festivalterrein',
|
||||
]);
|
||||
|
||||
$shift = Shift::factory()->create([
|
||||
'festival_section_id' => $section->id,
|
||||
'time_slot_id' => $timeSlot->id,
|
||||
'location_id' => $location->id,
|
||||
'title' => 'Tapper',
|
||||
]);
|
||||
|
||||
ShiftAssignment::factory()->approved()->create([
|
||||
'shift_id' => $shift->id,
|
||||
'person_id' => $this->person->id,
|
||||
'time_slot_id' => $timeSlot->id,
|
||||
]);
|
||||
|
||||
Sanctum::actingAs($this->user);
|
||||
|
||||
$response = $this->getJson("/api/v1/portal/me?event_id={$this->event->id}");
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertJsonPath('data.upcoming_shift.date', $futureDate);
|
||||
$response->assertJsonPath('data.upcoming_shift.time', '18:00 - 02:00');
|
||||
$response->assertJsonPath('data.upcoming_shift.title', 'Tapper');
|
||||
$response->assertJsonPath('data.upcoming_shift.section', 'Bar Hoofdpodium');
|
||||
$response->assertJsonPath('data.upcoming_shift.location', 'Festivalterrein');
|
||||
}
|
||||
|
||||
public function test_portal_me_returns_null_upcoming_shift_when_no_future_shifts(): void
|
||||
{
|
||||
// Create a past shift assignment
|
||||
$pastDate = now()->subDays(7)->toDateString();
|
||||
|
||||
$timeSlot = TimeSlot::factory()->create([
|
||||
'event_id' => $this->event->id,
|
||||
'date' => $pastDate,
|
||||
'start_time' => '18:00:00',
|
||||
'end_time' => '02:00:00',
|
||||
]);
|
||||
|
||||
$section = FestivalSection::factory()->create([
|
||||
'event_id' => $this->event->id,
|
||||
]);
|
||||
|
||||
$shift = Shift::factory()->create([
|
||||
'festival_section_id' => $section->id,
|
||||
'time_slot_id' => $timeSlot->id,
|
||||
'title' => 'Tapper',
|
||||
]);
|
||||
|
||||
ShiftAssignment::factory()->approved()->create([
|
||||
'shift_id' => $shift->id,
|
||||
'person_id' => $this->person->id,
|
||||
'time_slot_id' => $timeSlot->id,
|
||||
]);
|
||||
|
||||
Sanctum::actingAs($this->user);
|
||||
|
||||
$response = $this->getJson("/api/v1/portal/me?event_id={$this->event->id}");
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertJsonPath('data.upcoming_shift', null);
|
||||
}
|
||||
|
||||
public function test_portal_me_returns_null_upcoming_shift_for_pending_person(): void
|
||||
{
|
||||
$pendingPerson = Person::factory()->create([
|
||||
'event_id' => $this->event->id,
|
||||
'crowd_type_id' => $this->volunteerCrowdType->id,
|
||||
'user_id' => null,
|
||||
'status' => 'pending',
|
||||
]);
|
||||
|
||||
$pendingUser = User::factory()->create(['email' => $pendingPerson->email]);
|
||||
$pendingPerson->update(['user_id' => $pendingUser->id]);
|
||||
|
||||
$futureDate = now()->addDays(7)->toDateString();
|
||||
|
||||
$timeSlot = TimeSlot::factory()->create([
|
||||
'event_id' => $this->event->id,
|
||||
'date' => $futureDate,
|
||||
]);
|
||||
|
||||
$section = FestivalSection::factory()->create([
|
||||
'event_id' => $this->event->id,
|
||||
]);
|
||||
|
||||
$shift = Shift::factory()->create([
|
||||
'festival_section_id' => $section->id,
|
||||
'time_slot_id' => $timeSlot->id,
|
||||
]);
|
||||
|
||||
// Assignment is pending_approval, not approved
|
||||
ShiftAssignment::factory()->create([
|
||||
'shift_id' => $shift->id,
|
||||
'person_id' => $pendingPerson->id,
|
||||
'time_slot_id' => $timeSlot->id,
|
||||
'status' => ShiftAssignmentStatus::PENDING_APPROVAL,
|
||||
]);
|
||||
|
||||
Sanctum::actingAs($pendingUser);
|
||||
|
||||
$response = $this->getJson("/api/v1/portal/me?event_id={$this->event->id}");
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertJsonPath('data.upcoming_shift', null);
|
||||
}
|
||||
|
||||
public function test_portal_me_returns_nearest_future_shift(): void
|
||||
{
|
||||
$nearDate = now()->addDays(3)->toDateString();
|
||||
$farDate = now()->addDays(10)->toDateString();
|
||||
|
||||
$nearTimeSlot = TimeSlot::factory()->create([
|
||||
'event_id' => $this->event->id,
|
||||
'date' => $nearDate,
|
||||
'start_time' => '10:00:00',
|
||||
'end_time' => '18:00:00',
|
||||
]);
|
||||
|
||||
$farTimeSlot = TimeSlot::factory()->create([
|
||||
'event_id' => $this->event->id,
|
||||
'date' => $farDate,
|
||||
'start_time' => '18:00:00',
|
||||
'end_time' => '02:00:00',
|
||||
]);
|
||||
|
||||
$section = FestivalSection::factory()->create([
|
||||
'event_id' => $this->event->id,
|
||||
]);
|
||||
|
||||
$nearShift = Shift::factory()->create([
|
||||
'festival_section_id' => $section->id,
|
||||
'time_slot_id' => $nearTimeSlot->id,
|
||||
'title' => 'Ochtend Runner',
|
||||
]);
|
||||
|
||||
$farShift = Shift::factory()->create([
|
||||
'festival_section_id' => $section->id,
|
||||
'time_slot_id' => $farTimeSlot->id,
|
||||
'title' => 'Avond Tapper',
|
||||
]);
|
||||
|
||||
ShiftAssignment::factory()->approved()->create([
|
||||
'shift_id' => $farShift->id,
|
||||
'person_id' => $this->person->id,
|
||||
'time_slot_id' => $farTimeSlot->id,
|
||||
]);
|
||||
|
||||
ShiftAssignment::factory()->approved()->create([
|
||||
'shift_id' => $nearShift->id,
|
||||
'person_id' => $this->person->id,
|
||||
'time_slot_id' => $nearTimeSlot->id,
|
||||
]);
|
||||
|
||||
Sanctum::actingAs($this->user);
|
||||
|
||||
$response = $this->getJson("/api/v1/portal/me?event_id={$this->event->id}");
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertJsonPath('data.upcoming_shift.title', 'Ochtend Runner');
|
||||
$response->assertJsonPath('data.upcoming_shift.date', $nearDate);
|
||||
}
|
||||
|
||||
public function test_portal_me_ignores_cancelled_shift_assignments(): void
|
||||
{
|
||||
$futureDate = now()->addDays(7)->toDateString();
|
||||
|
||||
$timeSlot = TimeSlot::factory()->create([
|
||||
'event_id' => $this->event->id,
|
||||
'date' => $futureDate,
|
||||
]);
|
||||
|
||||
$section = FestivalSection::factory()->create([
|
||||
'event_id' => $this->event->id,
|
||||
]);
|
||||
|
||||
$shift = Shift::factory()->create([
|
||||
'festival_section_id' => $section->id,
|
||||
'time_slot_id' => $timeSlot->id,
|
||||
]);
|
||||
|
||||
ShiftAssignment::factory()->create([
|
||||
'shift_id' => $shift->id,
|
||||
'person_id' => $this->person->id,
|
||||
'time_slot_id' => $timeSlot->id,
|
||||
'status' => ShiftAssignmentStatus::CANCELLED,
|
||||
]);
|
||||
|
||||
Sanctum::actingAs($this->user);
|
||||
|
||||
$response = $this->getJson("/api/v1/portal/me?event_id={$this->event->id}");
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertJsonPath('data.upcoming_shift', null);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user