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).
276 lines
7.6 KiB
Markdown
276 lines
7.6 KiB
Markdown
## 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:**
|
|
```json
|
|
{
|
|
"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:**
|
|
```json
|
|
{
|
|
"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:
|
|
```json
|
|
{
|
|
"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`: UUID
|
|
- `name`: string
|
|
- `email`: string
|
|
- `role`: RoleResource (when loaded)
|
|
|
|
**Excluded:** password, remember_token
|
|
|
|
### Resource: RoleResource
|
|
**Model:** Role
|
|
**Purpose:** Transform role data
|
|
**Fields:**
|
|
- `id`: integer
|
|
- `name`: string
|
|
- `description`: string|null
|
|
|
|
### Resource: TeamMemberResource
|
|
**Model:** TeamMember
|
|
**Purpose:** Transform team member data
|
|
**Fields:**
|
|
- `id`: UUID
|
|
- `name`: string
|
|
- `role`: RoleResource (eager loaded)
|
|
- `hourly_rate`: number
|
|
- `active`: boolean
|
|
- `created_at`: ISO 8601 datetime
|
|
- `updated_at`: ISO 8601 datetime
|
|
|
|
### Resource: ProjectStatusResource
|
|
**Model:** ProjectStatus
|
|
**Purpose:** Transform project status data
|
|
**Fields:**
|
|
- `id`: integer
|
|
- `name`: string
|
|
- `order`: integer
|
|
- `is_active`: boolean
|
|
- `is_billable`: boolean
|
|
|
|
### Resource: ProjectTypeResource
|
|
**Model:** ProjectType
|
|
**Purpose:** Transform project type data
|
|
**Fields:**
|
|
- `id`: integer
|
|
- `name`: string
|
|
- `description`: string|null
|
|
|
|
### Resource: ProjectResource
|
|
**Model:** Project
|
|
**Purpose:** Transform project data
|
|
**Fields:**
|
|
- `id`: UUID
|
|
- `code`: string
|
|
- `title`: string
|
|
- `status`: ProjectStatusResource (eager loaded)
|
|
- `type`: ProjectTypeResource (eager loaded)
|
|
- `approved_estimate`: number|null
|
|
- `forecasted_effort`: object (month -> hours mapping)
|
|
- `start_date`: date|null
|
|
- `end_date`: date|null
|
|
- `created_at`: ISO 8601 datetime
|
|
- `updated_at`: ISO 8601 datetime
|
|
|
|
### Resource: HolidayResource
|
|
**Model:** Holiday
|
|
**Purpose:** Transform holiday data
|
|
**Fields:**
|
|
- `id`: UUID
|
|
- `date`: date (YYYY-MM-DD)
|
|
- `name`: string
|
|
- `description`: string|null
|
|
|
|
### Resource: PtoResource
|
|
**Model:** Pto
|
|
**Purpose:** Transform PTO request data
|
|
**Fields:**
|
|
- `id`: UUID
|
|
- `team_member`: TeamMemberResource (when loaded)
|
|
- `team_member_id`: UUID
|
|
- `start_date`: date
|
|
- `end_date`: date
|
|
- `reason`: string|null
|
|
- `status`: 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`: UUID
|
|
- `month`: string (YYYY-MM)
|
|
- `working_days`: integer
|
|
- `person_days`: number
|
|
- `hours`: integer
|
|
- `details`: 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`: number
|
|
- `total_hours`: integer
|
|
- `members`: array of member capacities
|
|
|
|
### Resource: RevenueResource
|
|
**Model:** N/A (calculated data)
|
|
**Purpose:** Transform revenue calculation
|
|
**Fields:**
|
|
- `month`: string (YYYY-MM)
|
|
- `possible_revenue`: number
|
|
- `member_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:
|
|
1. Single resource returns `"data"` wrapper
|
|
2. Collection returns `"data"` array wrapper
|
|
3. All expected fields are present
|
|
4. Sensitive fields are excluded (e.g., password)
|
|
5. Relationships are properly nested
|
|
6. Date formatting follows ISO 8601
|
|
|
|
### Feature Test Updates
|
|
All 63 existing feature tests MUST be updated to:
|
|
1. Assert response has `"data"` key
|
|
2. Access nested data via `response.json()['data']`
|
|
3. 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
|