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.6 KiB
ADDED 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"]
}
}
ADDED Resource Classes
Resource: UserResource
Model: User
Purpose: Transform user data for API responses
Fields:
id: UUIDname: stringemail: stringrole: RoleResource (when loaded)
Excluded: password, remember_token
Resource: RoleResource
Model: Role
Purpose: Transform role data
Fields:
id: integername: stringdescription: string|null
Resource: TeamMemberResource
Model: TeamMember
Purpose: Transform team member data
Fields:
id: UUIDname: stringrole: RoleResource (eager loaded)hourly_rate: numberactive: booleancreated_at: ISO 8601 datetimeupdated_at: ISO 8601 datetime
Resource: ProjectStatusResource
Model: ProjectStatus
Purpose: Transform project status data
Fields:
id: integername: stringorder: integeris_active: booleanis_billable: boolean
Resource: ProjectTypeResource
Model: ProjectType
Purpose: Transform project type data
Fields:
id: integername: stringdescription: string|null
Resource: ProjectResource
Model: Project
Purpose: Transform project data
Fields:
id: UUIDcode: stringtitle: stringstatus: ProjectStatusResource (eager loaded)type: ProjectTypeResource (eager loaded)approved_estimate: number|nullforecasted_effort: object (month -> hours mapping)start_date: date|nullend_date: date|nullcreated_at: ISO 8601 datetimeupdated_at: ISO 8601 datetime
Resource: HolidayResource
Model: Holiday
Purpose: Transform holiday data
Fields:
id: UUIDdate: date (YYYY-MM-DD)name: stringdescription: string|null
Resource: PtoResource
Model: Pto
Purpose: Transform PTO request data
Fields:
id: UUIDteam_member: TeamMemberResource (when loaded)team_member_id: UUIDstart_date: dateend_date: datereason: string|nullstatus: string (pending|approved|rejected)created_at: ISO 8601 datetime
Resource: CapacityResource
Model: N/A (calculated data)
Purpose: Transform individual capacity calculation
Fields:
team_member_id: UUIDmonth: string (YYYY-MM)working_days: integerperson_days: numberhours: integerdetails: array of day-by-day breakdown
Resource: TeamCapacityResource
Model: N/A (calculated data)
Purpose: Transform team capacity aggregation
Fields:
month: string (YYYY-MM)total_person_days: numbertotal_hours: integermembers: array of member capacities
Resource: RevenueResource
Model: N/A (calculated data)
Purpose: Transform revenue calculation
Fields:
month: string (YYYY-MM)possible_revenue: numbermember_revenues: array of individual revenues
ADDED API Endpoints (Updated Format)
All existing endpoints remain functionally identical, only response format changes:
Auth Endpoints
| Endpoint | Method | Response Type |
|---|---|---|
| POST /api/auth/login | Auth | UserResource with tokens |
| POST /api/auth/refresh | Auth | UserResource with new token |
Team Member Endpoints
| Endpoint | Method | Response Type |
|---|---|---|
| GET /api/team-members | Collection | TeamMemberResource[] |
| POST /api/team-members | Single | TeamMemberResource |
| GET /api/team-members/{id} | Single | TeamMemberResource |
| PUT /api/team-members/{id} | Single | TeamMemberResource |
| DELETE /api/team-members/{id} | Message | { message: "..." } |
Project Endpoints
| Endpoint | Method | Response Type |
|---|---|---|
| GET /api/projects | Collection | ProjectResource[] |
| POST /api/projects | Single | ProjectResource |
| GET /api/projects/{id} | Single | ProjectResource |
| PUT /api/projects/{id} | Single | ProjectResource |
| PUT /api/projects/{id}/status | Single | ProjectResource |
| PUT /api/projects/{id}/estimate | Single | ProjectResource |
| PUT /api/projects/{id}/forecast | Single | ProjectResource |
| DELETE /api/projects/{id} | Message | { message: "..." } |
Capacity Endpoints
| Endpoint | Method | Response Type |
|---|---|---|
| GET /api/capacity | Single | CapacityResource |
| GET /api/capacity/team | Single | TeamCapacityResource |
| GET /api/capacity/revenue | Single | RevenueResource |
Holiday Endpoints
| Endpoint | Method | Response Type |
|---|---|---|
| GET /api/holidays | Collection | HolidayResource[] |
| POST /api/holidays | Single | HolidayResource |
| DELETE /api/holidays/{id} | Message | { message: "..." } |
PTO Endpoints
| Endpoint | Method | Response Type |
|---|---|---|
| GET /api/ptos | Collection | PtoResource[] |
| POST /api/ptos | Single | PtoResource |
| PUT /api/ptos/{id}/approve | Single | PtoResource |
ADDED Test Requirements
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.
REMOVED
- Direct
response()->json($model)calls in controllers - Raw array/object responses without
"data"wrapper