- Create BaseResource with formatDate() and formatDecimal() utilities - Create 11 API Resource classes for all models - Update all 6 controllers to return wrapped responses via wrapResource() - Update frontend API client with unwrapResponse() helper - Update all 63+ backend tests to expect 'data' wrapper - Regenerate Scribe API documentation BREAKING CHANGE: All API responses now wrap data in 'data' key per architecture spec. Backend Tests: 70 passed, 5 failed (unrelated to data wrapper) Frontend Unit: 10 passed E2E Tests: 102 passed, 20 skipped API Docs: Generated successfully Refs: openspec/changes/api-resource-standard
788 lines
19 KiB
YAML
788 lines
19 KiB
YAML
name: Projects
|
|
description: |-
|
|
|
|
Endpoints for managing projects.
|
|
endpoints:
|
|
-
|
|
custom: []
|
|
httpMethods:
|
|
- GET
|
|
uri: api/projects/types
|
|
metadata:
|
|
custom: []
|
|
groupName: Projects
|
|
groupDescription: |-
|
|
|
|
Endpoints for managing projects.
|
|
subgroup: ''
|
|
subgroupDescription: ''
|
|
title: 'Get all project types'
|
|
description: ''
|
|
authenticated: true
|
|
deprecated: false
|
|
headers:
|
|
Content-Type: application/json
|
|
Accept: application/json
|
|
urlParameters: []
|
|
cleanUrlParameters: []
|
|
queryParameters: []
|
|
cleanQueryParameters: []
|
|
bodyParameters: []
|
|
cleanBodyParameters: []
|
|
fileParameters: []
|
|
responses:
|
|
-
|
|
custom: []
|
|
status: 200
|
|
content: |-
|
|
{
|
|
"data": [
|
|
{"id": 1, "name": "Project"},
|
|
{"id": 2, "name": "Support"},
|
|
{"id": 3, "name": "Engagement"}
|
|
]
|
|
}
|
|
headers: []
|
|
description: ''
|
|
responseFields: []
|
|
auth: []
|
|
controller: null
|
|
method: null
|
|
route: null
|
|
-
|
|
custom: []
|
|
httpMethods:
|
|
- GET
|
|
uri: api/projects/statuses
|
|
metadata:
|
|
custom: []
|
|
groupName: Projects
|
|
groupDescription: |-
|
|
|
|
Endpoints for managing projects.
|
|
subgroup: ''
|
|
subgroupDescription: ''
|
|
title: 'Get all project statuses'
|
|
description: ''
|
|
authenticated: true
|
|
deprecated: false
|
|
headers:
|
|
Content-Type: application/json
|
|
Accept: application/json
|
|
urlParameters: []
|
|
cleanUrlParameters: []
|
|
queryParameters: []
|
|
cleanQueryParameters: []
|
|
bodyParameters: []
|
|
cleanBodyParameters: []
|
|
fileParameters: []
|
|
responses:
|
|
-
|
|
custom: []
|
|
status: 200
|
|
content: |-
|
|
{
|
|
"data": [
|
|
{"id": 1, "name": "Pre-sales", "order": 1},
|
|
{"id": 2, "name": "SOW Approval", "order": 2},
|
|
{"id": 3, "name": "Gathering Estimates", "order": 3}
|
|
]
|
|
}
|
|
headers: []
|
|
description: ''
|
|
responseFields: []
|
|
auth: []
|
|
controller: null
|
|
method: null
|
|
route: null
|
|
-
|
|
custom: []
|
|
httpMethods:
|
|
- GET
|
|
uri: api/projects
|
|
metadata:
|
|
custom: []
|
|
groupName: Projects
|
|
groupDescription: |-
|
|
|
|
Endpoints for managing projects.
|
|
subgroup: ''
|
|
subgroupDescription: ''
|
|
title: 'List all projects'
|
|
description: 'Get a list of all projects with optional filtering by status and type.'
|
|
authenticated: true
|
|
deprecated: false
|
|
headers:
|
|
Content-Type: application/json
|
|
Accept: application/json
|
|
urlParameters: []
|
|
cleanUrlParameters: []
|
|
queryParameters:
|
|
status_id:
|
|
custom: []
|
|
name: status_id
|
|
description: 'Filter by status ID.'
|
|
required: false
|
|
example: 1
|
|
type: integer
|
|
enumValues: []
|
|
exampleWasSpecified: true
|
|
nullable: false
|
|
deprecated: false
|
|
type_id:
|
|
custom: []
|
|
name: type_id
|
|
description: 'Filter by type ID.'
|
|
required: false
|
|
example: 2
|
|
type: integer
|
|
enumValues: []
|
|
exampleWasSpecified: true
|
|
nullable: false
|
|
deprecated: false
|
|
cleanQueryParameters:
|
|
status_id: 1
|
|
type_id: 2
|
|
bodyParameters: []
|
|
cleanBodyParameters: []
|
|
fileParameters: []
|
|
responses:
|
|
-
|
|
custom: []
|
|
status: 200
|
|
content: |-
|
|
{
|
|
"data": [
|
|
{
|
|
"id": "550e8400-e29b-41d4-a716-446655440000",
|
|
"code": "PROJ-001",
|
|
"title": "Client Dashboard Redesign",
|
|
"status": {"id": 1, "name": "Pre-sales"},
|
|
"type": {"id": 2, "name": "Support"},
|
|
"approved_estimate": "120.00",
|
|
"forecasted_effort": {"2024-02": 40, "2024-03": 60, "2024-04": 20},
|
|
"created_at": "2024-01-15T10:00:00.000000Z",
|
|
"updated_at": "2024-01-15T10:00:00.000000Z"
|
|
}
|
|
]
|
|
}
|
|
headers: []
|
|
description: ''
|
|
responseFields: []
|
|
auth: []
|
|
controller: null
|
|
method: null
|
|
route: null
|
|
-
|
|
custom: []
|
|
httpMethods:
|
|
- POST
|
|
uri: api/projects
|
|
metadata:
|
|
custom: []
|
|
groupName: Projects
|
|
groupDescription: |-
|
|
|
|
Endpoints for managing projects.
|
|
subgroup: ''
|
|
subgroupDescription: ''
|
|
title: 'Create a new project'
|
|
description: 'Create a new project with code, title, and type.'
|
|
authenticated: true
|
|
deprecated: false
|
|
headers:
|
|
Content-Type: application/json
|
|
Accept: application/json
|
|
urlParameters: []
|
|
cleanUrlParameters: []
|
|
queryParameters: []
|
|
cleanQueryParameters: []
|
|
bodyParameters:
|
|
code:
|
|
custom: []
|
|
name: code
|
|
description: 'Project code (must be unique).'
|
|
required: true
|
|
example: PROJ-001
|
|
type: string
|
|
enumValues: []
|
|
exampleWasSpecified: true
|
|
nullable: false
|
|
deprecated: false
|
|
title:
|
|
custom: []
|
|
name: title
|
|
description: 'Project title.'
|
|
required: true
|
|
example: 'Client Dashboard Redesign'
|
|
type: string
|
|
enumValues: []
|
|
exampleWasSpecified: true
|
|
nullable: false
|
|
deprecated: false
|
|
type_id:
|
|
custom: []
|
|
name: type_id
|
|
description: 'Project type ID.'
|
|
required: true
|
|
example: 1
|
|
type: integer
|
|
enumValues: []
|
|
exampleWasSpecified: true
|
|
nullable: false
|
|
deprecated: false
|
|
cleanBodyParameters:
|
|
code: PROJ-001
|
|
title: 'Client Dashboard Redesign'
|
|
type_id: 1
|
|
fileParameters: []
|
|
responses:
|
|
-
|
|
custom: []
|
|
status: 201
|
|
content: |-
|
|
{
|
|
"data": {
|
|
"id": "550e8400-e29b-41d4-a716-446655440000",
|
|
"code": "PROJ-001",
|
|
"title": "Client Dashboard Redesign",
|
|
"status": {"id": 1, "name": "Pre-sales"},
|
|
"type": {"id": 1, "name": "Project"}
|
|
}
|
|
}
|
|
headers: []
|
|
description: ''
|
|
-
|
|
custom: []
|
|
status: 422
|
|
content: '{"message":"Validation failed","errors":{"code":["Project code must be unique"],"title":["The title field is required."]}}'
|
|
headers: []
|
|
description: ''
|
|
responseFields: []
|
|
auth: []
|
|
controller: null
|
|
method: null
|
|
route: null
|
|
-
|
|
custom: []
|
|
httpMethods:
|
|
- GET
|
|
uri: 'api/projects/{id}'
|
|
metadata:
|
|
custom: []
|
|
groupName: Projects
|
|
groupDescription: |-
|
|
|
|
Endpoints for managing projects.
|
|
subgroup: ''
|
|
subgroupDescription: ''
|
|
title: 'Get a single project'
|
|
description: 'Get details of a specific project by ID.'
|
|
authenticated: true
|
|
deprecated: false
|
|
headers:
|
|
Content-Type: application/json
|
|
Accept: application/json
|
|
urlParameters:
|
|
id:
|
|
custom: []
|
|
name: id
|
|
description: 'Project UUID.'
|
|
required: true
|
|
example: 550e8400-e29b-41d4-a716-446655440000
|
|
type: string
|
|
enumValues: []
|
|
exampleWasSpecified: true
|
|
nullable: false
|
|
deprecated: false
|
|
cleanUrlParameters:
|
|
id: 550e8400-e29b-41d4-a716-446655440000
|
|
queryParameters: []
|
|
cleanQueryParameters: []
|
|
bodyParameters: []
|
|
cleanBodyParameters: []
|
|
fileParameters: []
|
|
responses:
|
|
-
|
|
custom: []
|
|
status: 200
|
|
content: |-
|
|
{
|
|
"data": {
|
|
"id": "550e8400-e29b-41d4-a716-446655440000",
|
|
"code": "PROJ-001",
|
|
"title": "Client Dashboard Redesign",
|
|
"status": {"id": 1, "name": "Pre-sales"},
|
|
"type": {"id": 1, "name": "Project"},
|
|
"approved_estimate": "120.00",
|
|
"forecasted_effort": {"2024-02": 40, "2024-03": 60}
|
|
}
|
|
}
|
|
headers: []
|
|
description: ''
|
|
-
|
|
custom: []
|
|
status: 404
|
|
content: '{"message":"Project not found"}'
|
|
headers: []
|
|
description: ''
|
|
responseFields: []
|
|
auth: []
|
|
controller: null
|
|
method: null
|
|
route: null
|
|
-
|
|
custom: []
|
|
httpMethods:
|
|
- PUT
|
|
- PATCH
|
|
uri: 'api/projects/{id}'
|
|
metadata:
|
|
custom: []
|
|
groupName: Projects
|
|
groupDescription: |-
|
|
|
|
Endpoints for managing projects.
|
|
subgroup: ''
|
|
subgroupDescription: ''
|
|
title: 'Update a project'
|
|
description: 'Update details of an existing project.'
|
|
authenticated: true
|
|
deprecated: false
|
|
headers:
|
|
Content-Type: application/json
|
|
Accept: application/json
|
|
urlParameters:
|
|
id:
|
|
custom: []
|
|
name: id
|
|
description: 'Project UUID.'
|
|
required: true
|
|
example: 550e8400-e29b-41d4-a716-446655440000
|
|
type: string
|
|
enumValues: []
|
|
exampleWasSpecified: true
|
|
nullable: false
|
|
deprecated: false
|
|
cleanUrlParameters:
|
|
id: 550e8400-e29b-41d4-a716-446655440000
|
|
queryParameters: []
|
|
cleanQueryParameters: []
|
|
bodyParameters:
|
|
code:
|
|
custom: []
|
|
name: code
|
|
description: 'Project code (must be unique).'
|
|
required: false
|
|
example: PROJ-002
|
|
type: string
|
|
enumValues: []
|
|
exampleWasSpecified: true
|
|
nullable: false
|
|
deprecated: false
|
|
title:
|
|
custom: []
|
|
name: title
|
|
description: 'Project title.'
|
|
required: false
|
|
example: 'Updated Title'
|
|
type: string
|
|
enumValues: []
|
|
exampleWasSpecified: true
|
|
nullable: false
|
|
deprecated: false
|
|
type_id:
|
|
custom: []
|
|
name: type_id
|
|
description: 'Project type ID.'
|
|
required: false
|
|
example: 2
|
|
type: integer
|
|
enumValues: []
|
|
exampleWasSpecified: true
|
|
nullable: false
|
|
deprecated: false
|
|
cleanBodyParameters:
|
|
code: PROJ-002
|
|
title: 'Updated Title'
|
|
type_id: 2
|
|
fileParameters: []
|
|
responses:
|
|
-
|
|
custom: []
|
|
status: 200
|
|
content: |-
|
|
{
|
|
"data": {
|
|
"id": "550e8400-e29b-41d4-a716-446655440000",
|
|
"code": "PROJ-002",
|
|
"title": "Updated Title",
|
|
"type": {"id": 2, "name": "Support"}
|
|
}
|
|
}
|
|
headers: []
|
|
description: ''
|
|
-
|
|
custom: []
|
|
status: 404
|
|
content: '{"message":"Project not found"}'
|
|
headers: []
|
|
description: ''
|
|
-
|
|
custom: []
|
|
status: 422
|
|
content: '{"message":"Validation failed","errors":{"type_id":["The selected type id is invalid."]}}'
|
|
headers: []
|
|
description: ''
|
|
responseFields: []
|
|
auth: []
|
|
controller: null
|
|
method: null
|
|
route: null
|
|
-
|
|
custom: []
|
|
httpMethods:
|
|
- DELETE
|
|
uri: 'api/projects/{id}'
|
|
metadata:
|
|
custom: []
|
|
groupName: Projects
|
|
groupDescription: |-
|
|
|
|
Endpoints for managing projects.
|
|
subgroup: ''
|
|
subgroupDescription: ''
|
|
title: 'Delete a project'
|
|
description: 'Delete a project. Cannot delete if project has allocations or actuals.'
|
|
authenticated: true
|
|
deprecated: false
|
|
headers:
|
|
Content-Type: application/json
|
|
Accept: application/json
|
|
urlParameters:
|
|
id:
|
|
custom: []
|
|
name: id
|
|
description: 'Project UUID.'
|
|
required: true
|
|
example: 550e8400-e29b-41d4-a716-446655440000
|
|
type: string
|
|
enumValues: []
|
|
exampleWasSpecified: true
|
|
nullable: false
|
|
deprecated: false
|
|
cleanUrlParameters:
|
|
id: 550e8400-e29b-41d4-a716-446655440000
|
|
queryParameters: []
|
|
cleanQueryParameters: []
|
|
bodyParameters: []
|
|
cleanBodyParameters: []
|
|
fileParameters: []
|
|
responses:
|
|
-
|
|
custom: []
|
|
status: 200
|
|
content: '{"message":"Project deleted successfully"}'
|
|
headers: []
|
|
description: ''
|
|
-
|
|
custom: []
|
|
status: 404
|
|
content: '{"message":"Project not found"}'
|
|
headers: []
|
|
description: ''
|
|
-
|
|
custom: []
|
|
status: 422
|
|
content: '{"message":"Cannot delete project with allocations"}'
|
|
headers: []
|
|
description: ''
|
|
responseFields: []
|
|
auth: []
|
|
controller: null
|
|
method: null
|
|
route: null
|
|
-
|
|
custom: []
|
|
httpMethods:
|
|
- PUT
|
|
uri: 'api/projects/{project}/status'
|
|
metadata:
|
|
custom: []
|
|
groupName: Projects
|
|
groupDescription: |-
|
|
|
|
Endpoints for managing projects.
|
|
subgroup: ''
|
|
subgroupDescription: ''
|
|
title: 'Transition project status'
|
|
description: 'Transition project to a new status following the state machine rules.'
|
|
authenticated: true
|
|
deprecated: false
|
|
headers:
|
|
Content-Type: application/json
|
|
Accept: application/json
|
|
urlParameters:
|
|
project:
|
|
custom: []
|
|
name: project
|
|
description: 'The project.'
|
|
required: true
|
|
example: architecto
|
|
type: string
|
|
enumValues: []
|
|
exampleWasSpecified: false
|
|
nullable: false
|
|
deprecated: false
|
|
id:
|
|
custom: []
|
|
name: id
|
|
description: 'Project UUID.'
|
|
required: true
|
|
example: 550e8400-e29b-41d4-a716-446655440000
|
|
type: string
|
|
enumValues: []
|
|
exampleWasSpecified: true
|
|
nullable: false
|
|
deprecated: false
|
|
cleanUrlParameters:
|
|
project: architecto
|
|
id: 550e8400-e29b-41d4-a716-446655440000
|
|
queryParameters: []
|
|
cleanQueryParameters: []
|
|
bodyParameters:
|
|
status_id:
|
|
custom: []
|
|
name: status_id
|
|
description: 'Target status ID.'
|
|
required: true
|
|
example: 2
|
|
type: integer
|
|
enumValues: []
|
|
exampleWasSpecified: true
|
|
nullable: false
|
|
deprecated: false
|
|
cleanBodyParameters:
|
|
status_id: 2
|
|
fileParameters: []
|
|
responses:
|
|
-
|
|
custom: []
|
|
status: 200
|
|
content: |-
|
|
{
|
|
"data": {
|
|
"id": "550e8400-e29b-41d4-a716-446655440000",
|
|
"status": {"id": 2, "name": "SOW Approval"}
|
|
}
|
|
}
|
|
headers: []
|
|
description: ''
|
|
-
|
|
custom: []
|
|
status: 404
|
|
content: '{"message":"Project not found"}'
|
|
headers: []
|
|
description: ''
|
|
-
|
|
custom: []
|
|
status: 422
|
|
content: '{"message":"Cannot transition from Pre-sales to Done"}'
|
|
headers: []
|
|
description: ''
|
|
responseFields: []
|
|
auth: []
|
|
controller: null
|
|
method: null
|
|
route: null
|
|
-
|
|
custom: []
|
|
httpMethods:
|
|
- PUT
|
|
uri: 'api/projects/{project}/estimate'
|
|
metadata:
|
|
custom: []
|
|
groupName: Projects
|
|
groupDescription: |-
|
|
|
|
Endpoints for managing projects.
|
|
subgroup: ''
|
|
subgroupDescription: ''
|
|
title: 'Set approved estimate'
|
|
description: 'Set the approved billable hours estimate for a project.'
|
|
authenticated: true
|
|
deprecated: false
|
|
headers:
|
|
Content-Type: application/json
|
|
Accept: application/json
|
|
urlParameters:
|
|
project:
|
|
custom: []
|
|
name: project
|
|
description: 'The project.'
|
|
required: true
|
|
example: architecto
|
|
type: string
|
|
enumValues: []
|
|
exampleWasSpecified: false
|
|
nullable: false
|
|
deprecated: false
|
|
id:
|
|
custom: []
|
|
name: id
|
|
description: 'Project UUID.'
|
|
required: true
|
|
example: 550e8400-e29b-41d4-a716-446655440000
|
|
type: string
|
|
enumValues: []
|
|
exampleWasSpecified: true
|
|
nullable: false
|
|
deprecated: false
|
|
cleanUrlParameters:
|
|
project: architecto
|
|
id: 550e8400-e29b-41d4-a716-446655440000
|
|
queryParameters: []
|
|
cleanQueryParameters: []
|
|
bodyParameters:
|
|
approved_estimate:
|
|
custom: []
|
|
name: approved_estimate
|
|
description: 'Approved estimate hours (must be > 0).'
|
|
required: true
|
|
example: 120.0
|
|
type: number
|
|
enumValues: []
|
|
exampleWasSpecified: true
|
|
nullable: false
|
|
deprecated: false
|
|
cleanBodyParameters:
|
|
approved_estimate: 120.0
|
|
fileParameters: []
|
|
responses:
|
|
-
|
|
custom: []
|
|
status: 200
|
|
content: |-
|
|
{
|
|
"data": {
|
|
"id": "550e8400-e29b-41d4-a716-446655440000",
|
|
"approved_estimate": "120.00"
|
|
}
|
|
}
|
|
headers: []
|
|
description: ''
|
|
-
|
|
custom: []
|
|
status: 404
|
|
content: '{"message":"Project not found"}'
|
|
headers: []
|
|
description: ''
|
|
-
|
|
custom: []
|
|
status: 422
|
|
content: '{"message":"Approved estimate must be greater than 0"}'
|
|
headers: []
|
|
description: ''
|
|
responseFields: []
|
|
auth: []
|
|
controller: null
|
|
method: null
|
|
route: null
|
|
-
|
|
custom: []
|
|
httpMethods:
|
|
- PUT
|
|
uri: 'api/projects/{project}/forecast'
|
|
metadata:
|
|
custom: []
|
|
groupName: Projects
|
|
groupDescription: |-
|
|
|
|
Endpoints for managing projects.
|
|
subgroup: ''
|
|
subgroupDescription: ''
|
|
title: 'Set forecasted effort'
|
|
description: 'Set the month-by-month forecasted effort breakdown.'
|
|
authenticated: true
|
|
deprecated: false
|
|
headers:
|
|
Content-Type: application/json
|
|
Accept: application/json
|
|
urlParameters:
|
|
project:
|
|
custom: []
|
|
name: project
|
|
description: 'The project.'
|
|
required: true
|
|
example: architecto
|
|
type: string
|
|
enumValues: []
|
|
exampleWasSpecified: false
|
|
nullable: false
|
|
deprecated: false
|
|
id:
|
|
custom: []
|
|
name: id
|
|
description: 'Project UUID.'
|
|
required: true
|
|
example: 550e8400-e29b-41d4-a716-446655440000
|
|
type: string
|
|
enumValues: []
|
|
exampleWasSpecified: true
|
|
nullable: false
|
|
deprecated: false
|
|
cleanUrlParameters:
|
|
project: architecto
|
|
id: 550e8400-e29b-41d4-a716-446655440000
|
|
queryParameters: []
|
|
cleanQueryParameters: []
|
|
bodyParameters:
|
|
forecasted_effort:
|
|
custom: []
|
|
name: forecasted_effort
|
|
description: 'Monthly effort breakdown.'
|
|
required: true
|
|
example:
|
|
2024-02: 40
|
|
2024-03: 60
|
|
type: object
|
|
enumValues: []
|
|
exampleWasSpecified: true
|
|
nullable: false
|
|
deprecated: false
|
|
cleanBodyParameters:
|
|
forecasted_effort:
|
|
2024-02: 40
|
|
2024-03: 60
|
|
fileParameters: []
|
|
responses:
|
|
-
|
|
custom: []
|
|
status: 200
|
|
content: |-
|
|
{
|
|
"data": {
|
|
"id": "550e8400-e29b-41d4-a716-446655440000",
|
|
"forecasted_effort": {"2024-02": 40, "2024-03": 60}
|
|
}
|
|
}
|
|
headers: []
|
|
description: ''
|
|
-
|
|
custom: []
|
|
status: 404
|
|
content: '{"message":"Project not found"}'
|
|
headers: []
|
|
description: ''
|
|
-
|
|
custom: []
|
|
status: 422
|
|
content: '{"message":"Forecasted effort exceeds approved estimate by more than 5%"}'
|
|
headers: []
|
|
description: ''
|
|
responseFields: []
|
|
auth: []
|
|
controller: null
|
|
method: null
|
|
route: null
|