## 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