validate([ 'month' => 'required|date_format:Y-m', 'team_member_id' => 'required|exists:team_members,id', ]); $capacity = $this->capacityService->calculateIndividualCapacity($data['team_member_id'], $data['month']); $workingDays = $this->capacityService->calculateWorkingDays($data['month']); $payload = [ 'team_member_id' => $data['team_member_id'], 'month' => $data['month'], 'working_days' => $workingDays, 'person_days' => $capacity['person_days'], 'hours' => $capacity['hours'], 'details' => $capacity['details'], ]; return $this->wrapResource(new CapacityResource($payload)); } /** * Get Team Capacity * * Summarize the combined capacity for all active team members in a month. * * @group Capacity Planning * * @urlParam month string required The month in YYYY-MM format. Example: 2026-02 * * @response { * "data": { * "month": "2026-02", * "total_person_days": 180.5, * "total_hours": 1444, * "members": [ * { * "id": "550e8400-e29b-41d4-a716-446655440000", * "name": "Ada Lovelace", * "person_days": 18.5, * "hours": 148 * } * ] * } * } */ public function team(Request $request): JsonResponse { $data = $request->validate([ 'month' => 'required|date_format:Y-m', ]); $payload = $this->capacityService->calculateTeamCapacity($data['month']); return $this->wrapResource(new TeamCapacityResource($payload)); } /** * Get Possible Revenue * * Estimate monthly revenue based on capacity hours and hourly rates. * * @group Capacity Planning * * @urlParam month string required The month in YYYY-MM format. Example: 2026-02 * * @response { * "data": { * "month": "2026-02", * "possible_revenue": 21500.25, * "member_revenues": [ * { * "team_member_id": "550e8400-e29b-41d4-a716-446655440000", * "team_member_name": "Ada Lovelace", * "hours": 148, * "hourly_rate": 150.0, * "revenue": 22200.0 * } * ] * } * } */ public function revenue(Request $request): JsonResponse { $data = $request->validate([ 'month' => 'required|date_format:Y-m', ]); $revenue = $this->capacityService->calculatePossibleRevenue($data['month']); $memberRevenues = []; TeamMember::where('active', true) ->get() ->each(function (TeamMember $member) use ($data, &$memberRevenues): void { $capacity = $this->capacityService->calculateIndividualCapacity($member->id, $data['month']); $hours = $capacity['hours']; $hourlyRate = $member->hourly_rate !== null ? (float) $member->hourly_rate : null; $memberRevenue = $hourlyRate !== null ? round($hours * $hourlyRate, 2) : 0.0; $memberRevenues[] = [ 'team_member_id' => $member->id, 'team_member_name' => $member->name, 'hours' => $hours, 'hourly_rate' => $hourlyRate, 'revenue' => $memberRevenue, ]; }); return $this->wrapResource(new RevenueResource([ 'month' => $data['month'], 'possible_revenue' => $revenue, 'member_revenues' => $memberRevenues, ])); } /** * Save Team Member Availability * * Persist a daily availability override and refresh cached capacity totals. * * @group Capacity Planning * * @bodyParam team_member_id string required The team member UUID. * @bodyParam date string required The date for the availability override (YYYY-MM-DD). * @bodyParam availability numeric required The availability value (0, 0.5, 1.0). * * @response 201 { * "data": { * "team_member_id": "550e8400-e29b-41d4-a716-446655440000", * "date": "2026-02-03", * "availability": 0.5 * } * } */ public function saveAvailability(Request $request): JsonResponse { $data = $request->validate([ 'team_member_id' => 'required|exists:team_members,id', 'date' => 'required|date_format:Y-m-d', 'availability' => ['required', 'numeric', Rule::in([0, 0.5, 1])], ]); $entry = $this->capacityService->upsertTeamMemberAvailability( $data['team_member_id'], $data['date'], (float) $data['availability'] ); return $this->wrapResource(new TeamMemberAvailabilityResource($entry), 201); } }