docs(openspec): add reporting API contract documentation

Add comprehensive API documentation for the reporting endpoint:
- Request/response structure
- View type inference (did/is/will)
- Blank vs explicit zero semantics
- Status values and error responses

Related to enhanced-allocation change.
This commit is contained in:
2026-03-08 18:22:27 -04:00
parent 3324c4f156
commit b7bbfb45c0
27 changed files with 1632 additions and 35 deletions

View File

@@ -59,9 +59,51 @@ class AllocationController extends Controller
$allocations = $query->get();
// Compute allocation_indicator for each allocation based on project totals
$allocations->each(function ($allocation) {
$allocation->allocation_indicator = $this->computeAllocationIndicator(
$allocation->project_id,
$allocation->month
);
});
return $this->wrapResource(AllocationResource::collection($allocations));
}
/**
* Compute allocation indicator based on project totals.
*/
private function computeAllocationIndicator(string $projectId, string $month): string
{
// Convert month to date format if needed
$monthDate = strlen($month) === 7 ? $month . '-01' : $month;
// Get total allocated for this project in this month
$totalAllocated = Allocation::where('project_id', $projectId)
->where('month', $monthDate)
->sum('allocated_hours');
// Get project approved estimate
$project = \App\Models\Project::find($projectId);
$approvedEstimate = $project?->approved_estimate;
// Handle no estimate
if (! $approvedEstimate || $approvedEstimate <= 0) {
return 'gray';
}
$percentage = ($totalAllocated / $approvedEstimate) * 100;
// Check in correct order: over first, then at capacity, then under
if ($percentage > 100) {
return 'red';
} elseif ($percentage >= 100) {
return 'green';
} else {
return 'yellow';
}
}
/**
* Create a new allocation
*
@@ -89,7 +131,7 @@ class AllocationController extends Controller
{
$validator = Validator::make($request->all(), [
'project_id' => 'required|uuid|exists:projects,id',
'team_member_id' => 'required|uuid|exists:team_members,id',
'team_member_id' => 'nullable|uuid|exists:team_members,id',
'month' => 'required|date_format:Y-m',
'allocated_hours' => 'required|numeric|min:0',
]);
@@ -101,12 +143,17 @@ class AllocationController extends Controller
], 422);
}
// Validate against capacity and approved estimate
$capacityValidation = $this->validationService->validateCapacity(
$request->input('team_member_id'),
$request->input('month'),
(float) $request->input('allocated_hours')
);
// Validate against capacity and approved estimate (skip for untracked)
$teamMemberId = $request->input('team_member_id');
$capacityValidation = ['valid' => true, 'warning' => null, 'utilization' => 0];
if ($teamMemberId) {
$capacityValidation = $this->validationService->validateCapacity(
$teamMemberId,
$request->input('month'),
(float) $request->input('allocated_hours')
);
}
$estimateValidation = $this->validationService->validateApprovedEstimate(
$request->input('project_id'),