fix: allow organiser to approve shift assignments when shift is full
The approve() and bulkApprove() methods in ShiftAssignmentService hard-blocked with a 422 when all slots were filled. This was incorrect for organiser actions — only volunteer claims (portal self-service) should enforce capacity limits. Organiser assign() already allowed overbooking, making the approve block inconsistent. Changes: - Remove capacity hard-block from approve() and bulkApprove(), replace with audit log entry (shift.overbooked_approval) - Add overbook confirmation dialog in ShiftDetailPanel before approving a full shift (single + bulk approve) - Add onError handlers to all mutations in ShiftDetailPanel (approve, reject, cancel, bulk-approve) so errors display in the snackbar - Add global 422 validation error display in axios interceptor via useNotificationStore as safety net for all components - Add PHPUnit test for approve-when-full scenario Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -168,18 +168,26 @@ final class ShiftAssignmentService
|
||||
$this->validateStatusTransition($assignment, ShiftAssignmentStatus::APPROVED);
|
||||
|
||||
$shift = $assignment->shift;
|
||||
$oldStatus = $assignment->status;
|
||||
|
||||
// Log overbooking for audit trail (organisers may intentionally overbook)
|
||||
$approvedCount = $shift->shiftAssignments()
|
||||
->where('status', ShiftAssignmentStatus::APPROVED)
|
||||
->count();
|
||||
|
||||
if ($approvedCount >= $shift->slots_total) {
|
||||
throw ValidationException::withMessages([
|
||||
'shift' => ['Shift is vol — alle slots zijn bezet.'],
|
||||
]);
|
||||
activity('shift_assignment')
|
||||
->causedBy($approvedBy)
|
||||
->performedOn($shift)
|
||||
->withProperties([
|
||||
'filled_slots' => $approvedCount,
|
||||
'slots_total' => $shift->slots_total,
|
||||
'assignment_id' => $assignment->id,
|
||||
'person_id' => $assignment->person_id,
|
||||
])
|
||||
->log('shift.overbooked_approval');
|
||||
}
|
||||
|
||||
$oldStatus = $assignment->status;
|
||||
|
||||
$assignment->status = ShiftAssignmentStatus::APPROVED;
|
||||
$assignment->approved_by = $approvedBy->id;
|
||||
$assignment->approved_at = now();
|
||||
@@ -280,16 +288,22 @@ final class ShiftAssignmentService
|
||||
}
|
||||
|
||||
$shift = $assignment->shift;
|
||||
|
||||
// Log overbooking for audit trail
|
||||
$approvedCount = $shift->shiftAssignments()
|
||||
->where('status', ShiftAssignmentStatus::APPROVED)
|
||||
->count();
|
||||
|
||||
if ($approvedCount >= $shift->slots_total) {
|
||||
return [
|
||||
'assignment_id' => $assignment->id,
|
||||
'status' => 'skipped',
|
||||
'reason' => 'Shift is vol.',
|
||||
];
|
||||
activity('shift_assignment')
|
||||
->causedBy($approvedBy)
|
||||
->performedOn($shift)
|
||||
->withProperties([
|
||||
'filled_slots' => $approvedCount,
|
||||
'slots_total' => $shift->slots_total,
|
||||
'assignment_id' => $assignment->id,
|
||||
])
|
||||
->log('shift.overbooked_approval');
|
||||
}
|
||||
|
||||
$assignment->status = ShiftAssignmentStatus::APPROVED;
|
||||
|
||||
Reference in New Issue
Block a user