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

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