validationService = $validationService; } /** * List allocations / Get allocation matrix * * Get all allocations, optionally filtered by month. * * @authenticated * * @queryParam month string Filter by month (YYYY-MM format). Example: 2026-02 * * @response 200 { * "data": [ * { * "id": "550e8400-e29b-41d4-a716-446655440000", * "project_id": "550e8400-e29b-41d4-a716-446655440001", * "team_member_id": "550e8400-e29b-41d4-a716-446655440002", * "month": "2026-02", * "allocated_hours": 40.00 * } * ] * } */ public function index(Request $request): JsonResponse { $month = $request->query('month'); $query = Allocation::with(['project', 'teamMember']); if ($month) { // Convert YYYY-MM to YYYY-MM-01 for date comparison $monthDate = $month . '-01'; $query->where('month', $monthDate); } $allocations = $query->get(); return $this->wrapResource(AllocationResource::collection($allocations)); } /** * Create a new allocation * * Allocate hours for a team member to a project for a specific month. * * @authenticated * * @bodyParam project_id string required Project UUID. Example: 550e8400-e29b-41d4-a716-446655440001 * @bodyParam team_member_id string required Team member UUID. Example: 550e8400-e29b-41d4-a716-446655440002 * @bodyParam month string required Month (YYYY-MM format). Example: 2026-02 * @bodyParam allocated_hours numeric required Hours to allocate (must be >= 0). Example: 40 * * @response 201 { * "data": { * "id": "550e8400-e29b-41d4-a716-446655440000", * "project_id": "550e8400-e29b-41d4-a716-446655440001", * "team_member_id": "550e8400-e29b-41d4-a716-446655440002", * "month": "2026-02", * "allocated_hours": 40.00 * } * } * @response 422 {"message":"Validation failed","errors":{"allocated_hours":["Allocated hours must be greater than or equal to 0"]}} */ public function store(Request $request): JsonResponse { $validator = Validator::make($request->all(), [ 'project_id' => 'required|uuid|exists:projects,id', 'team_member_id' => 'required|uuid|exists:team_members,id', 'month' => 'required|date_format:Y-m', 'allocated_hours' => 'required|numeric|min:0', ]); if ($validator->fails()) { return response()->json([ 'message' => 'Validation failed', 'errors' => $validator->errors(), ], 422); } // Validate against capacity and approved estimate $capacityValidation = $this->validationService->validateCapacity( $request->input('team_member_id'), $request->input('month'), (float) $request->input('allocated_hours') ); $estimateValidation = $this->validationService->validateApprovedEstimate( $request->input('project_id'), $request->input('month'), (float) $request->input('allocated_hours') ); // Convert YYYY-MM to YYYY-MM-01 for database storage $data = $request->all(); $data['month'] = $data['month'] . '-01'; $allocation = Allocation::create($data); $allocation->load(['project', 'teamMember']); $response = new AllocationResource($allocation); $data = $response->toArray($request); // Add validation warnings/info to response $data['warnings'] = []; if ($capacityValidation['warning']) { $data['warnings'][] = $capacityValidation['warning']; } if ($estimateValidation['message']) { $data['warnings'][] = $estimateValidation['message']; } $data['utilization'] = $capacityValidation['utilization']; $data['allocation_indicator'] = $estimateValidation['indicator']; return response()->json(['data' => $data], 201); } /** * Get a single allocation * * Get details of a specific allocation by ID. * * @authenticated * * @urlParam id string required Allocation UUID. Example: 550e8400-e29b-41d4-a716-446655440000 * * @response 200 { * "data": { * "id": "550e8400-e29b-41d4-a716-446655440000", * "project_id": "550e8400-e29b-41d4-a716-446655440001", * "team_member_id": "550e8400-e29b-41d4-a716-446655440002", * "month": "2026-02", * "allocated_hours": 40.00 * } * } * @response 404 {"message": "Allocation not found"} */ public function show(string $id): JsonResponse { $allocation = Allocation::with(['project', 'teamMember'])->find($id); if (! $allocation) { return response()->json([ 'message' => 'Allocation not found', ], 404); } return $this->wrapResource(new AllocationResource($allocation)); } /** * Update an allocation * * Update an existing allocation's hours. * * @authenticated * * @urlParam id string required Allocation UUID. Example: 550e8400-e29b-41d4-a716-446655440000 * * @bodyParam allocated_hours numeric required Hours to allocate (must be >= 0). Example: 60 * * @response 200 { * "data": { * "id": "550e8400-e29b-41d4-a716-446655440000", * "project_id": "550e8400-e29b-41d4-a716-446655440001", * "team_member_id": "550e8400-e29b-41d4-a716-446655440002", * "month": "2026-02", * "allocated_hours": 60.00 * } * } * @response 404 {"message": "Allocation not found"} * @response 422 {"message":"Validation failed","errors":{"allocated_hours":["Allocated hours must be greater than or equal to 0"]}} */ public function update(Request $request, string $id): JsonResponse { $allocation = Allocation::find($id); if (! $allocation) { return response()->json([ 'message' => 'Allocation not found', ], 404); } $validator = Validator::make($request->all(), [ 'allocated_hours' => 'required|numeric|min:0', ]); if ($validator->fails()) { return response()->json([ 'message' => 'Validation failed', 'errors' => $validator->errors(), ], 422); } $allocation->update($request->all()); $allocation->load(['project', 'teamMember']); return $this->wrapResource(new AllocationResource($allocation)); } /** * Delete an allocation * * Remove an allocation. * * @authenticated * * @urlParam id string required Allocation UUID. Example: 550e8400-e29b-41d4-a716-446655440000 * * @response 200 {"message": "Allocation deleted successfully"} * @response 404 {"message": "Allocation not found"} */ public function destroy(string $id): JsonResponse { $allocation = Allocation::find($id); if (! $allocation) { return response()->json([ 'message' => 'Allocation not found', ], 404); } $allocation->delete(); return response()->json([ 'message' => 'Allocation deleted successfully', ]); } /** * Bulk create allocations * * Create or update multiple allocations in a single request. * * @authenticated * * @bodyParam allocations array required Array of allocations. Example: [{"project_id": "...", "team_member_id": "...", "month": "2026-02", "allocated_hours": 40}] * * @response 201 { * "data": [ * { * "id": "550e8400-e29b-41d4-a716-446655440000", * "project_id": "550e8400-e29b-41d4-a716-446655440001", * "team_member_id": "550e8400-e29b-41d4-a716-446655440002", * "month": "2026-02", * "allocated_hours": 40.00 * } * ] * } */ public function bulkStore(Request $request): JsonResponse { $validator = Validator::make($request->all(), [ 'allocations' => 'required|array|min:1', 'allocations.*.project_id' => 'required|uuid|exists:projects,id', 'allocations.*.team_member_id' => 'required|uuid|exists:team_members,id', 'allocations.*.month' => 'required|date_format:Y-m', 'allocations.*.allocated_hours' => 'required|numeric|min:0', ]); if ($validator->fails()) { return response()->json([ 'message' => 'Validation failed', 'errors' => $validator->errors(), ], 422); } $created = []; foreach ($request->input('allocations') as $allocationData) { $allocation = Allocation::create($allocationData); $created[] = $allocation; } return $this->wrapResource(AllocationResource::collection($created), 201); } }