Archive three completed changes to archive/: - api-resource-standard (70 tasks, 14 resource classes) - capacity-expert-mode (68 tasks, expert mode planning grid) - enhanced-allocation (62 tasks, planning fidelity + reporting) Sync all delta specs to main specs/: - api-resource-standard: API response standardization - capacity-expert-mode: Expert mode toggle, grid, KPIs, batch API - resource-allocation: Month execution comparison, bulk, untracked - untracked-allocation: Null team member support - allocation-indicators: Variance indicators (red/amber/neutral) - monthly-budget: Explicit project-month planning All changes verified and tested (157 tests passing).
7.1 KiB
Purpose
Define the API resource standardization requirements for Headroom. All API responses MUST follow Laravel API Resource format with a consistent "data" wrapper to ensure predictable response structures across the entire API surface.
Requirements
Requirement: API Response Standardization
All API responses MUST follow Laravel API Resource format with consistent "data" wrapper.
Scenario: Single resource response
- WHEN an API endpoint returns a single model
- THEN the response MUST have a
"data"key containing the resource - AND the resource MUST include all required fields defined in the resource class
Example:
{
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "John Doe",
"role": {
"id": 1,
"name": "Backend Developer"
},
"hourly_rate": 150.00,
"active": true,
"created_at": "2026-02-01T10:00:00Z"
}
}
Scenario: Collection response
- WHEN an API endpoint returns multiple models
- THEN the response MUST have a
"data"key containing an array - AND each item in the array MUST follow the single resource format
Example:
{
"data": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "John Doe",
"role": { "id": 1, "name": "Backend Developer" },
"hourly_rate": 150.00,
"active": true,
"created_at": "2026-02-01T10:00:00Z"
},
{
"id": "660e8400-e29b-41d4-a716-446655440001",
"name": "Jane Smith",
"role": { "id": 2, "name": "Frontend Developer" },
"hourly_rate": 140.00,
"active": true,
"created_at": "2026-02-01T10:00:00Z"
}
]
}
Scenario: Nested relationships
- WHEN a resource has relationships (e.g., TeamMember has Role)
- THEN the relationship MUST be nested within the resource
- AND the nested resource SHOULD also follow the standard format
Scenario: Error responses
- WHEN an error occurs (validation, not found, etc.)
- THEN the response format remains unchanged (no
"data"wrapper for errors) - AND errors continue to use standard Laravel error format:
{
"message": "The given data was invalid.",
"errors": {
"field": ["Error message"]
}
}
Requirement: Resource Classes
The following resource classes MUST be implemented:
UserResource
- Model: User
- Purpose: Transform user data for API responses
- Fields: id (UUID), name (string), email (string), role (RoleResource when loaded)
- Excluded: password, remember_token
RoleResource
- Model: Role
- Purpose: Transform role data
- Fields: id (integer), name (string), description (string|null)
TeamMemberResource
- Model: TeamMember
- Purpose: Transform team member data
- Fields: id (UUID), name (string), role (RoleResource), hourly_rate (number), active (boolean), created_at, updated_at (ISO 8601)
ProjectStatusResource
- Model: ProjectStatus
- Purpose: Transform project status data
- Fields: id (integer), name (string), order (integer), is_active (boolean), is_billable (boolean)
ProjectTypeResource
- Model: ProjectType
- Purpose: Transform project type data
- Fields: id (integer), name (string), description (string|null)
ProjectResource
- Model: Project
- Purpose: Transform project data
- Fields: id (UUID), code (string), title (string), status (ProjectStatusResource), type (ProjectTypeResource), approved_estimate (number|null), forecasted_effort (object), start_date, end_date (date|null), created_at, updated_at (ISO 8601)
HolidayResource
- Model: Holiday
- Purpose: Transform holiday data
- Fields: id (UUID), date (date), name (string), description (string|null)
PtoResource
- Model: Pto
- Purpose: Transform PTO request data
- Fields: id (UUID), team_member (TeamMemberResource when loaded), team_member_id (UUID), start_date, end_date (date), reason (string|null), status (string: pending|approved|rejected), created_at (ISO 8601)
CapacityResource
- Model: N/A (calculated data)
- Purpose: Transform individual capacity calculation
- Fields: team_member_id (UUID), month (YYYY-MM), working_days (integer), person_days (number), hours (integer), details (array of day-by-day breakdown)
TeamCapacityResource
- Model: N/A (calculated data)
- Purpose: Transform team capacity aggregation
- Fields: month (YYYY-MM), total_person_days (number), total_hours (integer), members (array of member capacities)
RevenueResource
- Model: N/A (calculated data)
- Purpose: Transform revenue calculation
- Fields: month (YYYY-MM), possible_revenue (number), member_revenues (array of individual revenues)
Requirement: API Endpoints Updated Format
All existing endpoints remain functionally identical, only response format changes to use Resource classes with "data" wrapper:
Auth Endpoints:
- POST /api/auth/login → UserResource with tokens
- POST /api/auth/refresh → UserResource with new token
Team Member Endpoints:
- GET /api/team-members → TeamMemberResource[] (collection)
- POST /api/team-members → TeamMemberResource (single)
- GET /api/team-members/{id} → TeamMemberResource (single)
- PUT /api/team-members/{id} → TeamMemberResource (single)
- DELETE /api/team-members/{id} → { message: "..." }
Project Endpoints:
- GET /api/projects → ProjectResource[] (collection)
- POST /api/projects → ProjectResource (single)
- GET /api/projects/{id} → ProjectResource (single)
- PUT /api/projects/{id} → ProjectResource (single)
- PUT /api/projects/{id}/status → ProjectResource (single)
- PUT /api/projects/{id}/estimate → ProjectResource (single)
- PUT /api/projects/{id}/forecast → ProjectResource (single)
- DELETE /api/projects/{id} → { message: "..." }
Capacity Endpoints:
- GET /api/capacity → CapacityResource (single)
- GET /api/capacity/team → TeamCapacityResource (single)
- GET /api/capacity/revenue → RevenueResource (single)
Holiday Endpoints:
- GET /api/holidays → HolidayResource[] (collection)
- POST /api/holidays → HolidayResource (single)
- DELETE /api/holidays/{id} → { message: "..." }
PTO Endpoints:
- GET /api/ptos → PtoResource[] (collection)
- POST /api/ptos → PtoResource (single)
- PUT /api/ptos/{id}/approve → PtoResource (single)
Requirement: Test Coverage
Resource Unit Tests Each resource MUST have unit tests verifying:
- Single resource returns
"data"wrapper - Collection returns
"data"array wrapper - All expected fields are present
- Sensitive fields are excluded (e.g., password)
- Relationships are properly nested
- Date formatting follows ISO 8601
Feature Test Updates All 63 existing feature tests MUST be updated to:
- Assert response has
"data"key - Access nested data via
response.json()['data'] - Verify collection responses have
"data"array
E2E Test Verification All 134 E2E tests MUST pass after frontend API client is updated.
Requirement: Deprecated Patterns
The following patterns are DEPRECATED and MUST NOT be used:
- Direct
response()->json($model)calls in controllers - Raw array/object responses without
"data"wrapper