diff --git a/.ralph/ralph-loop.state.json b/.ralph/ralph-loop.state.json
deleted file mode 100644
index c78a670e..00000000
--- a/.ralph/ralph-loop.state.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "active": true,
- "iteration": 10,
- "minIterations": 1,
- "maxIterations": 10,
- "completionPromise": "COMPLETE",
- "tasksMode": false,
- "taskPromise": "READY_FOR_NEXT_TASK",
- "prompt": "Test the changes made by running the scribe command and the swagger is working fine. In case any issues found, fix and retest until the issue is resolved. once that is done, /opsx-verify, /opsx-sync and /opsx-archive. Then commit the code. Attempt a push, if failed, leave it for me. id of an existing record in the projects table.'
+ required: true
+ example: 6ff8f7f6-1eb3-3525-be4a-3932c805afed
+ type: string
enumValues: []
- exampleWasSpecified: true
+ exampleWasSpecified: false
nullable: false
deprecated: false
+ 'items[].month':
+ custom: []
+ name: 'items[].month'
+ description: 'Must be a valid date in the format Y-m.'
+ required: true
+ example: 2026-02
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ 'items[].planned_hours':
+ custom: []
+ name: 'items[].planned_hours'
+ description: 'Must be at least 0.'
+ required: false
+ example: 84
+ type: number
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: true
+ deprecated: false
cleanBodyParameters:
- name: 'John Doe'
- role_id: 1
- hourly_rate: '175.00'
- active: false
+ year: 1
+ items:
+ -
+ project_id: 6ff8f7f6-1eb3-3525-be4a-3932c805afed
+ month: 2026-02
+ planned_hours: 84
fileParameters: []
- responses:
- -
- custom: []
- status: 200
- content: |-
- {
- "data": {
- "id": "550e8400-e29b-41d4-a716-446655440000",
- "name": "John Doe",
- "role": {
- "id": 1,
- "name": "Backend Developer"
- },
- "hourly_rate": "175.00",
- "active": false,
- "created_at": "2024-01-15T10:00:00.000000Z",
- "updated_at": "2024-01-15T11:00:00.000000Z"
- }
- }
- headers: []
- description: ''
- -
- custom: []
- status: 404
- content: '{"message":"Team member not found"}'
- headers: []
- description: ''
- -
- custom: []
- status: 422
- content: '{"message":"Validation failed","errors":{"hourly_rate":["Hourly rate must be greater than 0"]}}'
- headers: []
- description: ''
+ responses: []
responseFields: []
auth: []
controller: null
@@ -380,18 +184,16 @@ endpoints:
custom: []
httpMethods:
- DELETE
- uri: 'api/team-members/{id}'
+ uri: 'api/ptos/{id}'
metadata:
custom: []
- groupName: 'Team Members'
- groupDescription: |-
-
- Endpoints for managing team members.
+ groupName: Endpoints
+ groupDescription: ''
subgroup: ''
subgroupDescription: ''
- title: 'Delete a team member'
- description: 'Delete a team member. Cannot delete if member has allocations or actuals.'
- authenticated: true
+ title: ''
+ description: ''
+ authenticated: false
deprecated: false
headers:
Content-Type: application/json
@@ -400,46 +202,22 @@ endpoints:
id:
custom: []
name: id
- description: 'Team member UUID.'
+ description: 'The ID of the pto.'
required: true
- example: 550e8400-e29b-41d4-a716-446655440000
+ example: architecto
type: string
enumValues: []
- exampleWasSpecified: true
+ exampleWasSpecified: false
nullable: false
deprecated: false
cleanUrlParameters:
- id: 550e8400-e29b-41d4-a716-446655440000
+ id: architecto
queryParameters: []
cleanQueryParameters: []
bodyParameters: []
cleanBodyParameters: []
fileParameters: []
- responses:
- -
- custom: []
- status: 200
- content: '{"message":"Team member deleted successfully"}'
- headers: []
- description: ''
- -
- custom: []
- status: 404
- content: '{"message":"Team member not found"}'
- headers: []
- description: ''
- -
- custom: []
- status: 422
- content: '{"message":"Cannot delete team member with active allocations","suggestion":"Consider deactivating the team member instead"}'
- headers: []
- description: ''
- -
- custom: []
- status: 422
- content: '{"message":"Cannot delete team member with historical data","suggestion":"Consider deactivating the team member instead"}'
- headers: []
- description: ''
+ responses: []
responseFields: []
auth: []
controller: null
diff --git a/backend/.scribe/endpoints.cache/02.yaml b/backend/.scribe/endpoints.cache/02.yaml
index 018b0b8a..0f06fc4e 100644
--- a/backend/.scribe/endpoints.cache/02.yaml
+++ b/backend/.scribe/endpoints.cache/02.yaml
@@ -1,117 +1,25 @@
## Autogenerated by Scribe. DO NOT MODIFY.
-name: Projects
+name: 'Team Members'
description: |-
- Endpoints for managing projects.
+ Endpoints for managing team members.
endpoints:
-
custom: []
httpMethods:
- GET
- uri: api/projects/types
+ uri: api/team-members
metadata:
custom: []
- groupName: Projects
+ groupName: 'Team Members'
groupDescription: |-
- Endpoints for managing projects.
+ Endpoints for managing team members.
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.'
+ title: 'List all team members'
+ description: 'Get a list of all team members with optional filtering by active status.'
authenticated: true
deprecated: false
headers:
@@ -120,31 +28,19 @@ endpoints:
urlParameters: []
cleanUrlParameters: []
queryParameters:
- status_id:
+ active:
custom: []
- name: status_id
- description: 'Filter by status ID.'
+ name: active
+ description: 'Filter by active status.'
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
+ example: true
+ type: boolean
enumValues: []
exampleWasSpecified: true
nullable: false
deprecated: false
cleanQueryParameters:
- status_id: 1
- type_id: 2
+ active: true
bodyParameters: []
cleanBodyParameters: []
fileParameters: []
@@ -157,12 +53,13 @@ endpoints:
"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},
+ "name": "John Doe",
+ "role": {
+ "id": 1,
+ "name": "Backend Developer"
+ },
+ "hourly_rate": "150.00",
+ "active": true,
"created_at": "2024-01-15T10:00:00.000000Z",
"updated_at": "2024-01-15T10:00:00.000000Z"
}
@@ -179,17 +76,17 @@ endpoints:
custom: []
httpMethods:
- POST
- uri: api/projects
+ uri: api/team-members
metadata:
custom: []
- groupName: Projects
+ groupName: 'Team Members'
groupDescription: |-
- Endpoints for managing projects.
+ Endpoints for managing team members.
subgroup: ''
subgroupDescription: ''
- title: 'Create a new project'
- description: 'Create a new project with code, title, and type.'
+ title: 'Create a new team member'
+ description: 'Create a new team member with name, role, and hourly rate.'
authenticated: true
deprecated: false
headers:
@@ -200,32 +97,21 @@ endpoints:
queryParameters: []
cleanQueryParameters: []
bodyParameters:
- code:
+ name:
custom: []
- name: code
- description: 'Project code (must be unique).'
+ name: name
+ description: 'Team member name.'
required: true
- example: PROJ-001
+ example: 'John Doe'
type: string
enumValues: []
exampleWasSpecified: true
nullable: false
deprecated: false
- title:
+ role_id:
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.'
+ name: role_id
+ description: 'Role ID.'
required: true
example: 1
type: integer
@@ -233,10 +119,33 @@ endpoints:
exampleWasSpecified: true
nullable: false
deprecated: false
+ hourly_rate:
+ custom: []
+ name: hourly_rate
+ description: 'Hourly rate (must be > 0).'
+ required: true
+ example: '150.00'
+ type: numeric
+ enumValues: []
+ exampleWasSpecified: true
+ nullable: false
+ deprecated: false
+ active:
+ custom: []
+ name: active
+ description: 'Active status (defaults to true).'
+ required: false
+ example: true
+ type: boolean
+ enumValues: []
+ exampleWasSpecified: true
+ nullable: false
+ deprecated: false
cleanBodyParameters:
- code: PROJ-001
- title: 'Client Dashboard Redesign'
- type_id: 1
+ name: 'John Doe'
+ role_id: 1
+ hourly_rate: '150.00'
+ active: true
fileParameters: []
responses:
-
@@ -246,10 +155,15 @@ endpoints:
{
"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"}
+ "name": "John Doe",
+ "role": {
+ "id": 1,
+ "name": "Backend Developer"
+ },
+ "hourly_rate": "150.00",
+ "active": true,
+ "created_at": "2024-01-15T10:00:00.000000Z",
+ "updated_at": "2024-01-15T10:00:00.000000Z"
}
}
headers: []
@@ -257,7 +171,7 @@ endpoints:
-
custom: []
status: 422
- content: '{"message":"Validation failed","errors":{"code":["Project code must be unique"],"title":["The title field is required."]}}'
+ content: '{"message":"Validation failed","errors":{"name":["The name field is required."],"hourly_rate":["Hourly rate must be greater than 0"]}}'
headers: []
description: ''
responseFields: []
@@ -269,17 +183,17 @@ endpoints:
custom: []
httpMethods:
- GET
- uri: 'api/projects/{id}'
+ uri: 'api/team-members/{id}'
metadata:
custom: []
- groupName: Projects
+ groupName: 'Team Members'
groupDescription: |-
- Endpoints for managing projects.
+ Endpoints for managing team members.
subgroup: ''
subgroupDescription: ''
- title: 'Get a single project'
- description: 'Get details of a specific project by ID.'
+ title: 'Get a single team member'
+ description: 'Get details of a specific team member by ID.'
authenticated: true
deprecated: false
headers:
@@ -289,7 +203,7 @@ endpoints:
id:
custom: []
name: id
- description: 'Project UUID.'
+ description: 'Team member UUID.'
required: true
example: 550e8400-e29b-41d4-a716-446655440000
type: string
@@ -312,12 +226,15 @@ endpoints:
{
"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}
+ "name": "John Doe",
+ "role": {
+ "id": 1,
+ "name": "Backend Developer"
+ },
+ "hourly_rate": "150.00",
+ "active": true,
+ "created_at": "2024-01-15T10:00:00.000000Z",
+ "updated_at": "2024-01-15T10:00:00.000000Z"
}
}
headers: []
@@ -325,7 +242,7 @@ endpoints:
-
custom: []
status: 404
- content: '{"message":"Project not found"}'
+ content: '{"message":"Team member not found"}'
headers: []
description: ''
responseFields: []
@@ -338,17 +255,17 @@ endpoints:
httpMethods:
- PUT
- PATCH
- uri: 'api/projects/{id}'
+ uri: 'api/team-members/{id}'
metadata:
custom: []
- groupName: Projects
+ groupName: 'Team Members'
groupDescription: |-
- Endpoints for managing projects.
+ Endpoints for managing team members.
subgroup: ''
subgroupDescription: ''
- title: 'Update a project'
- description: 'Update details of an existing project.'
+ title: 'Update a team member'
+ description: 'Update details of an existing team member.'
authenticated: true
deprecated: false
headers:
@@ -358,7 +275,7 @@ endpoints:
id:
custom: []
name: id
- description: 'Project UUID.'
+ description: 'Team member UUID.'
required: true
example: 550e8400-e29b-41d4-a716-446655440000
type: string
@@ -371,43 +288,55 @@ endpoints:
queryParameters: []
cleanQueryParameters: []
bodyParameters:
- code:
+ name:
custom: []
- name: code
- description: 'Project code (must be unique).'
+ name: name
+ description: 'Team member name.'
required: false
- example: PROJ-002
+ example: 'John Doe'
type: string
enumValues: []
exampleWasSpecified: true
nullable: false
deprecated: false
- title:
+ role_id:
custom: []
- name: title
- description: 'Project title.'
+ name: role_id
+ description: 'Role ID.'
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
+ example: 1
type: integer
enumValues: []
exampleWasSpecified: true
nullable: false
deprecated: false
+ hourly_rate:
+ custom: []
+ name: hourly_rate
+ description: 'Hourly rate (must be > 0).'
+ required: false
+ example: '175.00'
+ type: numeric
+ enumValues: []
+ exampleWasSpecified: true
+ nullable: false
+ deprecated: false
+ active:
+ custom: []
+ name: active
+ description: 'Active status.'
+ required: false
+ example: false
+ type: boolean
+ enumValues: []
+ exampleWasSpecified: true
+ nullable: false
+ deprecated: false
cleanBodyParameters:
- code: PROJ-002
- title: 'Updated Title'
- type_id: 2
+ name: 'John Doe'
+ role_id: 1
+ hourly_rate: '175.00'
+ active: false
fileParameters: []
responses:
-
@@ -417,9 +346,15 @@ endpoints:
{
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
- "code": "PROJ-002",
- "title": "Updated Title",
- "type": {"id": 2, "name": "Support"}
+ "name": "John Doe",
+ "role": {
+ "id": 1,
+ "name": "Backend Developer"
+ },
+ "hourly_rate": "175.00",
+ "active": false,
+ "created_at": "2024-01-15T10:00:00.000000Z",
+ "updated_at": "2024-01-15T11:00:00.000000Z"
}
}
headers: []
@@ -427,13 +362,13 @@ endpoints:
-
custom: []
status: 404
- content: '{"message":"Project not found"}'
+ content: '{"message":"Team member not found"}'
headers: []
description: ''
-
custom: []
status: 422
- content: '{"message":"Validation failed","errors":{"type_id":["The selected type id is invalid."]}}'
+ content: '{"message":"Validation failed","errors":{"hourly_rate":["Hourly rate must be greater than 0"]}}'
headers: []
description: ''
responseFields: []
@@ -445,17 +380,17 @@ endpoints:
custom: []
httpMethods:
- DELETE
- uri: 'api/projects/{id}'
+ uri: 'api/team-members/{id}'
metadata:
custom: []
- groupName: Projects
+ groupName: 'Team Members'
groupDescription: |-
- Endpoints for managing projects.
+ Endpoints for managing team members.
subgroup: ''
subgroupDescription: ''
- title: 'Delete a project'
- description: 'Delete a project. Cannot delete if project has allocations or actuals.'
+ title: 'Delete a team member'
+ description: 'Delete a team member. Cannot delete if member has allocations or actuals.'
authenticated: true
deprecated: false
headers:
@@ -465,7 +400,7 @@ endpoints:
id:
custom: []
name: id
- description: 'Project UUID.'
+ description: 'Team member UUID.'
required: true
example: 550e8400-e29b-41d4-a716-446655440000
type: string
@@ -484,302 +419,25 @@ endpoints:
-
custom: []
status: 200
- content: '{"message":"Project deleted successfully"}'
+ content: '{"message":"Team member deleted successfully"}'
headers: []
description: ''
-
custom: []
status: 404
- content: '{"message":"Project not found"}'
+ content: '{"message":"Team member 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%"}'
+ content: '{"message":"Cannot delete team member with active allocations","suggestion":"Consider deactivating the team member instead"}'
+ headers: []
+ description: ''
+ -
+ custom: []
+ status: 422
+ content: '{"message":"Cannot delete team member with historical data","suggestion":"Consider deactivating the team member instead"}'
headers: []
description: ''
responseFields: []
diff --git a/backend/.scribe/endpoints.cache/03.yaml b/backend/.scribe/endpoints.cache/03.yaml
index e3c2cadc..018b0b8a 100644
--- a/backend/.scribe/endpoints.cache/03.yaml
+++ b/backend/.scribe/endpoints.cache/03.yaml
@@ -1,80 +1,36 @@
## Autogenerated by Scribe. DO NOT MODIFY.
-name: 'Capacity Planning'
-description: ''
+name: Projects
+description: |-
+
+ Endpoints for managing projects.
endpoints:
-
custom: []
httpMethods:
- GET
- uri: api/capacity
+ uri: api/projects/types
metadata:
custom: []
- groupName: 'Capacity Planning'
- groupDescription: ''
+ groupName: Projects
+ groupDescription: |-
+
+ Endpoints for managing projects.
subgroup: ''
subgroupDescription: ''
- title: 'Get Individual Capacity'
- description: 'Calculate capacity for a specific team member in a given month.'
- authenticated: false
+ title: 'Get all project types'
+ description: ''
+ authenticated: true
deprecated: false
headers:
Content-Type: application/json
Accept: application/json
- urlParameters:
- month:
- custom: []
- name: month
- description: 'The month in YYYY-MM format.'
- required: true
- example: 2026-02
- type: string
- enumValues: []
- exampleWasSpecified: true
- nullable: false
- deprecated: false
- team_member_id:
- custom: []
- name: team_member_id
- description: 'The team member UUID.'
- required: true
- example: 550e8400-e29b-41d4-a716-446655440000
- type: string
- enumValues: []
- exampleWasSpecified: true
- nullable: false
- deprecated: false
- cleanUrlParameters:
- month: 2026-02
- team_member_id: 550e8400-e29b-41d4-a716-446655440000
+ urlParameters: []
+ cleanUrlParameters: []
queryParameters: []
cleanQueryParameters: []
- bodyParameters:
- month:
- custom: []
- name: month
- description: 'Must be a valid date in the format Y-m.'
- required: true
- example: 2026-02
- type: string
- enumValues: []
- exampleWasSpecified: false
- nullable: false
- deprecated: false
- team_member_id:
- custom: []
- name: team_member_id
- description: 'The id of an existing record in the team_members table.'
- required: true
- example: architecto
- type: string
- enumValues: []
- exampleWasSpecified: false
- nullable: false
- deprecated: false
- cleanBodyParameters:
- month: 2026-02
- team_member_id: architecto
+ bodyParameters: []
+ cleanBodyParameters: []
fileParameters: []
responses:
-
@@ -82,20 +38,11 @@ endpoints:
status: 200
content: |-
{
- "data": {
- "team_member_id": "550e8400-e29b-41d4-a716-446655440000",
- "month": "2026-02",
- "working_days": 20,
- "person_days": 18.5,
- "hours": 148,
- "details": [
- {
- "date": "2026-02-02",
- "availability": 1,
- "is_pto": false
- }
- ]
- }
+ "data": [
+ {"id": 1, "name": "Project"},
+ {"id": 2, "name": "Support"},
+ {"id": 3, "name": "Engagement"}
+ ]
}
headers: []
description: ''
@@ -108,50 +55,28 @@ endpoints:
custom: []
httpMethods:
- GET
- uri: api/capacity/team
+ uri: api/projects/statuses
metadata:
custom: []
- groupName: 'Capacity Planning'
- groupDescription: ''
+ groupName: Projects
+ groupDescription: |-
+
+ Endpoints for managing projects.
subgroup: ''
subgroupDescription: ''
- title: 'Get Team Capacity'
- description: 'Summarize the combined capacity for all active team members in a month.'
- authenticated: false
+ title: 'Get all project statuses'
+ description: ''
+ authenticated: true
deprecated: false
headers:
Content-Type: application/json
Accept: application/json
- urlParameters:
- month:
- custom: []
- name: month
- description: 'The month in YYYY-MM format.'
- required: true
- example: 2026-02
- type: string
- enumValues: []
- exampleWasSpecified: true
- nullable: false
- deprecated: false
- cleanUrlParameters:
- month: 2026-02
+ urlParameters: []
+ cleanUrlParameters: []
queryParameters: []
cleanQueryParameters: []
- bodyParameters:
- month:
- custom: []
- name: month
- description: 'Must be a valid date in the format Y-m.'
- required: true
- example: 2026-02
- type: string
- enumValues: []
- exampleWasSpecified: false
- nullable: false
- deprecated: false
- cleanBodyParameters:
- month: 2026-02
+ bodyParameters: []
+ cleanBodyParameters: []
fileParameters: []
responses:
-
@@ -159,19 +84,11 @@ endpoints:
status: 200
content: |-
{
- "data": {
- "month": "2026-02",
- "total_person_days": 180.5,
- "total_hours": 1444,
- "members": [
- {
- "id": "550e8400-e29b-41d4-a716-446655440000",
- "name": "Ada Lovelace",
- "person_days": 18.5,
- "hours": 148
- }
- ]
- }
+ "data": [
+ {"id": 1, "name": "Pre-sales", "order": 1},
+ {"id": 2, "name": "SOW Approval", "order": 2},
+ {"id": 3, "name": "Gathering Estimates", "order": 3}
+ ]
}
headers: []
description: ''
@@ -184,126 +101,52 @@ endpoints:
custom: []
httpMethods:
- GET
- uri: api/capacity/revenue
+ uri: api/projects
metadata:
custom: []
- groupName: 'Capacity Planning'
- groupDescription: ''
+ groupName: Projects
+ groupDescription: |-
+
+ Endpoints for managing projects.
subgroup: ''
subgroupDescription: ''
- title: 'Get Possible Revenue'
- description: 'Estimate monthly revenue based on capacity hours and hourly rates.'
- authenticated: false
+ 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:
- month:
+ urlParameters: []
+ cleanUrlParameters: []
+ queryParameters:
+ status_id:
custom: []
- name: month
- description: 'The month in YYYY-MM format.'
- required: true
- example: 2026-02
- type: string
- enumValues: []
- exampleWasSpecified: true
- nullable: false
- deprecated: false
- cleanUrlParameters:
- month: 2026-02
- queryParameters: []
- cleanQueryParameters: []
- bodyParameters:
- month:
- custom: []
- name: month
- description: 'Must be a valid date in the format Y-m.'
- required: true
- example: 2026-02
- type: string
- enumValues: []
- exampleWasSpecified: false
- nullable: false
- deprecated: false
- cleanBodyParameters:
- month: 2026-02
- fileParameters: []
- responses:
- -
- custom: []
- status: 200
- content: |-
- {
- "data": {
- "month": "2026-02",
- "possible_revenue": 21500.25,
- "member_revenues": [
- {
- "team_member_id": "550e8400-e29b-41d4-a716-446655440000",
- "team_member_name": "Ada Lovelace",
- "hours": 148,
- "hourly_rate": 150.0,
- "revenue": 22200.0
- }
- ]
- }
- }
- headers: []
- description: ''
- responseFields: []
- auth: []
- controller: null
- method: null
- route: null
- -
- custom: []
- httpMethods:
- - GET
- uri: api/holidays
- metadata:
- custom: []
- groupName: 'Capacity Planning'
- groupDescription: ''
- subgroup: ''
- subgroupDescription: ''
- title: 'List Holidays'
- description: 'Retrieve holidays for a specific month or all holidays when no month is provided.'
- authenticated: false
- deprecated: false
- headers:
- Content-Type: application/json
- Accept: application/json
- urlParameters:
- month:
- custom: []
- name: month
- description: 'nullable The month in YYYY-MM format.'
+ name: status_id
+ description: 'Filter by status ID.'
required: false
- example: 2026-02
- type: string
+ example: 1
+ type: integer
enumValues: []
exampleWasSpecified: true
nullable: false
deprecated: false
- cleanUrlParameters:
- month: 2026-02
- queryParameters: []
- cleanQueryParameters: []
- bodyParameters:
- month:
+ type_id:
custom: []
- name: month
- description: 'Must be a valid date in the format Y-m.'
+ name: type_id
+ description: 'Filter by type ID.'
required: false
- example: 2026-02
- type: string
+ example: 2
+ type: integer
enumValues: []
- exampleWasSpecified: false
- nullable: true
+ exampleWasSpecified: true
+ nullable: false
deprecated: false
- cleanBodyParameters:
- month: 2026-02
+ cleanQueryParameters:
+ status_id: 1
+ type_id: 2
+ bodyParameters: []
+ cleanBodyParameters: []
fileParameters: []
responses:
-
@@ -314,9 +157,14 @@ endpoints:
"data": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
- "date": "2026-02-14",
- "name": "Company Holiday",
- "description": "Office closed"
+ "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"
}
]
}
@@ -331,16 +179,18 @@ endpoints:
custom: []
httpMethods:
- POST
- uri: api/holidays
+ uri: api/projects
metadata:
custom: []
- groupName: 'Capacity Planning'
- groupDescription: ''
+ groupName: Projects
+ groupDescription: |-
+
+ Endpoints for managing projects.
subgroup: ''
subgroupDescription: ''
- title: 'Create Holiday'
- description: 'Add a holiday and clear cached capacity data for the related month.'
- authenticated: false
+ title: 'Create a new project'
+ description: 'Create a new project with code, title, and type.'
+ authenticated: true
deprecated: false
headers:
Content-Type: application/json
@@ -350,43 +200,43 @@ endpoints:
queryParameters: []
cleanQueryParameters: []
bodyParameters:
- date:
+ code:
custom: []
- name: date
- description: 'Date of the holiday.'
+ name: code
+ description: 'Project code (must be unique).'
required: true
- example: '2026-02-14'
+ example: PROJ-001
type: string
enumValues: []
exampleWasSpecified: true
nullable: false
deprecated: false
- name:
+ title:
custom: []
- name: name
- description: 'Name of the holiday.'
+ name: title
+ description: 'Project title.'
required: true
- example: "Presidents' Day"
+ example: 'Client Dashboard Redesign'
type: string
enumValues: []
exampleWasSpecified: true
nullable: false
deprecated: false
- description:
+ type_id:
custom: []
- name: description
- description: 'nullable Optional description of the holiday.'
- required: false
- example: 'Eius et animi quos velit et.'
- type: string
+ name: type_id
+ description: 'Project type ID.'
+ required: true
+ example: 1
+ type: integer
enumValues: []
- exampleWasSpecified: false
- nullable: true
+ exampleWasSpecified: true
+ nullable: false
deprecated: false
cleanBodyParameters:
- date: '2026-02-14'
- name: "Presidents' Day"
- description: 'Eius et animi quos velit et.'
+ code: PROJ-001
+ title: 'Client Dashboard Redesign'
+ type_id: 1
fileParameters: []
responses:
-
@@ -396,13 +246,20 @@ endpoints:
{
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
- "date": "2026-02-14",
- "name": "Presidents' Day",
- "description": "Office closed"
+ "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
@@ -411,17 +268,19 @@ endpoints:
-
custom: []
httpMethods:
- - DELETE
- uri: 'api/holidays/{id}'
+ - GET
+ uri: 'api/projects/{id}'
metadata:
custom: []
- groupName: 'Capacity Planning'
- groupDescription: ''
+ groupName: Projects
+ groupDescription: |-
+
+ Endpoints for managing projects.
subgroup: ''
subgroupDescription: ''
- title: 'Delete Holiday'
- description: 'Remove a holiday and clear affected capacity caches.'
- authenticated: false
+ title: 'Get a single project'
+ description: 'Get details of a specific project by ID.'
+ authenticated: true
deprecated: false
headers:
Content-Type: application/json
@@ -430,7 +289,7 @@ endpoints:
id:
custom: []
name: id
- description: 'The holiday UUID.'
+ description: 'Project UUID.'
required: true
example: 550e8400-e29b-41d4-a716-446655440000
type: string
@@ -449,204 +308,26 @@ endpoints:
-
custom: []
status: 200
- content: |-
- {
- "message": "Holiday deleted"
- }
- headers: []
- description: ''
- responseFields: []
- auth: []
- controller: null
- method: null
- route: null
- -
- custom: []
- httpMethods:
- - GET
- uri: api/ptos
- metadata:
- custom: []
- groupName: 'Capacity Planning'
- groupDescription: ''
- subgroup: ''
- subgroupDescription: ''
- title: 'List PTO Requests'
- description: 'Fetch PTO requests for a team member, optionally constrained to a month.'
- authenticated: false
- deprecated: false
- headers:
- Content-Type: application/json
- Accept: application/json
- urlParameters:
- team_member_id:
- custom: []
- name: team_member_id
- description: 'The team member UUID.'
- required: true
- example: 550e8400-e29b-41d4-a716-446655440000
- type: string
- enumValues: []
- exampleWasSpecified: true
- nullable: false
- deprecated: false
- month:
- custom: []
- name: month
- description: 'nullable The month in YYYY-MM format.'
- required: false
- example: 2026-02
- type: string
- enumValues: []
- exampleWasSpecified: true
- nullable: false
- deprecated: false
- cleanUrlParameters:
- team_member_id: 550e8400-e29b-41d4-a716-446655440000
- month: 2026-02
- queryParameters: []
- cleanQueryParameters: []
- bodyParameters:
- team_member_id:
- custom: []
- name: team_member_id
- description: 'The id of an existing record in the team_members table.'
- required: true
- example: architecto
- type: string
- enumValues: []
- exampleWasSpecified: false
- nullable: false
- deprecated: false
- month:
- custom: []
- name: month
- description: 'Must be a valid date in the format Y-m.'
- required: false
- example: 2026-02
- type: string
- enumValues: []
- exampleWasSpecified: false
- nullable: true
- deprecated: false
- cleanBodyParameters:
- team_member_id: architecto
- month: 2026-02
- fileParameters: []
- responses:
- -
- custom: []
- status: 200
- content: |-
- {
- "data": [
- {
- "id": "550e8400-e29b-41d4-a716-446655440001",
- "team_member_id": "550e8400-e29b-41d4-a716-446655440000",
- "start_date": "2026-02-10",
- "end_date": "2026-02-12",
- "status": "pending",
- "reason": "Family travel"
- }
- ]
- }
- headers: []
- description: ''
- responseFields: []
- auth: []
- controller: null
- method: null
- route: null
- -
- custom: []
- httpMethods:
- - POST
- uri: api/ptos
- metadata:
- custom: []
- groupName: 'Capacity Planning'
- groupDescription: ''
- subgroup: ''
- subgroupDescription: ''
- title: 'Request PTO'
- description: 'Create a PTO request for a team member and keep it in pending status.'
- authenticated: false
- deprecated: false
- headers:
- Content-Type: application/json
- Accept: application/json
- urlParameters: []
- cleanUrlParameters: []
- queryParameters: []
- cleanQueryParameters: []
- bodyParameters:
- team_member_id:
- custom: []
- name: team_member_id
- description: 'The team member UUID.'
- required: true
- example: 550e8400-e29b-41d4-a716-446655440000
- type: string
- enumValues: []
- exampleWasSpecified: true
- nullable: false
- deprecated: false
- start_date:
- custom: []
- name: start_date
- description: 'The first day of the PTO.'
- required: true
- example: '2026-02-10'
- type: string
- enumValues: []
- exampleWasSpecified: true
- nullable: false
- deprecated: false
- end_date:
- custom: []
- name: end_date
- description: 'The final day of the PTO.'
- required: true
- example: '2026-02-12'
- type: string
- enumValues: []
- exampleWasSpecified: true
- nullable: false
- deprecated: false
- reason:
- custom: []
- name: reason
- description: 'nullable Optional reason for the PTO.'
- required: false
- example: architecto
- type: string
- enumValues: []
- exampleWasSpecified: false
- nullable: true
- deprecated: false
- cleanBodyParameters:
- team_member_id: 550e8400-e29b-41d4-a716-446655440000
- start_date: '2026-02-10'
- end_date: '2026-02-12'
- reason: architecto
- fileParameters: []
- responses:
- -
- custom: []
- status: 201
content: |-
{
"data": {
- "id": "550e8400-e29b-41d4-a716-446655440001",
- "team_member_id": "550e8400-e29b-41d4-a716-446655440000",
- "start_date": "2026-02-10",
- "end_date": "2026-02-12",
- "status": "pending",
- "reason": "Family travel"
+ "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
@@ -656,16 +337,19 @@ endpoints:
custom: []
httpMethods:
- PUT
- uri: 'api/ptos/{id}/approve'
+ - PATCH
+ uri: 'api/projects/{id}'
metadata:
custom: []
- groupName: 'Capacity Planning'
- groupDescription: ''
+ groupName: Projects
+ groupDescription: |-
+
+ Endpoints for managing projects.
subgroup: ''
subgroupDescription: ''
- title: 'Approve PTO'
- description: 'Approve a pending PTO request and refresh the affected capacity caches.'
- authenticated: false
+ title: 'Update a project'
+ description: 'Update details of an existing project.'
+ authenticated: true
deprecated: false
headers:
Content-Type: application/json
@@ -674,16 +358,123 @@ endpoints:
id:
custom: []
name: id
- description: 'The PTO UUID that needs approval.'
+ description: 'Project UUID.'
required: true
- example: 550e8400-e29b-41d4-a716-446655440001
+ example: 550e8400-e29b-41d4-a716-446655440000
type: string
enumValues: []
exampleWasSpecified: true
nullable: false
deprecated: false
cleanUrlParameters:
- id: 550e8400-e29b-41d4-a716-446655440001
+ 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: []
@@ -693,13 +484,302 @@ endpoints:
-
custom: []
status: 200
- content: |-
- {
- "data": {
- "id": "550e8400-e29b-41d4-a716-446655440001",
- "status": "approved"
- }
- }
+ 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: []
diff --git a/backend/.scribe/endpoints.cache/04.yaml b/backend/.scribe/endpoints.cache/04.yaml
new file mode 100644
index 00000000..a958df1e
--- /dev/null
+++ b/backend/.scribe/endpoints.cache/04.yaml
@@ -0,0 +1,897 @@
+## Autogenerated by Scribe. DO NOT MODIFY.
+
+name: 'Capacity Planning'
+description: ''
+endpoints:
+ -
+ custom: []
+ httpMethods:
+ - GET
+ uri: api/capacity
+ metadata:
+ custom: []
+ groupName: 'Capacity Planning'
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: 'Get Individual Capacity'
+ description: 'Calculate capacity for a specific team member in a given month.'
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters:
+ month:
+ custom: []
+ name: month
+ description: 'The month in YYYY-MM format.'
+ required: true
+ example: 2026-02
+ type: string
+ enumValues: []
+ exampleWasSpecified: true
+ nullable: false
+ deprecated: false
+ team_member_id:
+ custom: []
+ name: team_member_id
+ description: 'The team member UUID.'
+ required: true
+ example: 550e8400-e29b-41d4-a716-446655440000
+ type: string
+ enumValues: []
+ exampleWasSpecified: true
+ nullable: false
+ deprecated: false
+ cleanUrlParameters:
+ month: 2026-02
+ team_member_id: 550e8400-e29b-41d4-a716-446655440000
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters:
+ month:
+ custom: []
+ name: month
+ description: 'Must be a valid date in the format Y-m.'
+ required: true
+ example: 2026-02
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ team_member_id:
+ custom: []
+ name: team_member_id
+ description: 'The id of an existing record in the team_members table.'
+ required: true
+ example: architecto
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ cleanBodyParameters:
+ month: 2026-02
+ team_member_id: architecto
+ fileParameters: []
+ responses:
+ -
+ custom: []
+ status: 200
+ content: |-
+ {
+ "data": {
+ "team_member_id": "550e8400-e29b-41d4-a716-446655440000",
+ "month": "2026-02",
+ "working_days": 20,
+ "person_days": 18.5,
+ "hours": 148,
+ "details": [
+ {
+ "date": "2026-02-02",
+ "availability": 1,
+ "is_pto": false
+ }
+ ]
+ }
+ }
+ headers: []
+ description: ''
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - GET
+ uri: api/capacity/team
+ metadata:
+ custom: []
+ groupName: 'Capacity Planning'
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: 'Get Team Capacity'
+ description: 'Summarize the combined capacity for all active team members in a month.'
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters:
+ month:
+ custom: []
+ name: month
+ description: 'The month in YYYY-MM format.'
+ required: true
+ example: 2026-02
+ type: string
+ enumValues: []
+ exampleWasSpecified: true
+ nullable: false
+ deprecated: false
+ cleanUrlParameters:
+ month: 2026-02
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters:
+ month:
+ custom: []
+ name: month
+ description: 'Must be a valid date in the format Y-m.'
+ required: true
+ example: 2026-02
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ cleanBodyParameters:
+ month: 2026-02
+ fileParameters: []
+ responses:
+ -
+ custom: []
+ status: 200
+ content: |-
+ {
+ "data": {
+ "month": "2026-02",
+ "total_person_days": 180.5,
+ "total_hours": 1444,
+ "members": [
+ {
+ "id": "550e8400-e29b-41d4-a716-446655440000",
+ "name": "Ada Lovelace",
+ "person_days": 18.5,
+ "hours": 148
+ }
+ ]
+ }
+ }
+ headers: []
+ description: ''
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - GET
+ uri: api/capacity/revenue
+ metadata:
+ custom: []
+ groupName: 'Capacity Planning'
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: 'Get Possible Revenue'
+ description: 'Estimate monthly revenue based on capacity hours and hourly rates.'
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters:
+ month:
+ custom: []
+ name: month
+ description: 'The month in YYYY-MM format.'
+ required: true
+ example: 2026-02
+ type: string
+ enumValues: []
+ exampleWasSpecified: true
+ nullable: false
+ deprecated: false
+ cleanUrlParameters:
+ month: 2026-02
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters:
+ month:
+ custom: []
+ name: month
+ description: 'Must be a valid date in the format Y-m.'
+ required: true
+ example: 2026-02
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ cleanBodyParameters:
+ month: 2026-02
+ fileParameters: []
+ responses:
+ -
+ custom: []
+ status: 200
+ content: |-
+ {
+ "data": {
+ "month": "2026-02",
+ "possible_revenue": 21500.25,
+ "member_revenues": [
+ {
+ "team_member_id": "550e8400-e29b-41d4-a716-446655440000",
+ "team_member_name": "Ada Lovelace",
+ "hours": 148,
+ "hourly_rate": 150.0,
+ "revenue": 22200.0
+ }
+ ]
+ }
+ }
+ headers: []
+ description: ''
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - POST
+ uri: api/capacity/availability
+ metadata:
+ custom: []
+ groupName: 'Capacity Planning'
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: 'Save Team Member Availability'
+ description: 'Persist a daily availability override and refresh cached capacity totals.'
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters: []
+ cleanUrlParameters: []
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters:
+ team_member_id:
+ custom: []
+ name: team_member_id
+ description: 'The team member UUID.'
+ required: true
+ example: architecto
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ date:
+ custom: []
+ name: date
+ description: 'The date for the availability override (YYYY-MM-DD).'
+ required: true
+ example: architecto
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ availability:
+ custom: []
+ name: availability
+ description: 'The availability value (0, 0.5, 1.0).'
+ required: true
+ example: architecto
+ type: numeric
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ cleanBodyParameters:
+ team_member_id: architecto
+ date: architecto
+ availability: architecto
+ fileParameters: []
+ responses:
+ -
+ custom: []
+ status: 201
+ content: |-
+ {
+ "data": {
+ "team_member_id": "550e8400-e29b-41d4-a716-446655440000",
+ "date": "2026-02-03",
+ "availability": 0.5
+ }
+ }
+ headers: []
+ description: ''
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - POST
+ uri: api/capacity/availability/batch
+ metadata:
+ custom: []
+ groupName: 'Capacity Planning'
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: 'Batch Update Team Member Availability'
+ description: 'Persist multiple daily availability overrides in a single batch operation.'
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters: []
+ cleanUrlParameters: []
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters:
+ month:
+ custom: []
+ name: month
+ description: 'The month in YYYY-MM format.'
+ required: true
+ example: 2026-02
+ type: string
+ enumValues: []
+ exampleWasSpecified: true
+ nullable: false
+ deprecated: false
+ updates:
+ custom: []
+ name: updates
+ description: 'Array of availability updates.'
+ required: true
+ example:
+ - architecto
+ type: 'string[]'
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ 'updates[].team_member_id':
+ custom: []
+ name: 'updates[].team_member_id'
+ description: 'The team member UUID.'
+ required: true
+ example: architecto
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ 'updates[].date':
+ custom: []
+ name: 'updates[].date'
+ description: 'The date (YYYY-MM-DD).'
+ required: true
+ example: architecto
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ 'updates[].availability':
+ custom: []
+ name: 'updates[].availability'
+ description: 'The availability value (0, 0.5, 1).'
+ required: true
+ example: architecto
+ type: numeric
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ cleanBodyParameters:
+ month: 2026-02
+ updates:
+ - architecto
+ fileParameters: []
+ responses:
+ -
+ custom: []
+ status: 200
+ content: |-
+ {
+ "data": {
+ "saved": 12,
+ "month": "2026-02"
+ }
+ }
+ headers: []
+ description: ''
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - GET
+ uri: api/holidays
+ metadata:
+ custom: []
+ groupName: 'Capacity Planning'
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: 'List Holidays'
+ description: 'Retrieve holidays for a specific month or all holidays when no month is provided.'
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters:
+ month:
+ custom: []
+ name: month
+ description: 'nullable The month in YYYY-MM format.'
+ required: false
+ example: 2026-02
+ type: string
+ enumValues: []
+ exampleWasSpecified: true
+ nullable: false
+ deprecated: false
+ cleanUrlParameters:
+ month: 2026-02
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters:
+ month:
+ custom: []
+ name: month
+ description: 'Must be a valid date in the format Y-m.'
+ required: false
+ example: 2026-02
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: true
+ deprecated: false
+ cleanBodyParameters:
+ month: 2026-02
+ fileParameters: []
+ responses:
+ -
+ custom: []
+ status: 200
+ content: |-
+ {
+ "data": [
+ {
+ "id": "550e8400-e29b-41d4-a716-446655440000",
+ "date": "2026-02-14",
+ "name": "Company Holiday",
+ "description": "Office closed"
+ }
+ ]
+ }
+ headers: []
+ description: ''
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - POST
+ uri: api/holidays
+ metadata:
+ custom: []
+ groupName: 'Capacity Planning'
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: 'Create Holiday'
+ description: 'Add a holiday and clear cached capacity data for the related month.'
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters: []
+ cleanUrlParameters: []
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters:
+ date:
+ custom: []
+ name: date
+ description: 'Date of the holiday.'
+ required: true
+ example: '2026-02-14'
+ type: string
+ enumValues: []
+ exampleWasSpecified: true
+ nullable: false
+ deprecated: false
+ name:
+ custom: []
+ name: name
+ description: 'Name of the holiday.'
+ required: true
+ example: "Presidents' Day"
+ type: string
+ enumValues: []
+ exampleWasSpecified: true
+ nullable: false
+ deprecated: false
+ description:
+ custom: []
+ name: description
+ description: 'nullable Optional description of the holiday.'
+ required: false
+ example: 'Eius et animi quos velit et.'
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: true
+ deprecated: false
+ cleanBodyParameters:
+ date: '2026-02-14'
+ name: "Presidents' Day"
+ description: 'Eius et animi quos velit et.'
+ fileParameters: []
+ responses:
+ -
+ custom: []
+ status: 201
+ content: |-
+ {
+ "data": {
+ "id": "550e8400-e29b-41d4-a716-446655440000",
+ "date": "2026-02-14",
+ "name": "Presidents' Day",
+ "description": "Office closed"
+ }
+ }
+ headers: []
+ description: ''
+ -
+ custom: []
+ status: 422
+ content: '{"message":"A holiday already exists for this date.","errors":{"date":["A holiday already exists for this date."]}}'
+ headers: []
+ description: ''
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - DELETE
+ uri: 'api/holidays/{id}'
+ metadata:
+ custom: []
+ groupName: 'Capacity Planning'
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: 'Delete Holiday'
+ description: 'Remove a holiday and clear affected capacity caches.'
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters:
+ id:
+ custom: []
+ name: id
+ description: 'The holiday 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": "Holiday deleted"
+ }
+ headers: []
+ description: ''
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - GET
+ uri: api/ptos
+ metadata:
+ custom: []
+ groupName: 'Capacity Planning'
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: 'List PTO Requests'
+ description: 'Fetch PTO requests for a team member, optionally constrained to a month.'
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters:
+ team_member_id:
+ custom: []
+ name: team_member_id
+ description: 'The team member UUID.'
+ required: true
+ example: 550e8400-e29b-41d4-a716-446655440000
+ type: string
+ enumValues: []
+ exampleWasSpecified: true
+ nullable: false
+ deprecated: false
+ month:
+ custom: []
+ name: month
+ description: 'nullable The month in YYYY-MM format.'
+ required: false
+ example: 2026-02
+ type: string
+ enumValues: []
+ exampleWasSpecified: true
+ nullable: false
+ deprecated: false
+ cleanUrlParameters:
+ team_member_id: 550e8400-e29b-41d4-a716-446655440000
+ month: 2026-02
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters:
+ team_member_id:
+ custom: []
+ name: team_member_id
+ description: 'The id of an existing record in the team_members table.'
+ required: true
+ example: architecto
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ month:
+ custom: []
+ name: month
+ description: 'Must be a valid date in the format Y-m.'
+ required: false
+ example: 2026-02
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: true
+ deprecated: false
+ cleanBodyParameters:
+ team_member_id: architecto
+ month: 2026-02
+ fileParameters: []
+ responses:
+ -
+ custom: []
+ status: 200
+ content: |-
+ {
+ "data": [
+ {
+ "id": "550e8400-e29b-41d4-a716-446655440001",
+ "team_member_id": "550e8400-e29b-41d4-a716-446655440000",
+ "start_date": "2026-02-10",
+ "end_date": "2026-02-12",
+ "status": "pending",
+ "reason": "Family travel"
+ }
+ ]
+ }
+ headers: []
+ description: ''
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - POST
+ uri: api/ptos
+ metadata:
+ custom: []
+ groupName: 'Capacity Planning'
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: 'Request PTO'
+ description: 'Create a PTO request for a team member and approve it immediately.'
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters: []
+ cleanUrlParameters: []
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters:
+ team_member_id:
+ custom: []
+ name: team_member_id
+ description: 'The team member UUID.'
+ required: true
+ example: 550e8400-e29b-41d4-a716-446655440000
+ type: string
+ enumValues: []
+ exampleWasSpecified: true
+ nullable: false
+ deprecated: false
+ start_date:
+ custom: []
+ name: start_date
+ description: 'The first day of the PTO.'
+ required: true
+ example: '2026-02-10'
+ type: string
+ enumValues: []
+ exampleWasSpecified: true
+ nullable: false
+ deprecated: false
+ end_date:
+ custom: []
+ name: end_date
+ description: 'The final day of the PTO.'
+ required: true
+ example: '2026-02-12'
+ type: string
+ enumValues: []
+ exampleWasSpecified: true
+ nullable: false
+ deprecated: false
+ reason:
+ custom: []
+ name: reason
+ description: 'nullable Optional reason for the PTO.'
+ required: false
+ example: architecto
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: true
+ deprecated: false
+ cleanBodyParameters:
+ team_member_id: 550e8400-e29b-41d4-a716-446655440000
+ start_date: '2026-02-10'
+ end_date: '2026-02-12'
+ reason: architecto
+ fileParameters: []
+ responses:
+ -
+ custom: []
+ status: 201
+ content: |-
+ {
+ "data": {
+ "id": "550e8400-e29b-41d4-a716-446655440001",
+ "team_member_id": "550e8400-e29b-41d4-a716-446655440000",
+ "start_date": "2026-02-10",
+ "end_date": "2026-02-12",
+ "status": "approved",
+ "reason": "Family travel"
+ }
+ }
+ headers: []
+ description: ''
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - PUT
+ uri: 'api/ptos/{id}/approve'
+ metadata:
+ custom: []
+ groupName: 'Capacity Planning'
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: 'Approve PTO'
+ description: 'Approve a pending PTO request and refresh the affected capacity caches.'
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters:
+ id:
+ custom: []
+ name: id
+ description: 'The PTO UUID that needs approval.'
+ required: true
+ example: 550e8400-e29b-41d4-a716-446655440001
+ type: string
+ enumValues: []
+ exampleWasSpecified: true
+ nullable: false
+ deprecated: false
+ cleanUrlParameters:
+ id: 550e8400-e29b-41d4-a716-446655440001
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters: []
+ cleanBodyParameters: []
+ fileParameters: []
+ responses:
+ -
+ custom: []
+ status: 200
+ content: |-
+ {
+ "data": {
+ "id": "550e8400-e29b-41d4-a716-446655440001",
+ "status": "approved"
+ }
+ }
+ headers: []
+ description: ''
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
diff --git a/backend/.scribe/endpoints.cache/05.yaml b/backend/.scribe/endpoints.cache/05.yaml
new file mode 100644
index 00000000..759f13fc
--- /dev/null
+++ b/backend/.scribe/endpoints.cache/05.yaml
@@ -0,0 +1,495 @@
+## Autogenerated by Scribe. DO NOT MODIFY.
+
+name: 'Resource Allocation'
+description: |-
+
+ Endpoints for managing resource allocations.
+endpoints:
+ -
+ custom: []
+ httpMethods:
+ - GET
+ uri: api/allocations
+ metadata:
+ custom: []
+ groupName: 'Resource Allocation'
+ groupDescription: |-
+
+ Endpoints for managing resource allocations.
+ subgroup: ''
+ subgroupDescription: ''
+ title: 'List allocations / Get allocation matrix'
+ description: 'Get all allocations, optionally filtered by month.'
+ authenticated: true
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters: []
+ cleanUrlParameters: []
+ queryParameters:
+ month:
+ custom: []
+ name: month
+ description: 'Filter by month (YYYY-MM format).'
+ required: false
+ example: 2026-02
+ type: string
+ enumValues: []
+ exampleWasSpecified: true
+ nullable: false
+ deprecated: false
+ cleanQueryParameters:
+ month: 2026-02
+ bodyParameters: []
+ cleanBodyParameters: []
+ fileParameters: []
+ responses:
+ -
+ custom: []
+ status: 200
+ content: |-
+ {
+ "data": [
+ {
+ "id": "550e8400-e29b-41d4-a716-446655440000",
+ "project_id": "550e8400-e29b-41d4-a716-446655440001",
+ "team_member_id": "550e8400-e29b-41d4-a716-446655440002",
+ "month": "2026-02",
+ "allocated_hours": 40.00
+ }
+ ]
+ }
+ headers: []
+ description: ''
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - POST
+ uri: api/allocations
+ metadata:
+ custom: []
+ groupName: 'Resource Allocation'
+ groupDescription: |-
+
+ Endpoints for managing resource allocations.
+ subgroup: ''
+ subgroupDescription: ''
+ title: 'Create a new allocation'
+ description: 'Allocate hours for a team member to a project for a specific month.'
+ authenticated: true
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters: []
+ cleanUrlParameters: []
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters:
+ project_id:
+ custom: []
+ name: project_id
+ description: 'Project UUID.'
+ required: true
+ example: 550e8400-e29b-41d4-a716-446655440001
+ type: string
+ enumValues: []
+ exampleWasSpecified: true
+ nullable: false
+ deprecated: false
+ team_member_id:
+ custom: []
+ name: team_member_id
+ description: 'Team member UUID.'
+ required: true
+ example: 550e8400-e29b-41d4-a716-446655440002
+ type: string
+ enumValues: []
+ exampleWasSpecified: true
+ nullable: true
+ deprecated: false
+ month:
+ custom: []
+ name: month
+ description: 'Month (YYYY-MM format).'
+ required: true
+ example: 2026-02
+ type: string
+ enumValues: []
+ exampleWasSpecified: true
+ nullable: false
+ deprecated: false
+ allocated_hours:
+ custom: []
+ name: allocated_hours
+ description: 'Hours to allocate (must be >= 0).'
+ required: true
+ example: '40'
+ type: numeric
+ enumValues: []
+ exampleWasSpecified: true
+ nullable: false
+ deprecated: false
+ cleanBodyParameters:
+ project_id: 550e8400-e29b-41d4-a716-446655440001
+ team_member_id: 550e8400-e29b-41d4-a716-446655440002
+ month: 2026-02
+ allocated_hours: '40'
+ fileParameters: []
+ responses:
+ -
+ custom: []
+ status: 201
+ content: |-
+ {
+ "data": {
+ "id": "550e8400-e29b-41d4-a716-446655440000",
+ "project_id": "550e8400-e29b-41d4-a716-446655440001",
+ "team_member_id": "550e8400-e29b-41d4-a716-446655440002",
+ "month": "2026-02",
+ "allocated_hours": 40.00
+ }
+ }
+ headers: []
+ description: ''
+ -
+ custom: []
+ status: 422
+ content: '{"message":"Validation failed","errors":{"allocated_hours":["Allocated hours must be greater than or equal to 0"]}}'
+ headers: []
+ description: ''
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - GET
+ uri: 'api/allocations/{id}'
+ metadata:
+ custom: []
+ groupName: 'Resource Allocation'
+ groupDescription: |-
+
+ Endpoints for managing resource allocations.
+ subgroup: ''
+ subgroupDescription: ''
+ title: 'Get a single allocation'
+ description: 'Get details of a specific allocation by ID.'
+ authenticated: true
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters:
+ id:
+ custom: []
+ name: id
+ description: 'Allocation 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",
+ "project_id": "550e8400-e29b-41d4-a716-446655440001",
+ "team_member_id": "550e8400-e29b-41d4-a716-446655440002",
+ "month": "2026-02",
+ "allocated_hours": 40.00
+ }
+ }
+ headers: []
+ description: ''
+ -
+ custom: []
+ status: 404
+ content: '{"message": "Allocation not found"}'
+ headers: []
+ description: ''
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - PUT
+ - PATCH
+ uri: 'api/allocations/{id}'
+ metadata:
+ custom: []
+ groupName: 'Resource Allocation'
+ groupDescription: |-
+
+ Endpoints for managing resource allocations.
+ subgroup: ''
+ subgroupDescription: ''
+ title: 'Update an allocation'
+ description: "Update an existing allocation's hours."
+ authenticated: true
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters:
+ id:
+ custom: []
+ name: id
+ description: 'Allocation 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:
+ allocated_hours:
+ custom: []
+ name: allocated_hours
+ description: 'Hours to allocate (must be >= 0).'
+ required: true
+ example: '60'
+ type: numeric
+ enumValues: []
+ exampleWasSpecified: true
+ nullable: false
+ deprecated: false
+ cleanBodyParameters:
+ allocated_hours: '60'
+ fileParameters: []
+ responses:
+ -
+ custom: []
+ status: 200
+ content: |-
+ {
+ "data": {
+ "id": "550e8400-e29b-41d4-a716-446655440000",
+ "project_id": "550e8400-e29b-41d4-a716-446655440001",
+ "team_member_id": "550e8400-e29b-41d4-a716-446655440002",
+ "month": "2026-02",
+ "allocated_hours": 60.00
+ }
+ }
+ headers: []
+ description: ''
+ -
+ custom: []
+ status: 404
+ content: '{"message": "Allocation not found"}'
+ headers: []
+ description: ''
+ -
+ custom: []
+ status: 422
+ content: '{"message":"Validation failed","errors":{"allocated_hours":["Allocated hours must be greater than or equal to 0"]}}'
+ headers: []
+ description: ''
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - DELETE
+ uri: 'api/allocations/{id}'
+ metadata:
+ custom: []
+ groupName: 'Resource Allocation'
+ groupDescription: |-
+
+ Endpoints for managing resource allocations.
+ subgroup: ''
+ subgroupDescription: ''
+ title: 'Delete an allocation'
+ description: 'Remove an allocation.'
+ authenticated: true
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters:
+ id:
+ custom: []
+ name: id
+ description: 'Allocation 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": "Allocation deleted successfully"}'
+ headers: []
+ description: ''
+ -
+ custom: []
+ status: 404
+ content: '{"message": "Allocation not found"}'
+ headers: []
+ description: ''
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - POST
+ uri: api/allocations/bulk
+ metadata:
+ custom: []
+ groupName: 'Resource Allocation'
+ groupDescription: |-
+
+ Endpoints for managing resource allocations.
+ subgroup: ''
+ subgroupDescription: ''
+ title: 'Bulk create allocations'
+ description: 'Create or update multiple allocations in a single request.'
+ authenticated: true
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters: []
+ cleanUrlParameters: []
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters:
+ allocations:
+ custom: []
+ name: allocations
+ description: 'Array of allocations.'
+ required: true
+ example:
+ -
+ project_id: ...
+ team_member_id: ...
+ month: 2026-02
+ allocated_hours: 40
+ type: 'string[]'
+ enumValues: []
+ exampleWasSpecified: true
+ nullable: false
+ deprecated: false
+ 'allocations[].project_id':
+ custom: []
+ name: 'allocations[].project_id'
+ description: 'Must be a valid UUID. The id of an existing record in the projects table.'
+ required: true
+ example: 6ff8f7f6-1eb3-3525-be4a-3932c805afed
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ 'allocations[].team_member_id':
+ custom: []
+ name: 'allocations[].team_member_id'
+ description: 'Must be a valid UUID. The id of an existing record in the team_members table.'
+ required: true
+ example: 6b72fe4a-5b40-307c-bc24-f79acf9a1bb9
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ 'allocations[].month':
+ custom: []
+ name: 'allocations[].month'
+ description: 'Must be a valid date in the format Y-m.'
+ required: true
+ example: 2026-02
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ 'allocations[].allocated_hours':
+ custom: []
+ name: 'allocations[].allocated_hours'
+ description: 'Must be at least 0.'
+ required: true
+ example: 77
+ type: number
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ cleanBodyParameters:
+ allocations:
+ -
+ project_id: ...
+ team_member_id: ...
+ month: 2026-02
+ allocated_hours: 40
+ fileParameters: []
+ responses:
+ -
+ custom: []
+ status: 201
+ content: |-
+ {
+ "data": [
+ {
+ "id": "550e8400-e29b-41d4-a716-446655440000",
+ "project_id": "550e8400-e29b-41d4-a716-446655440001",
+ "team_member_id": "550e8400-e29b-41d4-a716-446655440002",
+ "month": "2026-02",
+ "allocated_hours": 40.00
+ }
+ ]
+ }
+ headers: []
+ description: ''
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
diff --git a/backend/.scribe/endpoints/01.yaml b/backend/.scribe/endpoints/01.yaml
index 8648829b..b0404895 100644
--- a/backend/.scribe/endpoints/01.yaml
+++ b/backend/.scribe/endpoints/01.yaml
@@ -1,91 +1,20 @@
-name: 'Team Members'
-description: |-
-
- Endpoints for managing team members.
+name: Endpoints
+description: ''
endpoints:
-
custom: []
httpMethods:
- GET
- uri: api/team-members
+ uri: api/user
metadata:
custom: []
- groupName: 'Team Members'
- groupDescription: |-
-
- Endpoints for managing team members.
+ groupName: Endpoints
+ groupDescription: ''
subgroup: ''
subgroupDescription: ''
- title: 'List all team members'
- description: 'Get a list of all team members with optional filtering by active status.'
- authenticated: true
- deprecated: false
- headers:
- Content-Type: application/json
- Accept: application/json
- urlParameters: []
- cleanUrlParameters: []
- queryParameters:
- active:
- custom: []
- name: active
- description: 'Filter by active status.'
- required: false
- example: true
- type: boolean
- enumValues: []
- exampleWasSpecified: true
- nullable: false
- deprecated: false
- cleanQueryParameters:
- active: true
- bodyParameters: []
- cleanBodyParameters: []
- fileParameters: []
- responses:
- -
- custom: []
- status: 200
- content: |-
- {
- "data": [
- {
- "id": "550e8400-e29b-41d4-a716-446655440000",
- "name": "John Doe",
- "role": {
- "id": 1,
- "name": "Backend Developer"
- },
- "hourly_rate": "150.00",
- "active": true,
- "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/team-members
- metadata:
- custom: []
- groupName: 'Team Members'
- groupDescription: |-
-
- Endpoints for managing team members.
- subgroup: ''
- subgroupDescription: ''
- title: 'Create a new team member'
- description: 'Create a new team member with name, role, and hourly rate.'
- authenticated: true
+ title: ''
+ description: ''
+ authenticated: false
deprecated: false
headers:
Content-Type: application/json
@@ -94,84 +23,19 @@ endpoints:
cleanUrlParameters: []
queryParameters: []
cleanQueryParameters: []
- bodyParameters:
- name:
- custom: []
- name: name
- description: 'Team member name.'
- required: true
- example: 'John Doe'
- type: string
- enumValues: []
- exampleWasSpecified: true
- nullable: false
- deprecated: false
- role_id:
- custom: []
- name: role_id
- description: 'Role ID.'
- required: true
- example: 1
- type: integer
- enumValues: []
- exampleWasSpecified: true
- nullable: false
- deprecated: false
- hourly_rate:
- custom: []
- name: hourly_rate
- description: 'Hourly rate (must be > 0).'
- required: true
- example: '150.00'
- type: numeric
- enumValues: []
- exampleWasSpecified: true
- nullable: false
- deprecated: false
- active:
- custom: []
- name: active
- description: 'Active status (defaults to true).'
- required: false
- example: true
- type: boolean
- enumValues: []
- exampleWasSpecified: true
- nullable: false
- deprecated: false
- cleanBodyParameters:
- name: 'John Doe'
- role_id: 1
- hourly_rate: '150.00'
- active: true
+ bodyParameters: []
+ cleanBodyParameters: []
fileParameters: []
responses:
-
custom: []
- status: 201
- content: |-
- {
- "data": {
- "id": "550e8400-e29b-41d4-a716-446655440000",
- "name": "John Doe",
- "role": {
- "id": 1,
- "name": "Backend Developer"
- },
- "hourly_rate": "150.00",
- "active": true,
- "created_at": "2024-01-15T10:00:00.000000Z",
- "updated_at": "2024-01-15T10:00:00.000000Z"
- }
- }
- headers: []
- description: ''
- -
- custom: []
- status: 422
- content: '{"message":"Validation failed","errors":{"name":["The name field is required."],"hourly_rate":["Hourly rate must be greater than 0"]}}'
- headers: []
- description: ''
+ status: 401
+ content: '{"message":"Authentication required"}'
+ headers:
+ cache-control: 'no-cache, private'
+ content-type: application/json
+ access-control-allow-origin: '*'
+ description: null
responseFields: []
auth: []
controller: null
@@ -181,36 +45,24 @@ endpoints:
custom: []
httpMethods:
- GET
- uri: 'api/team-members/{id}'
+ uri: api/project-month-plans
metadata:
custom: []
- groupName: 'Team Members'
- groupDescription: |-
-
- Endpoints for managing team members.
+ groupName: Endpoints
+ groupDescription: ''
subgroup: ''
subgroupDescription: ''
- title: 'Get a single team member'
- description: 'Get details of a specific team member by ID.'
- authenticated: true
+ title: |-
+ GET /api/project-month-plans?year=2026
+ Returns month-plan grid payload by project/month for the year.
+ description: ''
+ authenticated: false
deprecated: false
headers:
Content-Type: application/json
Accept: application/json
- urlParameters:
- id:
- custom: []
- name: id
- description: 'Team member 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
+ urlParameters: []
+ cleanUrlParameters: []
queryParameters: []
cleanQueryParameters: []
bodyParameters: []
@@ -219,30 +71,13 @@ endpoints:
responses:
-
custom: []
- status: 200
- content: |-
- {
- "data": {
- "id": "550e8400-e29b-41d4-a716-446655440000",
- "name": "John Doe",
- "role": {
- "id": 1,
- "name": "Backend Developer"
- },
- "hourly_rate": "150.00",
- "active": true,
- "created_at": "2024-01-15T10:00:00.000000Z",
- "updated_at": "2024-01-15T10:00:00.000000Z"
- }
- }
- headers: []
- description: ''
- -
- custom: []
- status: 404
- content: '{"message":"Team member not found"}'
- headers: []
- description: ''
+ status: 401
+ content: '{"message":"Authentication required"}'
+ headers:
+ cache-control: 'no-cache, private'
+ content-type: application/json
+ access-control-allow-origin: '*'
+ description: null
responseFields: []
auth: []
controller: null
@@ -252,123 +87,92 @@ endpoints:
custom: []
httpMethods:
- PUT
- - PATCH
- uri: 'api/team-members/{id}'
+ uri: api/project-month-plans/bulk
metadata:
custom: []
- groupName: 'Team Members'
- groupDescription: |-
-
- Endpoints for managing team members.
+ groupName: Endpoints
+ groupDescription: ''
subgroup: ''
subgroupDescription: ''
- title: 'Update a team member'
- description: 'Update details of an existing team member.'
- authenticated: true
+ title: |-
+ PUT /api/project-month-plans/bulk
+ Bulk upsert month plan cells.
+ description: ''
+ authenticated: false
deprecated: false
headers:
Content-Type: application/json
Accept: application/json
- urlParameters:
- id:
- custom: []
- name: id
- description: 'Team member 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
+ urlParameters: []
+ cleanUrlParameters: []
queryParameters: []
cleanQueryParameters: []
bodyParameters:
- name:
+ year:
custom: []
- name: name
- description: 'Team member name.'
- required: false
- example: 'John Doe'
- type: string
- enumValues: []
- exampleWasSpecified: true
- nullable: false
- deprecated: false
- role_id:
- custom: []
- name: role_id
- description: 'Role ID.'
- required: false
+ name: year
+ description: 'Must be at least 2020. Must not be greater than 2100.'
+ required: true
example: 1
type: integer
enumValues: []
- exampleWasSpecified: true
+ exampleWasSpecified: false
nullable: false
deprecated: false
- hourly_rate:
+ items:
custom: []
- name: hourly_rate
- description: 'Hourly rate (must be > 0).'
- required: false
- example: '175.00'
- type: numeric
+ name: items
+ description: ''
+ required: true
+ example:
+ - []
+ type: 'object[]'
enumValues: []
- exampleWasSpecified: true
+ exampleWasSpecified: false
nullable: false
deprecated: false
- active:
+ 'items[].project_id':
custom: []
- name: active
- description: 'Active status.'
- required: false
- example: false
- type: boolean
+ name: 'items[].project_id'
+ description: 'Must be a valid UUID. The id of an existing record in the projects table.'
+ required: true
+ example: 6ff8f7f6-1eb3-3525-be4a-3932c805afed
+ type: string
enumValues: []
- exampleWasSpecified: true
+ exampleWasSpecified: false
nullable: false
deprecated: false
+ 'items[].month':
+ custom: []
+ name: 'items[].month'
+ description: 'Must be a valid date in the format Y-m.'
+ required: true
+ example: 2026-02
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ 'items[].planned_hours':
+ custom: []
+ name: 'items[].planned_hours'
+ description: 'Must be at least 0.'
+ required: false
+ example: 84
+ type: number
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: true
+ deprecated: false
cleanBodyParameters:
- name: 'John Doe'
- role_id: 1
- hourly_rate: '175.00'
- active: false
+ year: 1
+ items:
+ -
+ project_id: 6ff8f7f6-1eb3-3525-be4a-3932c805afed
+ month: 2026-02
+ planned_hours: 84
fileParameters: []
- responses:
- -
- custom: []
- status: 200
- content: |-
- {
- "data": {
- "id": "550e8400-e29b-41d4-a716-446655440000",
- "name": "John Doe",
- "role": {
- "id": 1,
- "name": "Backend Developer"
- },
- "hourly_rate": "175.00",
- "active": false,
- "created_at": "2024-01-15T10:00:00.000000Z",
- "updated_at": "2024-01-15T11:00:00.000000Z"
- }
- }
- headers: []
- description: ''
- -
- custom: []
- status: 404
- content: '{"message":"Team member not found"}'
- headers: []
- description: ''
- -
- custom: []
- status: 422
- content: '{"message":"Validation failed","errors":{"hourly_rate":["Hourly rate must be greater than 0"]}}'
- headers: []
- description: ''
+ responses: []
responseFields: []
auth: []
controller: null
@@ -378,18 +182,16 @@ endpoints:
custom: []
httpMethods:
- DELETE
- uri: 'api/team-members/{id}'
+ uri: 'api/ptos/{id}'
metadata:
custom: []
- groupName: 'Team Members'
- groupDescription: |-
-
- Endpoints for managing team members.
+ groupName: Endpoints
+ groupDescription: ''
subgroup: ''
subgroupDescription: ''
- title: 'Delete a team member'
- description: 'Delete a team member. Cannot delete if member has allocations or actuals.'
- authenticated: true
+ title: ''
+ description: ''
+ authenticated: false
deprecated: false
headers:
Content-Type: application/json
@@ -398,46 +200,22 @@ endpoints:
id:
custom: []
name: id
- description: 'Team member UUID.'
+ description: 'The ID of the pto.'
required: true
- example: 550e8400-e29b-41d4-a716-446655440000
+ example: architecto
type: string
enumValues: []
- exampleWasSpecified: true
+ exampleWasSpecified: false
nullable: false
deprecated: false
cleanUrlParameters:
- id: 550e8400-e29b-41d4-a716-446655440000
+ id: architecto
queryParameters: []
cleanQueryParameters: []
bodyParameters: []
cleanBodyParameters: []
fileParameters: []
- responses:
- -
- custom: []
- status: 200
- content: '{"message":"Team member deleted successfully"}'
- headers: []
- description: ''
- -
- custom: []
- status: 404
- content: '{"message":"Team member not found"}'
- headers: []
- description: ''
- -
- custom: []
- status: 422
- content: '{"message":"Cannot delete team member with active allocations","suggestion":"Consider deactivating the team member instead"}'
- headers: []
- description: ''
- -
- custom: []
- status: 422
- content: '{"message":"Cannot delete team member with historical data","suggestion":"Consider deactivating the team member instead"}'
- headers: []
- description: ''
+ responses: []
responseFields: []
auth: []
controller: null
diff --git a/backend/.scribe/endpoints/02.yaml b/backend/.scribe/endpoints/02.yaml
index c5f44c11..8648829b 100644
--- a/backend/.scribe/endpoints/02.yaml
+++ b/backend/.scribe/endpoints/02.yaml
@@ -1,115 +1,23 @@
-name: Projects
+name: 'Team Members'
description: |-
- Endpoints for managing projects.
+ Endpoints for managing team members.
endpoints:
-
custom: []
httpMethods:
- GET
- uri: api/projects/types
+ uri: api/team-members
metadata:
custom: []
- groupName: Projects
+ groupName: 'Team Members'
groupDescription: |-
- Endpoints for managing projects.
+ Endpoints for managing team members.
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.'
+ title: 'List all team members'
+ description: 'Get a list of all team members with optional filtering by active status.'
authenticated: true
deprecated: false
headers:
@@ -118,31 +26,19 @@ endpoints:
urlParameters: []
cleanUrlParameters: []
queryParameters:
- status_id:
+ active:
custom: []
- name: status_id
- description: 'Filter by status ID.'
+ name: active
+ description: 'Filter by active status.'
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
+ example: true
+ type: boolean
enumValues: []
exampleWasSpecified: true
nullable: false
deprecated: false
cleanQueryParameters:
- status_id: 1
- type_id: 2
+ active: true
bodyParameters: []
cleanBodyParameters: []
fileParameters: []
@@ -155,12 +51,13 @@ endpoints:
"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},
+ "name": "John Doe",
+ "role": {
+ "id": 1,
+ "name": "Backend Developer"
+ },
+ "hourly_rate": "150.00",
+ "active": true,
"created_at": "2024-01-15T10:00:00.000000Z",
"updated_at": "2024-01-15T10:00:00.000000Z"
}
@@ -177,17 +74,17 @@ endpoints:
custom: []
httpMethods:
- POST
- uri: api/projects
+ uri: api/team-members
metadata:
custom: []
- groupName: Projects
+ groupName: 'Team Members'
groupDescription: |-
- Endpoints for managing projects.
+ Endpoints for managing team members.
subgroup: ''
subgroupDescription: ''
- title: 'Create a new project'
- description: 'Create a new project with code, title, and type.'
+ title: 'Create a new team member'
+ description: 'Create a new team member with name, role, and hourly rate.'
authenticated: true
deprecated: false
headers:
@@ -198,32 +95,21 @@ endpoints:
queryParameters: []
cleanQueryParameters: []
bodyParameters:
- code:
+ name:
custom: []
- name: code
- description: 'Project code (must be unique).'
+ name: name
+ description: 'Team member name.'
required: true
- example: PROJ-001
+ example: 'John Doe'
type: string
enumValues: []
exampleWasSpecified: true
nullable: false
deprecated: false
- title:
+ role_id:
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.'
+ name: role_id
+ description: 'Role ID.'
required: true
example: 1
type: integer
@@ -231,10 +117,33 @@ endpoints:
exampleWasSpecified: true
nullable: false
deprecated: false
+ hourly_rate:
+ custom: []
+ name: hourly_rate
+ description: 'Hourly rate (must be > 0).'
+ required: true
+ example: '150.00'
+ type: numeric
+ enumValues: []
+ exampleWasSpecified: true
+ nullable: false
+ deprecated: false
+ active:
+ custom: []
+ name: active
+ description: 'Active status (defaults to true).'
+ required: false
+ example: true
+ type: boolean
+ enumValues: []
+ exampleWasSpecified: true
+ nullable: false
+ deprecated: false
cleanBodyParameters:
- code: PROJ-001
- title: 'Client Dashboard Redesign'
- type_id: 1
+ name: 'John Doe'
+ role_id: 1
+ hourly_rate: '150.00'
+ active: true
fileParameters: []
responses:
-
@@ -244,10 +153,15 @@ endpoints:
{
"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"}
+ "name": "John Doe",
+ "role": {
+ "id": 1,
+ "name": "Backend Developer"
+ },
+ "hourly_rate": "150.00",
+ "active": true,
+ "created_at": "2024-01-15T10:00:00.000000Z",
+ "updated_at": "2024-01-15T10:00:00.000000Z"
}
}
headers: []
@@ -255,7 +169,7 @@ endpoints:
-
custom: []
status: 422
- content: '{"message":"Validation failed","errors":{"code":["Project code must be unique"],"title":["The title field is required."]}}'
+ content: '{"message":"Validation failed","errors":{"name":["The name field is required."],"hourly_rate":["Hourly rate must be greater than 0"]}}'
headers: []
description: ''
responseFields: []
@@ -267,17 +181,17 @@ endpoints:
custom: []
httpMethods:
- GET
- uri: 'api/projects/{id}'
+ uri: 'api/team-members/{id}'
metadata:
custom: []
- groupName: Projects
+ groupName: 'Team Members'
groupDescription: |-
- Endpoints for managing projects.
+ Endpoints for managing team members.
subgroup: ''
subgroupDescription: ''
- title: 'Get a single project'
- description: 'Get details of a specific project by ID.'
+ title: 'Get a single team member'
+ description: 'Get details of a specific team member by ID.'
authenticated: true
deprecated: false
headers:
@@ -287,7 +201,7 @@ endpoints:
id:
custom: []
name: id
- description: 'Project UUID.'
+ description: 'Team member UUID.'
required: true
example: 550e8400-e29b-41d4-a716-446655440000
type: string
@@ -310,12 +224,15 @@ endpoints:
{
"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}
+ "name": "John Doe",
+ "role": {
+ "id": 1,
+ "name": "Backend Developer"
+ },
+ "hourly_rate": "150.00",
+ "active": true,
+ "created_at": "2024-01-15T10:00:00.000000Z",
+ "updated_at": "2024-01-15T10:00:00.000000Z"
}
}
headers: []
@@ -323,7 +240,7 @@ endpoints:
-
custom: []
status: 404
- content: '{"message":"Project not found"}'
+ content: '{"message":"Team member not found"}'
headers: []
description: ''
responseFields: []
@@ -336,17 +253,17 @@ endpoints:
httpMethods:
- PUT
- PATCH
- uri: 'api/projects/{id}'
+ uri: 'api/team-members/{id}'
metadata:
custom: []
- groupName: Projects
+ groupName: 'Team Members'
groupDescription: |-
- Endpoints for managing projects.
+ Endpoints for managing team members.
subgroup: ''
subgroupDescription: ''
- title: 'Update a project'
- description: 'Update details of an existing project.'
+ title: 'Update a team member'
+ description: 'Update details of an existing team member.'
authenticated: true
deprecated: false
headers:
@@ -356,7 +273,7 @@ endpoints:
id:
custom: []
name: id
- description: 'Project UUID.'
+ description: 'Team member UUID.'
required: true
example: 550e8400-e29b-41d4-a716-446655440000
type: string
@@ -369,43 +286,55 @@ endpoints:
queryParameters: []
cleanQueryParameters: []
bodyParameters:
- code:
+ name:
custom: []
- name: code
- description: 'Project code (must be unique).'
+ name: name
+ description: 'Team member name.'
required: false
- example: PROJ-002
+ example: 'John Doe'
type: string
enumValues: []
exampleWasSpecified: true
nullable: false
deprecated: false
- title:
+ role_id:
custom: []
- name: title
- description: 'Project title.'
+ name: role_id
+ description: 'Role ID.'
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
+ example: 1
type: integer
enumValues: []
exampleWasSpecified: true
nullable: false
deprecated: false
+ hourly_rate:
+ custom: []
+ name: hourly_rate
+ description: 'Hourly rate (must be > 0).'
+ required: false
+ example: '175.00'
+ type: numeric
+ enumValues: []
+ exampleWasSpecified: true
+ nullable: false
+ deprecated: false
+ active:
+ custom: []
+ name: active
+ description: 'Active status.'
+ required: false
+ example: false
+ type: boolean
+ enumValues: []
+ exampleWasSpecified: true
+ nullable: false
+ deprecated: false
cleanBodyParameters:
- code: PROJ-002
- title: 'Updated Title'
- type_id: 2
+ name: 'John Doe'
+ role_id: 1
+ hourly_rate: '175.00'
+ active: false
fileParameters: []
responses:
-
@@ -415,9 +344,15 @@ endpoints:
{
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
- "code": "PROJ-002",
- "title": "Updated Title",
- "type": {"id": 2, "name": "Support"}
+ "name": "John Doe",
+ "role": {
+ "id": 1,
+ "name": "Backend Developer"
+ },
+ "hourly_rate": "175.00",
+ "active": false,
+ "created_at": "2024-01-15T10:00:00.000000Z",
+ "updated_at": "2024-01-15T11:00:00.000000Z"
}
}
headers: []
@@ -425,13 +360,13 @@ endpoints:
-
custom: []
status: 404
- content: '{"message":"Project not found"}'
+ content: '{"message":"Team member not found"}'
headers: []
description: ''
-
custom: []
status: 422
- content: '{"message":"Validation failed","errors":{"type_id":["The selected type id is invalid."]}}'
+ content: '{"message":"Validation failed","errors":{"hourly_rate":["Hourly rate must be greater than 0"]}}'
headers: []
description: ''
responseFields: []
@@ -443,17 +378,17 @@ endpoints:
custom: []
httpMethods:
- DELETE
- uri: 'api/projects/{id}'
+ uri: 'api/team-members/{id}'
metadata:
custom: []
- groupName: Projects
+ groupName: 'Team Members'
groupDescription: |-
- Endpoints for managing projects.
+ Endpoints for managing team members.
subgroup: ''
subgroupDescription: ''
- title: 'Delete a project'
- description: 'Delete a project. Cannot delete if project has allocations or actuals.'
+ title: 'Delete a team member'
+ description: 'Delete a team member. Cannot delete if member has allocations or actuals.'
authenticated: true
deprecated: false
headers:
@@ -463,7 +398,7 @@ endpoints:
id:
custom: []
name: id
- description: 'Project UUID.'
+ description: 'Team member UUID.'
required: true
example: 550e8400-e29b-41d4-a716-446655440000
type: string
@@ -482,302 +417,25 @@ endpoints:
-
custom: []
status: 200
- content: '{"message":"Project deleted successfully"}'
+ content: '{"message":"Team member deleted successfully"}'
headers: []
description: ''
-
custom: []
status: 404
- content: '{"message":"Project not found"}'
+ content: '{"message":"Team member 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%"}'
+ content: '{"message":"Cannot delete team member with active allocations","suggestion":"Consider deactivating the team member instead"}'
+ headers: []
+ description: ''
+ -
+ custom: []
+ status: 422
+ content: '{"message":"Cannot delete team member with historical data","suggestion":"Consider deactivating the team member instead"}'
headers: []
description: ''
responseFields: []
diff --git a/backend/.scribe/endpoints/03.yaml b/backend/.scribe/endpoints/03.yaml
index d4611b9a..c5f44c11 100644
--- a/backend/.scribe/endpoints/03.yaml
+++ b/backend/.scribe/endpoints/03.yaml
@@ -1,78 +1,34 @@
-name: 'Capacity Planning'
-description: ''
+name: Projects
+description: |-
+
+ Endpoints for managing projects.
endpoints:
-
custom: []
httpMethods:
- GET
- uri: api/capacity
+ uri: api/projects/types
metadata:
custom: []
- groupName: 'Capacity Planning'
- groupDescription: ''
+ groupName: Projects
+ groupDescription: |-
+
+ Endpoints for managing projects.
subgroup: ''
subgroupDescription: ''
- title: 'Get Individual Capacity'
- description: 'Calculate capacity for a specific team member in a given month.'
- authenticated: false
+ title: 'Get all project types'
+ description: ''
+ authenticated: true
deprecated: false
headers:
Content-Type: application/json
Accept: application/json
- urlParameters:
- month:
- custom: []
- name: month
- description: 'The month in YYYY-MM format.'
- required: true
- example: 2026-02
- type: string
- enumValues: []
- exampleWasSpecified: true
- nullable: false
- deprecated: false
- team_member_id:
- custom: []
- name: team_member_id
- description: 'The team member UUID.'
- required: true
- example: 550e8400-e29b-41d4-a716-446655440000
- type: string
- enumValues: []
- exampleWasSpecified: true
- nullable: false
- deprecated: false
- cleanUrlParameters:
- month: 2026-02
- team_member_id: 550e8400-e29b-41d4-a716-446655440000
+ urlParameters: []
+ cleanUrlParameters: []
queryParameters: []
cleanQueryParameters: []
- bodyParameters:
- month:
- custom: []
- name: month
- description: 'Must be a valid date in the format Y-m.'
- required: true
- example: 2026-02
- type: string
- enumValues: []
- exampleWasSpecified: false
- nullable: false
- deprecated: false
- team_member_id:
- custom: []
- name: team_member_id
- description: 'The id of an existing record in the team_members table.'
- required: true
- example: architecto
- type: string
- enumValues: []
- exampleWasSpecified: false
- nullable: false
- deprecated: false
- cleanBodyParameters:
- month: 2026-02
- team_member_id: architecto
+ bodyParameters: []
+ cleanBodyParameters: []
fileParameters: []
responses:
-
@@ -80,20 +36,11 @@ endpoints:
status: 200
content: |-
{
- "data": {
- "team_member_id": "550e8400-e29b-41d4-a716-446655440000",
- "month": "2026-02",
- "working_days": 20,
- "person_days": 18.5,
- "hours": 148,
- "details": [
- {
- "date": "2026-02-02",
- "availability": 1,
- "is_pto": false
- }
- ]
- }
+ "data": [
+ {"id": 1, "name": "Project"},
+ {"id": 2, "name": "Support"},
+ {"id": 3, "name": "Engagement"}
+ ]
}
headers: []
description: ''
@@ -106,50 +53,28 @@ endpoints:
custom: []
httpMethods:
- GET
- uri: api/capacity/team
+ uri: api/projects/statuses
metadata:
custom: []
- groupName: 'Capacity Planning'
- groupDescription: ''
+ groupName: Projects
+ groupDescription: |-
+
+ Endpoints for managing projects.
subgroup: ''
subgroupDescription: ''
- title: 'Get Team Capacity'
- description: 'Summarize the combined capacity for all active team members in a month.'
- authenticated: false
+ title: 'Get all project statuses'
+ description: ''
+ authenticated: true
deprecated: false
headers:
Content-Type: application/json
Accept: application/json
- urlParameters:
- month:
- custom: []
- name: month
- description: 'The month in YYYY-MM format.'
- required: true
- example: 2026-02
- type: string
- enumValues: []
- exampleWasSpecified: true
- nullable: false
- deprecated: false
- cleanUrlParameters:
- month: 2026-02
+ urlParameters: []
+ cleanUrlParameters: []
queryParameters: []
cleanQueryParameters: []
- bodyParameters:
- month:
- custom: []
- name: month
- description: 'Must be a valid date in the format Y-m.'
- required: true
- example: 2026-02
- type: string
- enumValues: []
- exampleWasSpecified: false
- nullable: false
- deprecated: false
- cleanBodyParameters:
- month: 2026-02
+ bodyParameters: []
+ cleanBodyParameters: []
fileParameters: []
responses:
-
@@ -157,19 +82,11 @@ endpoints:
status: 200
content: |-
{
- "data": {
- "month": "2026-02",
- "total_person_days": 180.5,
- "total_hours": 1444,
- "members": [
- {
- "id": "550e8400-e29b-41d4-a716-446655440000",
- "name": "Ada Lovelace",
- "person_days": 18.5,
- "hours": 148
- }
- ]
- }
+ "data": [
+ {"id": 1, "name": "Pre-sales", "order": 1},
+ {"id": 2, "name": "SOW Approval", "order": 2},
+ {"id": 3, "name": "Gathering Estimates", "order": 3}
+ ]
}
headers: []
description: ''
@@ -182,126 +99,52 @@ endpoints:
custom: []
httpMethods:
- GET
- uri: api/capacity/revenue
+ uri: api/projects
metadata:
custom: []
- groupName: 'Capacity Planning'
- groupDescription: ''
+ groupName: Projects
+ groupDescription: |-
+
+ Endpoints for managing projects.
subgroup: ''
subgroupDescription: ''
- title: 'Get Possible Revenue'
- description: 'Estimate monthly revenue based on capacity hours and hourly rates.'
- authenticated: false
+ 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:
- month:
+ urlParameters: []
+ cleanUrlParameters: []
+ queryParameters:
+ status_id:
custom: []
- name: month
- description: 'The month in YYYY-MM format.'
- required: true
- example: 2026-02
- type: string
- enumValues: []
- exampleWasSpecified: true
- nullable: false
- deprecated: false
- cleanUrlParameters:
- month: 2026-02
- queryParameters: []
- cleanQueryParameters: []
- bodyParameters:
- month:
- custom: []
- name: month
- description: 'Must be a valid date in the format Y-m.'
- required: true
- example: 2026-02
- type: string
- enumValues: []
- exampleWasSpecified: false
- nullable: false
- deprecated: false
- cleanBodyParameters:
- month: 2026-02
- fileParameters: []
- responses:
- -
- custom: []
- status: 200
- content: |-
- {
- "data": {
- "month": "2026-02",
- "possible_revenue": 21500.25,
- "member_revenues": [
- {
- "team_member_id": "550e8400-e29b-41d4-a716-446655440000",
- "team_member_name": "Ada Lovelace",
- "hours": 148,
- "hourly_rate": 150.0,
- "revenue": 22200.0
- }
- ]
- }
- }
- headers: []
- description: ''
- responseFields: []
- auth: []
- controller: null
- method: null
- route: null
- -
- custom: []
- httpMethods:
- - GET
- uri: api/holidays
- metadata:
- custom: []
- groupName: 'Capacity Planning'
- groupDescription: ''
- subgroup: ''
- subgroupDescription: ''
- title: 'List Holidays'
- description: 'Retrieve holidays for a specific month or all holidays when no month is provided.'
- authenticated: false
- deprecated: false
- headers:
- Content-Type: application/json
- Accept: application/json
- urlParameters:
- month:
- custom: []
- name: month
- description: 'nullable The month in YYYY-MM format.'
+ name: status_id
+ description: 'Filter by status ID.'
required: false
- example: 2026-02
- type: string
+ example: 1
+ type: integer
enumValues: []
exampleWasSpecified: true
nullable: false
deprecated: false
- cleanUrlParameters:
- month: 2026-02
- queryParameters: []
- cleanQueryParameters: []
- bodyParameters:
- month:
+ type_id:
custom: []
- name: month
- description: 'Must be a valid date in the format Y-m.'
+ name: type_id
+ description: 'Filter by type ID.'
required: false
- example: 2026-02
- type: string
+ example: 2
+ type: integer
enumValues: []
- exampleWasSpecified: false
- nullable: true
+ exampleWasSpecified: true
+ nullable: false
deprecated: false
- cleanBodyParameters:
- month: 2026-02
+ cleanQueryParameters:
+ status_id: 1
+ type_id: 2
+ bodyParameters: []
+ cleanBodyParameters: []
fileParameters: []
responses:
-
@@ -312,9 +155,14 @@ endpoints:
"data": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
- "date": "2026-02-14",
- "name": "Company Holiday",
- "description": "Office closed"
+ "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"
}
]
}
@@ -329,16 +177,18 @@ endpoints:
custom: []
httpMethods:
- POST
- uri: api/holidays
+ uri: api/projects
metadata:
custom: []
- groupName: 'Capacity Planning'
- groupDescription: ''
+ groupName: Projects
+ groupDescription: |-
+
+ Endpoints for managing projects.
subgroup: ''
subgroupDescription: ''
- title: 'Create Holiday'
- description: 'Add a holiday and clear cached capacity data for the related month.'
- authenticated: false
+ title: 'Create a new project'
+ description: 'Create a new project with code, title, and type.'
+ authenticated: true
deprecated: false
headers:
Content-Type: application/json
@@ -348,43 +198,43 @@ endpoints:
queryParameters: []
cleanQueryParameters: []
bodyParameters:
- date:
+ code:
custom: []
- name: date
- description: 'Date of the holiday.'
+ name: code
+ description: 'Project code (must be unique).'
required: true
- example: '2026-02-14'
+ example: PROJ-001
type: string
enumValues: []
exampleWasSpecified: true
nullable: false
deprecated: false
- name:
+ title:
custom: []
- name: name
- description: 'Name of the holiday.'
+ name: title
+ description: 'Project title.'
required: true
- example: "Presidents' Day"
+ example: 'Client Dashboard Redesign'
type: string
enumValues: []
exampleWasSpecified: true
nullable: false
deprecated: false
- description:
+ type_id:
custom: []
- name: description
- description: 'nullable Optional description of the holiday.'
- required: false
- example: 'Eius et animi quos velit et.'
- type: string
+ name: type_id
+ description: 'Project type ID.'
+ required: true
+ example: 1
+ type: integer
enumValues: []
- exampleWasSpecified: false
- nullable: true
+ exampleWasSpecified: true
+ nullable: false
deprecated: false
cleanBodyParameters:
- date: '2026-02-14'
- name: "Presidents' Day"
- description: 'Eius et animi quos velit et.'
+ code: PROJ-001
+ title: 'Client Dashboard Redesign'
+ type_id: 1
fileParameters: []
responses:
-
@@ -394,13 +244,20 @@ endpoints:
{
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
- "date": "2026-02-14",
- "name": "Presidents' Day",
- "description": "Office closed"
+ "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
@@ -409,17 +266,19 @@ endpoints:
-
custom: []
httpMethods:
- - DELETE
- uri: 'api/holidays/{id}'
+ - GET
+ uri: 'api/projects/{id}'
metadata:
custom: []
- groupName: 'Capacity Planning'
- groupDescription: ''
+ groupName: Projects
+ groupDescription: |-
+
+ Endpoints for managing projects.
subgroup: ''
subgroupDescription: ''
- title: 'Delete Holiday'
- description: 'Remove a holiday and clear affected capacity caches.'
- authenticated: false
+ title: 'Get a single project'
+ description: 'Get details of a specific project by ID.'
+ authenticated: true
deprecated: false
headers:
Content-Type: application/json
@@ -428,7 +287,7 @@ endpoints:
id:
custom: []
name: id
- description: 'The holiday UUID.'
+ description: 'Project UUID.'
required: true
example: 550e8400-e29b-41d4-a716-446655440000
type: string
@@ -447,204 +306,26 @@ endpoints:
-
custom: []
status: 200
- content: |-
- {
- "message": "Holiday deleted"
- }
- headers: []
- description: ''
- responseFields: []
- auth: []
- controller: null
- method: null
- route: null
- -
- custom: []
- httpMethods:
- - GET
- uri: api/ptos
- metadata:
- custom: []
- groupName: 'Capacity Planning'
- groupDescription: ''
- subgroup: ''
- subgroupDescription: ''
- title: 'List PTO Requests'
- description: 'Fetch PTO requests for a team member, optionally constrained to a month.'
- authenticated: false
- deprecated: false
- headers:
- Content-Type: application/json
- Accept: application/json
- urlParameters:
- team_member_id:
- custom: []
- name: team_member_id
- description: 'The team member UUID.'
- required: true
- example: 550e8400-e29b-41d4-a716-446655440000
- type: string
- enumValues: []
- exampleWasSpecified: true
- nullable: false
- deprecated: false
- month:
- custom: []
- name: month
- description: 'nullable The month in YYYY-MM format.'
- required: false
- example: 2026-02
- type: string
- enumValues: []
- exampleWasSpecified: true
- nullable: false
- deprecated: false
- cleanUrlParameters:
- team_member_id: 550e8400-e29b-41d4-a716-446655440000
- month: 2026-02
- queryParameters: []
- cleanQueryParameters: []
- bodyParameters:
- team_member_id:
- custom: []
- name: team_member_id
- description: 'The id of an existing record in the team_members table.'
- required: true
- example: architecto
- type: string
- enumValues: []
- exampleWasSpecified: false
- nullable: false
- deprecated: false
- month:
- custom: []
- name: month
- description: 'Must be a valid date in the format Y-m.'
- required: false
- example: 2026-02
- type: string
- enumValues: []
- exampleWasSpecified: false
- nullable: true
- deprecated: false
- cleanBodyParameters:
- team_member_id: architecto
- month: 2026-02
- fileParameters: []
- responses:
- -
- custom: []
- status: 200
- content: |-
- {
- "data": [
- {
- "id": "550e8400-e29b-41d4-a716-446655440001",
- "team_member_id": "550e8400-e29b-41d4-a716-446655440000",
- "start_date": "2026-02-10",
- "end_date": "2026-02-12",
- "status": "pending",
- "reason": "Family travel"
- }
- ]
- }
- headers: []
- description: ''
- responseFields: []
- auth: []
- controller: null
- method: null
- route: null
- -
- custom: []
- httpMethods:
- - POST
- uri: api/ptos
- metadata:
- custom: []
- groupName: 'Capacity Planning'
- groupDescription: ''
- subgroup: ''
- subgroupDescription: ''
- title: 'Request PTO'
- description: 'Create a PTO request for a team member and keep it in pending status.'
- authenticated: false
- deprecated: false
- headers:
- Content-Type: application/json
- Accept: application/json
- urlParameters: []
- cleanUrlParameters: []
- queryParameters: []
- cleanQueryParameters: []
- bodyParameters:
- team_member_id:
- custom: []
- name: team_member_id
- description: 'The team member UUID.'
- required: true
- example: 550e8400-e29b-41d4-a716-446655440000
- type: string
- enumValues: []
- exampleWasSpecified: true
- nullable: false
- deprecated: false
- start_date:
- custom: []
- name: start_date
- description: 'The first day of the PTO.'
- required: true
- example: '2026-02-10'
- type: string
- enumValues: []
- exampleWasSpecified: true
- nullable: false
- deprecated: false
- end_date:
- custom: []
- name: end_date
- description: 'The final day of the PTO.'
- required: true
- example: '2026-02-12'
- type: string
- enumValues: []
- exampleWasSpecified: true
- nullable: false
- deprecated: false
- reason:
- custom: []
- name: reason
- description: 'nullable Optional reason for the PTO.'
- required: false
- example: architecto
- type: string
- enumValues: []
- exampleWasSpecified: false
- nullable: true
- deprecated: false
- cleanBodyParameters:
- team_member_id: 550e8400-e29b-41d4-a716-446655440000
- start_date: '2026-02-10'
- end_date: '2026-02-12'
- reason: architecto
- fileParameters: []
- responses:
- -
- custom: []
- status: 201
content: |-
{
"data": {
- "id": "550e8400-e29b-41d4-a716-446655440001",
- "team_member_id": "550e8400-e29b-41d4-a716-446655440000",
- "start_date": "2026-02-10",
- "end_date": "2026-02-12",
- "status": "pending",
- "reason": "Family travel"
+ "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
@@ -654,16 +335,19 @@ endpoints:
custom: []
httpMethods:
- PUT
- uri: 'api/ptos/{id}/approve'
+ - PATCH
+ uri: 'api/projects/{id}'
metadata:
custom: []
- groupName: 'Capacity Planning'
- groupDescription: ''
+ groupName: Projects
+ groupDescription: |-
+
+ Endpoints for managing projects.
subgroup: ''
subgroupDescription: ''
- title: 'Approve PTO'
- description: 'Approve a pending PTO request and refresh the affected capacity caches.'
- authenticated: false
+ title: 'Update a project'
+ description: 'Update details of an existing project.'
+ authenticated: true
deprecated: false
headers:
Content-Type: application/json
@@ -672,16 +356,123 @@ endpoints:
id:
custom: []
name: id
- description: 'The PTO UUID that needs approval.'
+ description: 'Project UUID.'
required: true
- example: 550e8400-e29b-41d4-a716-446655440001
+ example: 550e8400-e29b-41d4-a716-446655440000
type: string
enumValues: []
exampleWasSpecified: true
nullable: false
deprecated: false
cleanUrlParameters:
- id: 550e8400-e29b-41d4-a716-446655440001
+ 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: []
@@ -691,13 +482,302 @@ endpoints:
-
custom: []
status: 200
- content: |-
- {
- "data": {
- "id": "550e8400-e29b-41d4-a716-446655440001",
- "status": "approved"
- }
- }
+ 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: []
diff --git a/backend/.scribe/endpoints/04.yaml b/backend/.scribe/endpoints/04.yaml
new file mode 100644
index 00000000..bffbafc4
--- /dev/null
+++ b/backend/.scribe/endpoints/04.yaml
@@ -0,0 +1,895 @@
+name: 'Capacity Planning'
+description: ''
+endpoints:
+ -
+ custom: []
+ httpMethods:
+ - GET
+ uri: api/capacity
+ metadata:
+ custom: []
+ groupName: 'Capacity Planning'
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: 'Get Individual Capacity'
+ description: 'Calculate capacity for a specific team member in a given month.'
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters:
+ month:
+ custom: []
+ name: month
+ description: 'The month in YYYY-MM format.'
+ required: true
+ example: 2026-02
+ type: string
+ enumValues: []
+ exampleWasSpecified: true
+ nullable: false
+ deprecated: false
+ team_member_id:
+ custom: []
+ name: team_member_id
+ description: 'The team member UUID.'
+ required: true
+ example: 550e8400-e29b-41d4-a716-446655440000
+ type: string
+ enumValues: []
+ exampleWasSpecified: true
+ nullable: false
+ deprecated: false
+ cleanUrlParameters:
+ month: 2026-02
+ team_member_id: 550e8400-e29b-41d4-a716-446655440000
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters:
+ month:
+ custom: []
+ name: month
+ description: 'Must be a valid date in the format Y-m.'
+ required: true
+ example: 2026-02
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ team_member_id:
+ custom: []
+ name: team_member_id
+ description: 'The id of an existing record in the team_members table.'
+ required: true
+ example: architecto
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ cleanBodyParameters:
+ month: 2026-02
+ team_member_id: architecto
+ fileParameters: []
+ responses:
+ -
+ custom: []
+ status: 200
+ content: |-
+ {
+ "data": {
+ "team_member_id": "550e8400-e29b-41d4-a716-446655440000",
+ "month": "2026-02",
+ "working_days": 20,
+ "person_days": 18.5,
+ "hours": 148,
+ "details": [
+ {
+ "date": "2026-02-02",
+ "availability": 1,
+ "is_pto": false
+ }
+ ]
+ }
+ }
+ headers: []
+ description: ''
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - GET
+ uri: api/capacity/team
+ metadata:
+ custom: []
+ groupName: 'Capacity Planning'
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: 'Get Team Capacity'
+ description: 'Summarize the combined capacity for all active team members in a month.'
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters:
+ month:
+ custom: []
+ name: month
+ description: 'The month in YYYY-MM format.'
+ required: true
+ example: 2026-02
+ type: string
+ enumValues: []
+ exampleWasSpecified: true
+ nullable: false
+ deprecated: false
+ cleanUrlParameters:
+ month: 2026-02
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters:
+ month:
+ custom: []
+ name: month
+ description: 'Must be a valid date in the format Y-m.'
+ required: true
+ example: 2026-02
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ cleanBodyParameters:
+ month: 2026-02
+ fileParameters: []
+ responses:
+ -
+ custom: []
+ status: 200
+ content: |-
+ {
+ "data": {
+ "month": "2026-02",
+ "total_person_days": 180.5,
+ "total_hours": 1444,
+ "members": [
+ {
+ "id": "550e8400-e29b-41d4-a716-446655440000",
+ "name": "Ada Lovelace",
+ "person_days": 18.5,
+ "hours": 148
+ }
+ ]
+ }
+ }
+ headers: []
+ description: ''
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - GET
+ uri: api/capacity/revenue
+ metadata:
+ custom: []
+ groupName: 'Capacity Planning'
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: 'Get Possible Revenue'
+ description: 'Estimate monthly revenue based on capacity hours and hourly rates.'
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters:
+ month:
+ custom: []
+ name: month
+ description: 'The month in YYYY-MM format.'
+ required: true
+ example: 2026-02
+ type: string
+ enumValues: []
+ exampleWasSpecified: true
+ nullable: false
+ deprecated: false
+ cleanUrlParameters:
+ month: 2026-02
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters:
+ month:
+ custom: []
+ name: month
+ description: 'Must be a valid date in the format Y-m.'
+ required: true
+ example: 2026-02
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ cleanBodyParameters:
+ month: 2026-02
+ fileParameters: []
+ responses:
+ -
+ custom: []
+ status: 200
+ content: |-
+ {
+ "data": {
+ "month": "2026-02",
+ "possible_revenue": 21500.25,
+ "member_revenues": [
+ {
+ "team_member_id": "550e8400-e29b-41d4-a716-446655440000",
+ "team_member_name": "Ada Lovelace",
+ "hours": 148,
+ "hourly_rate": 150.0,
+ "revenue": 22200.0
+ }
+ ]
+ }
+ }
+ headers: []
+ description: ''
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - POST
+ uri: api/capacity/availability
+ metadata:
+ custom: []
+ groupName: 'Capacity Planning'
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: 'Save Team Member Availability'
+ description: 'Persist a daily availability override and refresh cached capacity totals.'
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters: []
+ cleanUrlParameters: []
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters:
+ team_member_id:
+ custom: []
+ name: team_member_id
+ description: 'The team member UUID.'
+ required: true
+ example: architecto
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ date:
+ custom: []
+ name: date
+ description: 'The date for the availability override (YYYY-MM-DD).'
+ required: true
+ example: architecto
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ availability:
+ custom: []
+ name: availability
+ description: 'The availability value (0, 0.5, 1.0).'
+ required: true
+ example: architecto
+ type: numeric
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ cleanBodyParameters:
+ team_member_id: architecto
+ date: architecto
+ availability: architecto
+ fileParameters: []
+ responses:
+ -
+ custom: []
+ status: 201
+ content: |-
+ {
+ "data": {
+ "team_member_id": "550e8400-e29b-41d4-a716-446655440000",
+ "date": "2026-02-03",
+ "availability": 0.5
+ }
+ }
+ headers: []
+ description: ''
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - POST
+ uri: api/capacity/availability/batch
+ metadata:
+ custom: []
+ groupName: 'Capacity Planning'
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: 'Batch Update Team Member Availability'
+ description: 'Persist multiple daily availability overrides in a single batch operation.'
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters: []
+ cleanUrlParameters: []
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters:
+ month:
+ custom: []
+ name: month
+ description: 'The month in YYYY-MM format.'
+ required: true
+ example: 2026-02
+ type: string
+ enumValues: []
+ exampleWasSpecified: true
+ nullable: false
+ deprecated: false
+ updates:
+ custom: []
+ name: updates
+ description: 'Array of availability updates.'
+ required: true
+ example:
+ - architecto
+ type: 'string[]'
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ 'updates[].team_member_id':
+ custom: []
+ name: 'updates[].team_member_id'
+ description: 'The team member UUID.'
+ required: true
+ example: architecto
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ 'updates[].date':
+ custom: []
+ name: 'updates[].date'
+ description: 'The date (YYYY-MM-DD).'
+ required: true
+ example: architecto
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ 'updates[].availability':
+ custom: []
+ name: 'updates[].availability'
+ description: 'The availability value (0, 0.5, 1).'
+ required: true
+ example: architecto
+ type: numeric
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ cleanBodyParameters:
+ month: 2026-02
+ updates:
+ - architecto
+ fileParameters: []
+ responses:
+ -
+ custom: []
+ status: 200
+ content: |-
+ {
+ "data": {
+ "saved": 12,
+ "month": "2026-02"
+ }
+ }
+ headers: []
+ description: ''
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - GET
+ uri: api/holidays
+ metadata:
+ custom: []
+ groupName: 'Capacity Planning'
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: 'List Holidays'
+ description: 'Retrieve holidays for a specific month or all holidays when no month is provided.'
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters:
+ month:
+ custom: []
+ name: month
+ description: 'nullable The month in YYYY-MM format.'
+ required: false
+ example: 2026-02
+ type: string
+ enumValues: []
+ exampleWasSpecified: true
+ nullable: false
+ deprecated: false
+ cleanUrlParameters:
+ month: 2026-02
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters:
+ month:
+ custom: []
+ name: month
+ description: 'Must be a valid date in the format Y-m.'
+ required: false
+ example: 2026-02
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: true
+ deprecated: false
+ cleanBodyParameters:
+ month: 2026-02
+ fileParameters: []
+ responses:
+ -
+ custom: []
+ status: 200
+ content: |-
+ {
+ "data": [
+ {
+ "id": "550e8400-e29b-41d4-a716-446655440000",
+ "date": "2026-02-14",
+ "name": "Company Holiday",
+ "description": "Office closed"
+ }
+ ]
+ }
+ headers: []
+ description: ''
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - POST
+ uri: api/holidays
+ metadata:
+ custom: []
+ groupName: 'Capacity Planning'
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: 'Create Holiday'
+ description: 'Add a holiday and clear cached capacity data for the related month.'
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters: []
+ cleanUrlParameters: []
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters:
+ date:
+ custom: []
+ name: date
+ description: 'Date of the holiday.'
+ required: true
+ example: '2026-02-14'
+ type: string
+ enumValues: []
+ exampleWasSpecified: true
+ nullable: false
+ deprecated: false
+ name:
+ custom: []
+ name: name
+ description: 'Name of the holiday.'
+ required: true
+ example: "Presidents' Day"
+ type: string
+ enumValues: []
+ exampleWasSpecified: true
+ nullable: false
+ deprecated: false
+ description:
+ custom: []
+ name: description
+ description: 'nullable Optional description of the holiday.'
+ required: false
+ example: 'Eius et animi quos velit et.'
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: true
+ deprecated: false
+ cleanBodyParameters:
+ date: '2026-02-14'
+ name: "Presidents' Day"
+ description: 'Eius et animi quos velit et.'
+ fileParameters: []
+ responses:
+ -
+ custom: []
+ status: 201
+ content: |-
+ {
+ "data": {
+ "id": "550e8400-e29b-41d4-a716-446655440000",
+ "date": "2026-02-14",
+ "name": "Presidents' Day",
+ "description": "Office closed"
+ }
+ }
+ headers: []
+ description: ''
+ -
+ custom: []
+ status: 422
+ content: '{"message":"A holiday already exists for this date.","errors":{"date":["A holiday already exists for this date."]}}'
+ headers: []
+ description: ''
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - DELETE
+ uri: 'api/holidays/{id}'
+ metadata:
+ custom: []
+ groupName: 'Capacity Planning'
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: 'Delete Holiday'
+ description: 'Remove a holiday and clear affected capacity caches.'
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters:
+ id:
+ custom: []
+ name: id
+ description: 'The holiday 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": "Holiday deleted"
+ }
+ headers: []
+ description: ''
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - GET
+ uri: api/ptos
+ metadata:
+ custom: []
+ groupName: 'Capacity Planning'
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: 'List PTO Requests'
+ description: 'Fetch PTO requests for a team member, optionally constrained to a month.'
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters:
+ team_member_id:
+ custom: []
+ name: team_member_id
+ description: 'The team member UUID.'
+ required: true
+ example: 550e8400-e29b-41d4-a716-446655440000
+ type: string
+ enumValues: []
+ exampleWasSpecified: true
+ nullable: false
+ deprecated: false
+ month:
+ custom: []
+ name: month
+ description: 'nullable The month in YYYY-MM format.'
+ required: false
+ example: 2026-02
+ type: string
+ enumValues: []
+ exampleWasSpecified: true
+ nullable: false
+ deprecated: false
+ cleanUrlParameters:
+ team_member_id: 550e8400-e29b-41d4-a716-446655440000
+ month: 2026-02
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters:
+ team_member_id:
+ custom: []
+ name: team_member_id
+ description: 'The id of an existing record in the team_members table.'
+ required: true
+ example: architecto
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ month:
+ custom: []
+ name: month
+ description: 'Must be a valid date in the format Y-m.'
+ required: false
+ example: 2026-02
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: true
+ deprecated: false
+ cleanBodyParameters:
+ team_member_id: architecto
+ month: 2026-02
+ fileParameters: []
+ responses:
+ -
+ custom: []
+ status: 200
+ content: |-
+ {
+ "data": [
+ {
+ "id": "550e8400-e29b-41d4-a716-446655440001",
+ "team_member_id": "550e8400-e29b-41d4-a716-446655440000",
+ "start_date": "2026-02-10",
+ "end_date": "2026-02-12",
+ "status": "pending",
+ "reason": "Family travel"
+ }
+ ]
+ }
+ headers: []
+ description: ''
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - POST
+ uri: api/ptos
+ metadata:
+ custom: []
+ groupName: 'Capacity Planning'
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: 'Request PTO'
+ description: 'Create a PTO request for a team member and approve it immediately.'
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters: []
+ cleanUrlParameters: []
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters:
+ team_member_id:
+ custom: []
+ name: team_member_id
+ description: 'The team member UUID.'
+ required: true
+ example: 550e8400-e29b-41d4-a716-446655440000
+ type: string
+ enumValues: []
+ exampleWasSpecified: true
+ nullable: false
+ deprecated: false
+ start_date:
+ custom: []
+ name: start_date
+ description: 'The first day of the PTO.'
+ required: true
+ example: '2026-02-10'
+ type: string
+ enumValues: []
+ exampleWasSpecified: true
+ nullable: false
+ deprecated: false
+ end_date:
+ custom: []
+ name: end_date
+ description: 'The final day of the PTO.'
+ required: true
+ example: '2026-02-12'
+ type: string
+ enumValues: []
+ exampleWasSpecified: true
+ nullable: false
+ deprecated: false
+ reason:
+ custom: []
+ name: reason
+ description: 'nullable Optional reason for the PTO.'
+ required: false
+ example: architecto
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: true
+ deprecated: false
+ cleanBodyParameters:
+ team_member_id: 550e8400-e29b-41d4-a716-446655440000
+ start_date: '2026-02-10'
+ end_date: '2026-02-12'
+ reason: architecto
+ fileParameters: []
+ responses:
+ -
+ custom: []
+ status: 201
+ content: |-
+ {
+ "data": {
+ "id": "550e8400-e29b-41d4-a716-446655440001",
+ "team_member_id": "550e8400-e29b-41d4-a716-446655440000",
+ "start_date": "2026-02-10",
+ "end_date": "2026-02-12",
+ "status": "approved",
+ "reason": "Family travel"
+ }
+ }
+ headers: []
+ description: ''
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - PUT
+ uri: 'api/ptos/{id}/approve'
+ metadata:
+ custom: []
+ groupName: 'Capacity Planning'
+ groupDescription: ''
+ subgroup: ''
+ subgroupDescription: ''
+ title: 'Approve PTO'
+ description: 'Approve a pending PTO request and refresh the affected capacity caches.'
+ authenticated: false
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters:
+ id:
+ custom: []
+ name: id
+ description: 'The PTO UUID that needs approval.'
+ required: true
+ example: 550e8400-e29b-41d4-a716-446655440001
+ type: string
+ enumValues: []
+ exampleWasSpecified: true
+ nullable: false
+ deprecated: false
+ cleanUrlParameters:
+ id: 550e8400-e29b-41d4-a716-446655440001
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters: []
+ cleanBodyParameters: []
+ fileParameters: []
+ responses:
+ -
+ custom: []
+ status: 200
+ content: |-
+ {
+ "data": {
+ "id": "550e8400-e29b-41d4-a716-446655440001",
+ "status": "approved"
+ }
+ }
+ headers: []
+ description: ''
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
diff --git a/backend/.scribe/endpoints/05.yaml b/backend/.scribe/endpoints/05.yaml
new file mode 100644
index 00000000..e6613716
--- /dev/null
+++ b/backend/.scribe/endpoints/05.yaml
@@ -0,0 +1,493 @@
+name: 'Resource Allocation'
+description: |-
+
+ Endpoints for managing resource allocations.
+endpoints:
+ -
+ custom: []
+ httpMethods:
+ - GET
+ uri: api/allocations
+ metadata:
+ custom: []
+ groupName: 'Resource Allocation'
+ groupDescription: |-
+
+ Endpoints for managing resource allocations.
+ subgroup: ''
+ subgroupDescription: ''
+ title: 'List allocations / Get allocation matrix'
+ description: 'Get all allocations, optionally filtered by month.'
+ authenticated: true
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters: []
+ cleanUrlParameters: []
+ queryParameters:
+ month:
+ custom: []
+ name: month
+ description: 'Filter by month (YYYY-MM format).'
+ required: false
+ example: 2026-02
+ type: string
+ enumValues: []
+ exampleWasSpecified: true
+ nullable: false
+ deprecated: false
+ cleanQueryParameters:
+ month: 2026-02
+ bodyParameters: []
+ cleanBodyParameters: []
+ fileParameters: []
+ responses:
+ -
+ custom: []
+ status: 200
+ content: |-
+ {
+ "data": [
+ {
+ "id": "550e8400-e29b-41d4-a716-446655440000",
+ "project_id": "550e8400-e29b-41d4-a716-446655440001",
+ "team_member_id": "550e8400-e29b-41d4-a716-446655440002",
+ "month": "2026-02",
+ "allocated_hours": 40.00
+ }
+ ]
+ }
+ headers: []
+ description: ''
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - POST
+ uri: api/allocations
+ metadata:
+ custom: []
+ groupName: 'Resource Allocation'
+ groupDescription: |-
+
+ Endpoints for managing resource allocations.
+ subgroup: ''
+ subgroupDescription: ''
+ title: 'Create a new allocation'
+ description: 'Allocate hours for a team member to a project for a specific month.'
+ authenticated: true
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters: []
+ cleanUrlParameters: []
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters:
+ project_id:
+ custom: []
+ name: project_id
+ description: 'Project UUID.'
+ required: true
+ example: 550e8400-e29b-41d4-a716-446655440001
+ type: string
+ enumValues: []
+ exampleWasSpecified: true
+ nullable: false
+ deprecated: false
+ team_member_id:
+ custom: []
+ name: team_member_id
+ description: 'Team member UUID.'
+ required: true
+ example: 550e8400-e29b-41d4-a716-446655440002
+ type: string
+ enumValues: []
+ exampleWasSpecified: true
+ nullable: true
+ deprecated: false
+ month:
+ custom: []
+ name: month
+ description: 'Month (YYYY-MM format).'
+ required: true
+ example: 2026-02
+ type: string
+ enumValues: []
+ exampleWasSpecified: true
+ nullable: false
+ deprecated: false
+ allocated_hours:
+ custom: []
+ name: allocated_hours
+ description: 'Hours to allocate (must be >= 0).'
+ required: true
+ example: '40'
+ type: numeric
+ enumValues: []
+ exampleWasSpecified: true
+ nullable: false
+ deprecated: false
+ cleanBodyParameters:
+ project_id: 550e8400-e29b-41d4-a716-446655440001
+ team_member_id: 550e8400-e29b-41d4-a716-446655440002
+ month: 2026-02
+ allocated_hours: '40'
+ fileParameters: []
+ responses:
+ -
+ custom: []
+ status: 201
+ content: |-
+ {
+ "data": {
+ "id": "550e8400-e29b-41d4-a716-446655440000",
+ "project_id": "550e8400-e29b-41d4-a716-446655440001",
+ "team_member_id": "550e8400-e29b-41d4-a716-446655440002",
+ "month": "2026-02",
+ "allocated_hours": 40.00
+ }
+ }
+ headers: []
+ description: ''
+ -
+ custom: []
+ status: 422
+ content: '{"message":"Validation failed","errors":{"allocated_hours":["Allocated hours must be greater than or equal to 0"]}}'
+ headers: []
+ description: ''
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - GET
+ uri: 'api/allocations/{id}'
+ metadata:
+ custom: []
+ groupName: 'Resource Allocation'
+ groupDescription: |-
+
+ Endpoints for managing resource allocations.
+ subgroup: ''
+ subgroupDescription: ''
+ title: 'Get a single allocation'
+ description: 'Get details of a specific allocation by ID.'
+ authenticated: true
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters:
+ id:
+ custom: []
+ name: id
+ description: 'Allocation 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",
+ "project_id": "550e8400-e29b-41d4-a716-446655440001",
+ "team_member_id": "550e8400-e29b-41d4-a716-446655440002",
+ "month": "2026-02",
+ "allocated_hours": 40.00
+ }
+ }
+ headers: []
+ description: ''
+ -
+ custom: []
+ status: 404
+ content: '{"message": "Allocation not found"}'
+ headers: []
+ description: ''
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - PUT
+ - PATCH
+ uri: 'api/allocations/{id}'
+ metadata:
+ custom: []
+ groupName: 'Resource Allocation'
+ groupDescription: |-
+
+ Endpoints for managing resource allocations.
+ subgroup: ''
+ subgroupDescription: ''
+ title: 'Update an allocation'
+ description: "Update an existing allocation's hours."
+ authenticated: true
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters:
+ id:
+ custom: []
+ name: id
+ description: 'Allocation 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:
+ allocated_hours:
+ custom: []
+ name: allocated_hours
+ description: 'Hours to allocate (must be >= 0).'
+ required: true
+ example: '60'
+ type: numeric
+ enumValues: []
+ exampleWasSpecified: true
+ nullable: false
+ deprecated: false
+ cleanBodyParameters:
+ allocated_hours: '60'
+ fileParameters: []
+ responses:
+ -
+ custom: []
+ status: 200
+ content: |-
+ {
+ "data": {
+ "id": "550e8400-e29b-41d4-a716-446655440000",
+ "project_id": "550e8400-e29b-41d4-a716-446655440001",
+ "team_member_id": "550e8400-e29b-41d4-a716-446655440002",
+ "month": "2026-02",
+ "allocated_hours": 60.00
+ }
+ }
+ headers: []
+ description: ''
+ -
+ custom: []
+ status: 404
+ content: '{"message": "Allocation not found"}'
+ headers: []
+ description: ''
+ -
+ custom: []
+ status: 422
+ content: '{"message":"Validation failed","errors":{"allocated_hours":["Allocated hours must be greater than or equal to 0"]}}'
+ headers: []
+ description: ''
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - DELETE
+ uri: 'api/allocations/{id}'
+ metadata:
+ custom: []
+ groupName: 'Resource Allocation'
+ groupDescription: |-
+
+ Endpoints for managing resource allocations.
+ subgroup: ''
+ subgroupDescription: ''
+ title: 'Delete an allocation'
+ description: 'Remove an allocation.'
+ authenticated: true
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters:
+ id:
+ custom: []
+ name: id
+ description: 'Allocation 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": "Allocation deleted successfully"}'
+ headers: []
+ description: ''
+ -
+ custom: []
+ status: 404
+ content: '{"message": "Allocation not found"}'
+ headers: []
+ description: ''
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
+ -
+ custom: []
+ httpMethods:
+ - POST
+ uri: api/allocations/bulk
+ metadata:
+ custom: []
+ groupName: 'Resource Allocation'
+ groupDescription: |-
+
+ Endpoints for managing resource allocations.
+ subgroup: ''
+ subgroupDescription: ''
+ title: 'Bulk create allocations'
+ description: 'Create or update multiple allocations in a single request.'
+ authenticated: true
+ deprecated: false
+ headers:
+ Content-Type: application/json
+ Accept: application/json
+ urlParameters: []
+ cleanUrlParameters: []
+ queryParameters: []
+ cleanQueryParameters: []
+ bodyParameters:
+ allocations:
+ custom: []
+ name: allocations
+ description: 'Array of allocations.'
+ required: true
+ example:
+ -
+ project_id: ...
+ team_member_id: ...
+ month: 2026-02
+ allocated_hours: 40
+ type: 'string[]'
+ enumValues: []
+ exampleWasSpecified: true
+ nullable: false
+ deprecated: false
+ 'allocations[].project_id':
+ custom: []
+ name: 'allocations[].project_id'
+ description: 'Must be a valid UUID. The id of an existing record in the projects table.'
+ required: true
+ example: 6ff8f7f6-1eb3-3525-be4a-3932c805afed
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ 'allocations[].team_member_id':
+ custom: []
+ name: 'allocations[].team_member_id'
+ description: 'Must be a valid UUID. The id of an existing record in the team_members table.'
+ required: true
+ example: 6b72fe4a-5b40-307c-bc24-f79acf9a1bb9
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ 'allocations[].month':
+ custom: []
+ name: 'allocations[].month'
+ description: 'Must be a valid date in the format Y-m.'
+ required: true
+ example: 2026-02
+ type: string
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ 'allocations[].allocated_hours':
+ custom: []
+ name: 'allocations[].allocated_hours'
+ description: 'Must be at least 0.'
+ required: true
+ example: 77
+ type: number
+ enumValues: []
+ exampleWasSpecified: false
+ nullable: false
+ deprecated: false
+ cleanBodyParameters:
+ allocations:
+ -
+ project_id: ...
+ team_member_id: ...
+ month: 2026-02
+ allocated_hours: 40
+ fileParameters: []
+ responses:
+ -
+ custom: []
+ status: 201
+ content: |-
+ {
+ "data": [
+ {
+ "id": "550e8400-e29b-41d4-a716-446655440000",
+ "project_id": "550e8400-e29b-41d4-a716-446655440001",
+ "team_member_id": "550e8400-e29b-41d4-a716-446655440002",
+ "month": "2026-02",
+ "allocated_hours": 40.00
+ }
+ ]
+ }
+ headers: []
+ description: ''
+ responseFields: []
+ auth: []
+ controller: null
+ method: null
+ route: null
diff --git a/backend/resources/views/scribe/index.blade.php b/backend/resources/views/scribe/index.blade.php
index df547677..2c385777 100644
--- a/backend/resources/views/scribe/index.blade.php
+++ b/backend/resources/views/scribe/index.blade.php
@@ -95,6 +95,12 @@
Must be a valid date in the format Y-m. Example: 2026-02
+
+ +Persist a daily availability override and refresh cached capacity totals.
+ + +Example request:+ + +
curl --request POST \
+ "http://localhost/api/capacity/availability" \
+ --header "Content-Type: application/json" \
+ --header "Accept: application/json" \
+ --data "{
+ \"team_member_id\": \"architecto\",
+ \"date\": \"architecto\",
+ \"availability\": \"architecto\"
+}"
+const url = new URL(
+ "http://localhost/api/capacity/availability"
+);
+
+const headers = {
+ "Content-Type": "application/json",
+ "Accept": "application/json",
+};
+
+let body = {
+ "team_member_id": "architecto",
+ "date": "architecto",
+ "availability": "architecto"
+};
+
+fetch(url, {
+ method: "POST",
+ headers,
+ body: JSON.stringify(body),
+}).then(response => response.json());++Example response (201):
+
+
+{
+ "data": {
+ "team_member_id": "550e8400-e29b-41d4-a716-446655440000",
+ "date": "2026-02-03",
+ "availability": 0.5
+ }
+}
+
+
+
+ Received response: ++
+
+
+ Request failed with error:+
+
+Tip: Check that you're properly connected to the network.
+If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
+You can check the Dev Tools console for debugging information.
+
+
+
+ +
+ +Persist multiple daily availability overrides in a single batch operation.
+ + +Example request:+ + +
curl --request POST \
+ "http://localhost/api/capacity/availability/batch" \
+ --header "Content-Type: application/json" \
+ --header "Accept: application/json" \
+ --data "{
+ \"month\": \"2026-02\",
+ \"updates\": [
+ \"architecto\"
+ ]
+}"
+const url = new URL(
+ "http://localhost/api/capacity/availability/batch"
+);
+
+const headers = {
+ "Content-Type": "application/json",
+ "Accept": "application/json",
+};
+
+let body = {
+ "month": "2026-02",
+ "updates": [
+ "architecto"
+ ]
+};
+
+fetch(url, {
+ method: "POST",
+ headers,
+ body: JSON.stringify(body),
+}).then(response => response.json());++Example response (200):
+
+
+{
+ "data": {
+ "saved": 12,
+ "month": "2026-02"
+ }
+}
+
+
+
+ Received response: ++
+
+
+ Request failed with error:+
+
+Tip: Check that you're properly connected to the network.
+If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
+You can check the Dev Tools console for debugging information.
+
+
++Example response (422):
+
+
+{
+ "message": "A holiday already exists for this date.",
+ "errors": {
+ "date": [
+ "A holiday already exists for this date."
+ ]
+ }
}
@@ -1903,7 +2340,7 @@ You can check the Dev Tools console for debugging information.
-
Create a PTO request for a team member and keep it in pending status.
+Create a PTO request for a team member and approve it immediately.
Example request:@@ -1960,7 +2397,7 @@ fetch(url, { "team_member_id": "550e8400-e29b-41d4-a716-446655440000", "start_date": "2026-02-10", "end_date": "2026-02-12", - "status": "pending", + "status": "approved", "reason": "Family travel" } } @@ -2220,6 +2657,581 @@ You can check the Dev Tools console for debugging information. data-component="url">
The PTO UUID that needs approval. Example: 550e8400-e29b-41d4-a716-446655440001
+
+ + + + +Example request:+ + +
curl --request GET \
+ --get "http://localhost/api/user" \
+ --header "Content-Type: application/json" \
+ --header "Accept: application/json"const url = new URL(
+ "http://localhost/api/user"
+);
+
+const headers = {
+ "Content-Type": "application/json",
+ "Accept": "application/json",
+};
+
+fetch(url, {
+ method: "GET",
+ headers,
+}).then(response => response.json());++Example response (401):
+
cache-control: no-cache, private
+content-type: application/json
+access-control-allow-origin: *
+
+
+{
+ "message": "Authentication required"
+}
+
+
+
+ Received response: ++
+
+
+ Request failed with error:+
+
+Tip: Check that you're properly connected to the network.
+If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
+You can check the Dev Tools console for debugging information.
+
+
+
+ +
+ + + + +Example request:+ + +
curl --request GET \
+ --get "http://localhost/api/project-month-plans" \
+ --header "Content-Type: application/json" \
+ --header "Accept: application/json"const url = new URL(
+ "http://localhost/api/project-month-plans"
+);
+
+const headers = {
+ "Content-Type": "application/json",
+ "Accept": "application/json",
+};
+
+fetch(url, {
+ method: "GET",
+ headers,
+}).then(response => response.json());++Example response (401):
+
cache-control: no-cache, private
+content-type: application/json
+access-control-allow-origin: *
+
+
+{
+ "message": "Authentication required"
+}
+
+
+
+ Received response: ++
+
+
+ Request failed with error:+
+
+Tip: Check that you're properly connected to the network.
+If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
+You can check the Dev Tools console for debugging information.
+
+
+
+ +
+ + + + +Example request:+ + +
curl --request PUT \
+ "http://localhost/api/project-month-plans/bulk" \
+ --header "Content-Type: application/json" \
+ --header "Accept: application/json" \
+ --data "{
+ \"year\": 1,
+ \"items\": [
+ {
+ \"project_id\": \"6ff8f7f6-1eb3-3525-be4a-3932c805afed\",
+ \"month\": \"2026-02\",
+ \"planned_hours\": 84
+ }
+ ]
+}"
+const url = new URL(
+ "http://localhost/api/project-month-plans/bulk"
+);
+
+const headers = {
+ "Content-Type": "application/json",
+ "Accept": "application/json",
+};
+
+let body = {
+ "year": 1,
+ "items": [
+ {
+ "project_id": "6ff8f7f6-1eb3-3525-be4a-3932c805afed",
+ "month": "2026-02",
+ "planned_hours": 84
+ }
+ ]
+};
+
+fetch(url, {
+ method: "PUT",
+ headers,
+ body: JSON.stringify(body),
+}).then(response => response.json());Received response: ++
+
+
+ Request failed with error:+
+
+Tip: Check that you're properly connected to the network.
+If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
+You can check the Dev Tools console for debugging information.
+
+
+
+ +
+ + + + +Example request:+ + +
curl --request DELETE \
+ "http://localhost/api/ptos/architecto" \
+ --header "Content-Type: application/json" \
+ --header "Accept: application/json"const url = new URL(
+ "http://localhost/api/ptos/architecto"
+);
+
+const headers = {
+ "Content-Type": "application/json",
+ "Accept": "application/json",
+};
+
+fetch(url, {
+ method: "DELETE",
+ headers,
+}).then(response => response.json());Received response: ++
+
+
+ Request failed with error:+
+
+Tip: Check that you're properly connected to the network.
+If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
+You can check the Dev Tools console for debugging information.
+
+
@@ -3971,6 +4983,1051 @@ You can check the Dev Tools console for debugging information.
Monthly effort breakdown.
+ + +Endpoints for managing resource allocations.
+ ++requires authentication +
+ +Get all allocations, optionally filtered by month.
+ + +Example request:+ + +
curl --request GET \
+ --get "http://localhost/api/allocations?month=2026-02" \
+ --header "Content-Type: application/json" \
+ --header "Accept: application/json"const url = new URL(
+ "http://localhost/api/allocations"
+);
+
+const params = {
+ "month": "2026-02",
+};
+Object.keys(params)
+ .forEach(key => url.searchParams.append(key, params[key]));
+
+const headers = {
+ "Content-Type": "application/json",
+ "Accept": "application/json",
+};
+
+fetch(url, {
+ method: "GET",
+ headers,
+}).then(response => response.json());++Example response (200):
+
+
+{
+ "data": [
+ {
+ "id": "550e8400-e29b-41d4-a716-446655440000",
+ "project_id": "550e8400-e29b-41d4-a716-446655440001",
+ "team_member_id": "550e8400-e29b-41d4-a716-446655440002",
+ "month": "2026-02",
+ "allocated_hours": 40
+ }
+ ]
+}
+
+
+
+ Received response: ++
+
+
+ Request failed with error:+
+
+Tip: Check that you're properly connected to the network.
+If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
+You can check the Dev Tools console for debugging information.
+
+
+
+ +requires authentication +
+ +Allocate hours for a team member to a project for a specific month.
+ + +Example request:+ + +
curl --request POST \
+ "http://localhost/api/allocations" \
+ --header "Content-Type: application/json" \
+ --header "Accept: application/json" \
+ --data "{
+ \"project_id\": \"550e8400-e29b-41d4-a716-446655440001\",
+ \"team_member_id\": \"550e8400-e29b-41d4-a716-446655440002\",
+ \"month\": \"2026-02\",
+ \"allocated_hours\": \"40\"
+}"
+const url = new URL(
+ "http://localhost/api/allocations"
+);
+
+const headers = {
+ "Content-Type": "application/json",
+ "Accept": "application/json",
+};
+
+let body = {
+ "project_id": "550e8400-e29b-41d4-a716-446655440001",
+ "team_member_id": "550e8400-e29b-41d4-a716-446655440002",
+ "month": "2026-02",
+ "allocated_hours": "40"
+};
+
+fetch(url, {
+ method: "POST",
+ headers,
+ body: JSON.stringify(body),
+}).then(response => response.json());++Example response (201):
+
+
+{
+ "data": {
+ "id": "550e8400-e29b-41d4-a716-446655440000",
+ "project_id": "550e8400-e29b-41d4-a716-446655440001",
+ "team_member_id": "550e8400-e29b-41d4-a716-446655440002",
+ "month": "2026-02",
+ "allocated_hours": 40
+ }
+}
+
+ ++Example response (422):
+
+
+{
+ "message": "Validation failed",
+ "errors": {
+ "allocated_hours": [
+ "Allocated hours must be greater than or equal to 0"
+ ]
+ }
+}
+
+
+
+ Received response: ++
+
+
+ Request failed with error:+
+
+Tip: Check that you're properly connected to the network.
+If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
+You can check the Dev Tools console for debugging information.
+
+
+
+ +requires authentication +
+ +Get details of a specific allocation by ID.
+ + +Example request:+ + +
curl --request GET \
+ --get "http://localhost/api/allocations/550e8400-e29b-41d4-a716-446655440000" \
+ --header "Content-Type: application/json" \
+ --header "Accept: application/json"const url = new URL(
+ "http://localhost/api/allocations/550e8400-e29b-41d4-a716-446655440000"
+);
+
+const headers = {
+ "Content-Type": "application/json",
+ "Accept": "application/json",
+};
+
+fetch(url, {
+ method: "GET",
+ headers,
+}).then(response => response.json());++Example response (200):
+
+
+{
+ "data": {
+ "id": "550e8400-e29b-41d4-a716-446655440000",
+ "project_id": "550e8400-e29b-41d4-a716-446655440001",
+ "team_member_id": "550e8400-e29b-41d4-a716-446655440002",
+ "month": "2026-02",
+ "allocated_hours": 40
+ }
+}
+
+ ++Example response (404):
+
+
+{
+ "message": "Allocation not found"
+}
+
+
+
+ Received response: ++
+
+
+ Request failed with error:+
+
+Tip: Check that you're properly connected to the network.
+If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
+You can check the Dev Tools console for debugging information.
+
+
+
+ +requires authentication +
+ +Update an existing allocation's hours.
+ + +Example request:+ + +
curl --request PUT \
+ "http://localhost/api/allocations/550e8400-e29b-41d4-a716-446655440000" \
+ --header "Content-Type: application/json" \
+ --header "Accept: application/json" \
+ --data "{
+ \"allocated_hours\": \"60\"
+}"
+const url = new URL(
+ "http://localhost/api/allocations/550e8400-e29b-41d4-a716-446655440000"
+);
+
+const headers = {
+ "Content-Type": "application/json",
+ "Accept": "application/json",
+};
+
+let body = {
+ "allocated_hours": "60"
+};
+
+fetch(url, {
+ method: "PUT",
+ headers,
+ body: JSON.stringify(body),
+}).then(response => response.json());++Example response (200):
+
+
+{
+ "data": {
+ "id": "550e8400-e29b-41d4-a716-446655440000",
+ "project_id": "550e8400-e29b-41d4-a716-446655440001",
+ "team_member_id": "550e8400-e29b-41d4-a716-446655440002",
+ "month": "2026-02",
+ "allocated_hours": 60
+ }
+}
+
+ ++Example response (404):
+
+
+{
+ "message": "Allocation not found"
+}
+
+ ++Example response (422):
+
+
+{
+ "message": "Validation failed",
+ "errors": {
+ "allocated_hours": [
+ "Allocated hours must be greater than or equal to 0"
+ ]
+ }
+}
+
+
+
+ Received response: ++
+
+
+ Request failed with error:+
+
+Tip: Check that you're properly connected to the network.
+If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
+You can check the Dev Tools console for debugging information.
+
+
+
+ +requires authentication +
+ +Remove an allocation.
+ + +Example request:+ + +
curl --request DELETE \
+ "http://localhost/api/allocations/550e8400-e29b-41d4-a716-446655440000" \
+ --header "Content-Type: application/json" \
+ --header "Accept: application/json"const url = new URL(
+ "http://localhost/api/allocations/550e8400-e29b-41d4-a716-446655440000"
+);
+
+const headers = {
+ "Content-Type": "application/json",
+ "Accept": "application/json",
+};
+
+fetch(url, {
+ method: "DELETE",
+ headers,
+}).then(response => response.json());++Example response (200):
+
+
+{
+ "message": "Allocation deleted successfully"
+}
+
+ ++Example response (404):
+
+
+{
+ "message": "Allocation not found"
+}
+
+
+
+ Received response: ++
+
+
+ Request failed with error:+
+
+Tip: Check that you're properly connected to the network.
+If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
+You can check the Dev Tools console for debugging information.
+
+
+
+ +requires authentication +
+ +Create or update multiple allocations in a single request.
+ + +Example request:+ + +
curl --request POST \
+ "http://localhost/api/allocations/bulk" \
+ --header "Content-Type: application/json" \
+ --header "Accept: application/json" \
+ --data "{
+ \"allocations\": [
+ {
+ \"project_id\": \"...\",
+ \"team_member_id\": \"...\",
+ \"month\": \"2026-02\",
+ \"allocated_hours\": 40
+ }
+ ]
+}"
+const url = new URL(
+ "http://localhost/api/allocations/bulk"
+);
+
+const headers = {
+ "Content-Type": "application/json",
+ "Accept": "application/json",
+};
+
+let body = {
+ "allocations": [
+ {
+ "project_id": "...",
+ "team_member_id": "...",
+ "month": "2026-02",
+ "allocated_hours": 40
+ }
+ ]
+};
+
+fetch(url, {
+ method: "POST",
+ headers,
+ body: JSON.stringify(body),
+}).then(response => response.json());++Example response (201):
+
+
+{
+ "data": [
+ {
+ "id": "550e8400-e29b-41d4-a716-446655440000",
+ "project_id": "550e8400-e29b-41d4-a716-446655440001",
+ "team_member_id": "550e8400-e29b-41d4-a716-446655440002",
+ "month": "2026-02",
+ "allocated_hours": 40
+ }
+ ]
+}
+
+
+
+ Received response: ++
+
+
+ Request failed with error:+
+
+Tip: Check that you're properly connected to the network.
+If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
+You can check the Dev Tools console for debugging information.
+
+