feat: fase 2 backend — crowd types, persons, sections, shifts, invite flow
- Crowd Types + Persons CRUD (73 tests) - Festival Sections + Time Slots + Shifts CRUD met assign/claim flow (84 tests) - Invite Flow + Member Management met InvitationService (109 tests) - Schema v1.6 migraties volledig uitgevoerd - DevSeeder bijgewerkt met crowd types voor testorganisatie
This commit is contained in:
25
api/app/Http/Resources/Api/V1/CompanyResource.php
Normal file
25
api/app/Http/Resources/Api/V1/CompanyResource.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Resources\Api\V1;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
final class CompanyResource extends JsonResource
|
||||
{
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'organisation_id' => $this->organisation_id,
|
||||
'name' => $this->name,
|
||||
'type' => $this->type,
|
||||
'contact_name' => $this->contact_name,
|
||||
'contact_email' => $this->contact_email,
|
||||
'contact_phone' => $this->contact_phone,
|
||||
'created_at' => $this->created_at->toIso8601String(),
|
||||
];
|
||||
}
|
||||
}
|
||||
27
api/app/Http/Resources/Api/V1/CrowdListResource.php
Normal file
27
api/app/Http/Resources/Api/V1/CrowdListResource.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Resources\Api\V1;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
final class CrowdListResource extends JsonResource
|
||||
{
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'event_id' => $this->event_id,
|
||||
'crowd_type_id' => $this->crowd_type_id,
|
||||
'name' => $this->name,
|
||||
'type' => $this->type,
|
||||
'recipient_company_id' => $this->recipient_company_id,
|
||||
'auto_approve' => $this->auto_approve,
|
||||
'max_persons' => $this->max_persons,
|
||||
'created_at' => $this->created_at->toIso8601String(),
|
||||
'persons_count' => $this->whenCounted('persons'),
|
||||
];
|
||||
}
|
||||
}
|
||||
24
api/app/Http/Resources/Api/V1/CrowdTypeResource.php
Normal file
24
api/app/Http/Resources/Api/V1/CrowdTypeResource.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Resources\Api\V1;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
final class CrowdTypeResource extends JsonResource
|
||||
{
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'organisation_id' => $this->organisation_id,
|
||||
'name' => $this->name,
|
||||
'system_type' => $this->system_type,
|
||||
'color' => $this->color,
|
||||
'icon' => $this->icon,
|
||||
'is_active' => $this->is_active,
|
||||
];
|
||||
}
|
||||
}
|
||||
29
api/app/Http/Resources/Api/V1/FestivalSectionResource.php
Normal file
29
api/app/Http/Resources/Api/V1/FestivalSectionResource.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Resources\Api\V1;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
final class FestivalSectionResource extends JsonResource
|
||||
{
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'event_id' => $this->event_id,
|
||||
'name' => $this->name,
|
||||
'type' => $this->type,
|
||||
'sort_order' => $this->sort_order,
|
||||
'crew_need' => $this->crew_need,
|
||||
'crew_auto_accepts' => $this->crew_auto_accepts,
|
||||
'responder_self_checkin' => $this->responder_self_checkin,
|
||||
'added_to_timeline' => $this->added_to_timeline,
|
||||
'crew_accreditation_level' => $this->crew_accreditation_level,
|
||||
'created_at' => $this->created_at->toIso8601String(),
|
||||
'shifts_count' => $this->whenCounted('shifts'),
|
||||
];
|
||||
}
|
||||
}
|
||||
29
api/app/Http/Resources/Api/V1/InvitationResource.php
Normal file
29
api/app/Http/Resources/Api/V1/InvitationResource.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Resources\Api\V1;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
final class InvitationResource extends JsonResource
|
||||
{
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'email' => $this->email,
|
||||
'role' => $this->role,
|
||||
'status' => $this->isExpired() && $this->isPending() ? 'expired' : $this->status,
|
||||
'expires_at' => $this->expires_at->toIso8601String(),
|
||||
'created_at' => $this->created_at->toIso8601String(),
|
||||
'organisation' => $this->whenLoaded('organisation', fn () => [
|
||||
'name' => $this->organisation->name,
|
||||
]),
|
||||
'invited_by' => $this->whenLoaded('invitedBy', fn () => [
|
||||
'name' => $this->invitedBy?->name,
|
||||
]),
|
||||
];
|
||||
}
|
||||
}
|
||||
26
api/app/Http/Resources/Api/V1/LocationResource.php
Normal file
26
api/app/Http/Resources/Api/V1/LocationResource.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Resources\Api\V1;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
final class LocationResource extends JsonResource
|
||||
{
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'event_id' => $this->event_id,
|
||||
'name' => $this->name,
|
||||
'address' => $this->address,
|
||||
'lat' => $this->lat,
|
||||
'lng' => $this->lng,
|
||||
'description' => $this->description,
|
||||
'access_instructions' => $this->access_instructions,
|
||||
'created_at' => $this->created_at->toIso8601String(),
|
||||
];
|
||||
}
|
||||
}
|
||||
37
api/app/Http/Resources/Api/V1/MemberCollection.php
Normal file
37
api/app/Http/Resources/Api/V1/MemberCollection.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Resources\Api\V1;
|
||||
|
||||
use App\Models\UserInvitation;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\ResourceCollection;
|
||||
|
||||
final class MemberCollection extends ResourceCollection
|
||||
{
|
||||
public $collects = MemberResource::class;
|
||||
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'data' => $this->collection,
|
||||
];
|
||||
}
|
||||
|
||||
/** @return array<string, mixed> */
|
||||
public function with(Request $request): array
|
||||
{
|
||||
$organisation = $request->route('organisation');
|
||||
|
||||
return [
|
||||
'meta' => [
|
||||
'total_members' => $this->collection->count(),
|
||||
'pending_invitations_count' => UserInvitation::where('organisation_id', $organisation->id)
|
||||
->pending()
|
||||
->where('expires_at', '>', now())
|
||||
->count(),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
29
api/app/Http/Resources/Api/V1/MemberResource.php
Normal file
29
api/app/Http/Resources/Api/V1/MemberResource.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Resources\Api\V1;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
final class MemberResource extends JsonResource
|
||||
{
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'name' => $this->name,
|
||||
'email' => $this->email,
|
||||
'role' => $this->pivot?->role,
|
||||
'avatar' => $this->avatar,
|
||||
'event_roles' => $this->whenLoaded('events', fn () =>
|
||||
$this->events->map(fn ($event) => [
|
||||
'event_id' => $event->id,
|
||||
'event_name' => $event->name,
|
||||
'role' => $event->pivot->role,
|
||||
])
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
44
api/app/Http/Resources/Api/V1/PersonCollection.php
Normal file
44
api/app/Http/Resources/Api/V1/PersonCollection.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Resources\Api\V1;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\ResourceCollection;
|
||||
|
||||
final class PersonCollection extends ResourceCollection
|
||||
{
|
||||
public $collects = PersonResource::class;
|
||||
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'data' => $this->collection,
|
||||
];
|
||||
}
|
||||
|
||||
public function with(Request $request): array
|
||||
{
|
||||
$persons = $this->collection;
|
||||
|
||||
$byCrowdType = $persons->groupBy(fn ($person) => $person->crowd_type_id);
|
||||
|
||||
$crowdTypeMeta = [];
|
||||
foreach ($byCrowdType as $crowdTypeId => $group) {
|
||||
$crowdTypeMeta[$crowdTypeId] = [
|
||||
'approved_count' => $group->where('status', 'approved')->count(),
|
||||
'pending_count' => $group->where('status', 'pending')->count(),
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'meta' => [
|
||||
'total' => $persons->count(),
|
||||
'approved_count' => $persons->where('status', 'approved')->count(),
|
||||
'pending_count' => $persons->where('status', 'pending')->count(),
|
||||
'by_crowd_type' => $crowdTypeMeta,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
29
api/app/Http/Resources/Api/V1/PersonResource.php
Normal file
29
api/app/Http/Resources/Api/V1/PersonResource.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Resources\Api\V1;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
final class PersonResource extends JsonResource
|
||||
{
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'event_id' => $this->event_id,
|
||||
'name' => $this->name,
|
||||
'email' => $this->email,
|
||||
'phone' => $this->phone,
|
||||
'status' => $this->status,
|
||||
'is_blacklisted' => $this->is_blacklisted,
|
||||
'admin_notes' => $this->admin_notes,
|
||||
'custom_fields' => $this->custom_fields,
|
||||
'created_at' => $this->created_at->toIso8601String(),
|
||||
'crowd_type' => new CrowdTypeResource($this->whenLoaded('crowdType')),
|
||||
'company' => new CompanyResource($this->whenLoaded('company')),
|
||||
];
|
||||
}
|
||||
}
|
||||
24
api/app/Http/Resources/Api/V1/ShiftAssignmentResource.php
Normal file
24
api/app/Http/Resources/Api/V1/ShiftAssignmentResource.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Resources\Api\V1;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
final class ShiftAssignmentResource extends JsonResource
|
||||
{
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'shift_id' => $this->shift_id,
|
||||
'person_id' => $this->person_id,
|
||||
'status' => $this->status,
|
||||
'auto_approved' => $this->auto_approved,
|
||||
'assigned_at' => $this->assigned_at?->toIso8601String(),
|
||||
'approved_at' => $this->approved_at?->toIso8601String(),
|
||||
];
|
||||
}
|
||||
}
|
||||
71
api/app/Http/Resources/Api/V1/ShiftResource.php
Normal file
71
api/app/Http/Resources/Api/V1/ShiftResource.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Resources\Api\V1;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
final class ShiftResource extends JsonResource
|
||||
{
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'festival_section_id' => $this->festival_section_id,
|
||||
'time_slot_id' => $this->time_slot_id,
|
||||
'location_id' => $this->location_id,
|
||||
'title' => $this->title,
|
||||
'description' => $this->description,
|
||||
'instructions' => $this->instructions,
|
||||
'coordinator_notes' => $this->when(
|
||||
$this->shouldShowCoordinatorNotes($request),
|
||||
$this->coordinator_notes,
|
||||
),
|
||||
'slots_total' => $this->slots_total,
|
||||
'slots_open_for_claiming' => $this->slots_open_for_claiming,
|
||||
'is_lead_role' => $this->is_lead_role,
|
||||
'report_time' => $this->report_time,
|
||||
'actual_start_time' => $this->actual_start_time,
|
||||
'actual_end_time' => $this->actual_end_time,
|
||||
'allow_overlap' => $this->allow_overlap,
|
||||
'status' => $this->status,
|
||||
'filled_slots' => $this->filled_slots,
|
||||
'fill_rate' => $this->fill_rate,
|
||||
'effective_start_time' => $this->effective_start_time,
|
||||
'effective_end_time' => $this->effective_end_time,
|
||||
'created_at' => $this->created_at->toIso8601String(),
|
||||
'time_slot' => new TimeSlotResource($this->whenLoaded('timeSlot')),
|
||||
'location' => new LocationResource($this->whenLoaded('location')),
|
||||
'festival_section' => new FestivalSectionResource($this->whenLoaded('festivalSection')),
|
||||
];
|
||||
}
|
||||
|
||||
private function shouldShowCoordinatorNotes(Request $request): bool
|
||||
{
|
||||
$user = $request->user();
|
||||
if (! $user) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($user->hasRole('super_admin')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$shift = $this->resource;
|
||||
$event = $shift->festivalSection?->event;
|
||||
if (! $event) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $event->organisation->users()
|
||||
->where('user_id', $user->id)
|
||||
->wherePivot('role', 'org_admin')
|
||||
->exists()
|
||||
|| $event->users()
|
||||
->where('user_id', $user->id)
|
||||
->wherePivot('role', 'event_manager')
|
||||
->exists();
|
||||
}
|
||||
}
|
||||
26
api/app/Http/Resources/Api/V1/TimeSlotResource.php
Normal file
26
api/app/Http/Resources/Api/V1/TimeSlotResource.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Resources\Api\V1;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
final class TimeSlotResource extends JsonResource
|
||||
{
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'event_id' => $this->event_id,
|
||||
'name' => $this->name,
|
||||
'person_type' => $this->person_type,
|
||||
'date' => $this->date->toDateString(),
|
||||
'start_time' => $this->start_time,
|
||||
'end_time' => $this->end_time,
|
||||
'duration_hours' => $this->duration_hours,
|
||||
'created_at' => $this->created_at->toIso8601String(),
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user