Files
Santhosh Janardhanan b8262bbcaf docs(openspec): archive completed changes and sync main specs
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).
2026-03-08 19:13:28 -04:00

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