shifts() ->with(['timeSlot', 'location']) ->get(); return ShiftResource::collection($shifts); } public function store(StoreShiftRequest $request, Event $event, FestivalSection $section): JsonResponse { Gate::authorize('create', [Shift::class, $event]); $shift = $section->shifts()->create($request->validated()); $shift->load(['timeSlot', 'location']); return $this->created(new ShiftResource($shift)); } public function update(UpdateShiftRequest $request, Event $event, FestivalSection $section, Shift $shift): JsonResponse { Gate::authorize('update', [$shift, $event, $section]); $shift->update($request->validated()); $shift->load(['timeSlot', 'location']); return $this->success(new ShiftResource($shift->fresh()->load(['timeSlot', 'location']))); } public function destroy(Event $event, FestivalSection $section, Shift $shift): JsonResponse { Gate::authorize('delete', [$shift, $event, $section]); $shift->delete(); return response()->json(null, 204); } public function assign(AssignShiftRequest $request, Event $event, FestivalSection $section, Shift $shift): JsonResponse { Gate::authorize('assign', [$shift, $event, $section]); $personId = $request->validated('person_id'); // Check if shift is full $approvedCount = $shift->shiftAssignments()->where('status', 'approved')->count(); if ($approvedCount >= $shift->slots_total) { return $this->error('Shift is vol — alle slots zijn bezet.', 422); } // Check overlap conflict if allow_overlap is false if (! $shift->allow_overlap) { $conflict = ShiftAssignment::where('person_id', $personId) ->where('time_slot_id', $shift->time_slot_id) ->whereNotIn('status', ['rejected', 'cancelled']) ->exists(); if ($conflict) { return $this->error('Deze persoon is al ingepland voor dit tijdslot.', 422); } } $autoApprove = $section->crew_auto_accepts; $assignment = $shift->shiftAssignments()->create([ 'person_id' => $personId, 'time_slot_id' => $shift->time_slot_id, 'status' => $autoApprove ? 'approved' : 'approved', 'auto_approved' => $autoApprove, 'assigned_by' => $request->user()->id, 'assigned_at' => now(), 'approved_at' => now(), ]); // Update shift status if full $newApprovedCount = $shift->shiftAssignments()->where('status', 'approved')->count(); if ($newApprovedCount >= $shift->slots_total) { $shift->update(['status' => 'full']); } return $this->created(new ShiftAssignmentResource($assignment)); } public function claim(AssignShiftRequest $request, Event $event, FestivalSection $section, Shift $shift): JsonResponse { Gate::authorize('claim', [$shift, $event, $section]); $personId = $request->validated('person_id'); // Check claiming slots available $claimedCount = $shift->shiftAssignments() ->whereNotIn('status', ['rejected', 'cancelled']) ->count(); if ($shift->slots_open_for_claiming <= 0 || $claimedCount >= $shift->slots_open_for_claiming) { return $this->error('Geen claimbare slots beschikbaar voor deze shift.', 422); } // Check overlap conflict if allow_overlap is false if (! $shift->allow_overlap) { $conflict = ShiftAssignment::where('person_id', $personId) ->where('time_slot_id', $shift->time_slot_id) ->whereNotIn('status', ['rejected', 'cancelled']) ->exists(); if ($conflict) { return $this->error('Deze persoon is al ingepland voor dit tijdslot.', 422); } } $autoApprove = $section->crew_auto_accepts; $assignment = $shift->shiftAssignments()->create([ 'person_id' => $personId, 'time_slot_id' => $shift->time_slot_id, 'status' => $autoApprove ? 'approved' : 'pending_approval', 'auto_approved' => $autoApprove, 'assigned_at' => now(), 'approved_at' => $autoApprove ? now() : null, ]); return $this->created(new ShiftAssignmentResource($assignment)); } }