From b9775f2f5a77f608a74b0863a991addd81690cb0 Mon Sep 17 00:00:00 2001 From: Santhosh Janardhanan Date: Sun, 8 Mar 2026 18:23:11 -0400 Subject: [PATCH] docs(api): regenerate Scribe API documentation Update auto-generated API documentation: - New endpoints: ProjectMonthPlan, Reports, Roles - Updated endpoint docs for Allocations and Projects - Regenerated Scribe index with new endpoints Documentation now reflects enhanced-allocation features. --- .ralph/ralph-loop.state.json | 13 - backend/.scribe/endpoints.cache/01.yaml | 430 +--- backend/.scribe/endpoints.cache/02.yaml | 642 ++--- backend/.scribe/endpoints.cache/03.yaml | 1018 ++++---- backend/.scribe/endpoints.cache/04.yaml | 897 +++++++ backend/.scribe/endpoints.cache/05.yaml | 495 ++++ backend/.scribe/endpoints/01.yaml | 430 +--- backend/.scribe/endpoints/02.yaml | 642 ++--- backend/.scribe/endpoints/03.yaml | 1018 ++++---- backend/.scribe/endpoints/04.yaml | 895 +++++++ backend/.scribe/endpoints/05.yaml | 493 ++++ .../resources/views/scribe/index.blade.php | 2063 ++++++++++++++++- 12 files changed, 6446 insertions(+), 2590 deletions(-) delete mode 100644 .ralph/ralph-loop.state.json create mode 100644 backend/.scribe/endpoints.cache/04.yaml create mode 100644 backend/.scribe/endpoints.cache/05.yaml create mode 100644 backend/.scribe/endpoints/04.yaml create mode 100644 backend/.scribe/endpoints/05.yaml 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. DONE when complete.", - "startedAt": "2026-02-18T19:18:44.320Z", - "model": "", - "agent": "opencode" -} \ No newline at end of file diff --git a/backend/.scribe/endpoints.cache/01.yaml b/backend/.scribe/endpoints.cache/01.yaml index 0f06fc4e..14b88bac 100644 --- a/backend/.scribe/endpoints.cache/01.yaml +++ b/backend/.scribe/endpoints.cache/01.yaml @@ -1,93 +1,22 @@ ## Autogenerated by Scribe. DO NOT MODIFY. -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 @@ -96,84 +25,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 @@ -183,36 +47,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: [] @@ -221,30 +73,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 @@ -254,123 +89,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 @@ -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 @@
  • Get Possible Revenue +
  • +
  • + Save Team Member Availability +
  • +
  • + Batch Update Team Member Availability
  • List Holidays @@ -116,6 +122,27 @@
  • + + @@ -1240,6 +1292,377 @@ You can check the Dev Tools console for debugging information.

    Must be a valid date in the format Y-m. Example: 2026-02

    + + +

    Save Team Member Availability

    + +

    +

    + +

    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
    +    }
    +}
    + 
    +
    + + +
    +

    + Request    + +    + +

    +

    + POST + api/capacity/availability +

    +

    Headers

    +
    + Content-Type   +  +   +   + +
    +

    Example: application/json

    +
    +
    + Accept   +  +   +   + +
    +

    Example: application/json

    +
    +

    Body Parameters

    +
    + team_member_id   +string  +   +   + +
    +

    The team member UUID. Example: architecto

    +
    +
    + date   +string  +   +   + +
    +

    The date for the availability override (YYYY-MM-DD). Example: architecto

    +
    +
    + availability   +numeric  +   +   + +
    +

    The availability value (0, 0.5, 1.0). Example: architecto

    +
    +
    + +

    Batch Update Team Member Availability

    + +

    +

    + +

    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"
    +    }
    +}
    + 
    +
    + + +
    +

    + Request    + +    + +

    +

    + POST + api/capacity/availability/batch +

    +

    Headers

    +
    + Content-Type   +  +   +   + +
    +

    Example: application/json

    +
    +
    + Accept   +  +   +   + +
    +

    Example: application/json

    +
    +

    Body Parameters

    +
    + month   +string  +   +   + +
    +

    The month in YYYY-MM format. Example: 2026-02

    +
    +
    +
    + + updates   +string[]  +   +   +
    +

    Array of availability updates.

    +
    +
    + team_member_id   +string  +   +   + +
    +

    The team member UUID. Example: architecto

    +
    +
    + date   +string  +   +   + +
    +

    The date (YYYY-MM-DD). Example: architecto

    +
    +
    + availability   +numeric  +   +   + +
    +

    The availability value (0, 0.5, 1). Example: architecto

    +
    +
    +

    List Holidays

    @@ -1464,6 +1887,20 @@ fetch(url, { "name": "Presidents' Day", "description": "Office closed" } +} + +
    +

    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

    + + + +

    Endpoints

    + + + +

    GET api/user

    + +

    +

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

    +
    +
    + + Show headers + +
    cache-control: no-cache, private
    +content-type: application/json
    +access-control-allow-origin: *
    + 
    +
    +{
    +    "message": "Authentication required"
    +}
    + 
    +
    + + +
    +

    + Request    + +    + +

    +

    + GET + api/user +

    +

    Headers

    +
    + Content-Type   +  +   +   + +
    +

    Example: application/json

    +
    +
    + Accept   +  +   +   + +
    +

    Example: application/json

    +
    +
    + +

    GET /api/project-month-plans?year=2026 +Returns month-plan grid payload by project/month for the year.

    + +

    +

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

    +
    +
    + + Show headers + +
    cache-control: no-cache, private
    +content-type: application/json
    +access-control-allow-origin: *
    + 
    +
    +{
    +    "message": "Authentication required"
    +}
    + 
    +
    + + +
    +

    + Request    + +    + +

    +

    + GET + api/project-month-plans +

    +

    Headers

    +
    + Content-Type   +  +   +   + +
    +

    Example: application/json

    +
    +
    + Accept   +  +   +   + +
    +

    Example: application/json

    +
    +
    + +

    PUT /api/project-month-plans/bulk +Bulk upsert month plan cells.

    + +

    +

    + + + + +
    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());
    + +
    + + + + + +
    +

    + Request    + +    + +

    +

    + PUT + api/project-month-plans/bulk +

    +

    Headers

    +
    + Content-Type   +  +   +   + +
    +

    Example: application/json

    +
    +
    + Accept   +  +   +   + +
    +

    Example: application/json

    +
    +

    Body Parameters

    +
    + year   +integer  +   +   + +
    +

    Must be at least 2020. Must not be greater than 2100. Example: 1

    +
    +
    +
    + + items   +object[]  +   +   +
    + +
    +
    + project_id   +string  +   +   + +
    +

    Must be a valid UUID. The id of an existing record in the projects table. Example: 6ff8f7f6-1eb3-3525-be4a-3932c805afed

    +
    +
    + month   +string  +   +   + +
    +

    Must be a valid date in the format Y-m. Example: 2026-02

    +
    +
    + planned_hours   +number  +optional   +   + +
    +

    Must be at least 0. Example: 84

    +
    +
    +
    +
    + +

    DELETE api/ptos/{id}

    + +

    +

    + + + + +
    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());
    + +
    + + + + + +
    +

    + Request    + +    + +

    +

    + DELETE + api/ptos/{id} +

    +

    Headers

    +
    + Content-Type   +  +   +   + +
    +

    Example: application/json

    +
    +
    + Accept   +  +   +   + +
    +

    Example: application/json

    +
    +

    URL Parameters

    +
    + id   +string  +   +   + +
    +

    The ID of the pto. Example: architecto

    @@ -3971,6 +4983,1051 @@ You can check the Dev Tools console for debugging information.

    Monthly effort breakdown.

    + + +

    Resource Allocation

    + +

    Endpoints for managing resource allocations.

    + +

    List allocations / Get allocation matrix

    + +

    +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
    +        }
    +    ]
    +}
    + 
    +
    + + +
    +

    + Request    + +    + +

    +

    + GET + api/allocations +

    +

    Headers

    +
    + Content-Type   +  +   +   + +
    +

    Example: application/json

    +
    +
    + Accept   +  +   +   + +
    +

    Example: application/json

    +
    +

    Query Parameters

    +
    + month   +string  +optional   +   + +
    +

    Filter by month (YYYY-MM format). Example: 2026-02

    +
    +
    + +

    Create a new allocation

    + +

    +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"
    +        ]
    +    }
    +}
    + 
    +
    + + +
    +

    + Request    + +    + +

    +

    + POST + api/allocations +

    +

    Headers

    +
    + Content-Type   +  +   +   + +
    +

    Example: application/json

    +
    +
    + Accept   +  +   +   + +
    +

    Example: application/json

    +
    +

    Body Parameters

    +
    + project_id   +string  +   +   + +
    +

    Project UUID. Example: 550e8400-e29b-41d4-a716-446655440001

    +
    +
    + team_member_id   +string  +   +   + +
    +

    Team member UUID. Example: 550e8400-e29b-41d4-a716-446655440002

    +
    +
    + month   +string  +   +   + +
    +

    Month (YYYY-MM format). Example: 2026-02

    +
    +
    + allocated_hours   +numeric  +   +   + +
    +

    Hours to allocate (must be >= 0). Example: 40

    +
    +
    + +

    Get a single allocation

    + +

    +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"
    +}
    + 
    +
    + + +
    +

    + Request    + +    + +

    +

    + GET + api/allocations/{id} +

    +

    Headers

    +
    + Content-Type   +  +   +   + +
    +

    Example: application/json

    +
    +
    + Accept   +  +   +   + +
    +

    Example: application/json

    +
    +

    URL Parameters

    +
    + id   +string  +   +   + +
    +

    Allocation UUID. Example: 550e8400-e29b-41d4-a716-446655440000

    +
    +
    + +

    Update an allocation

    + +

    +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"
    +        ]
    +    }
    +}
    + 
    +
    + + +
    +

    + Request    + +    + +

    +

    + PUT + api/allocations/{id} +

    +

    + PATCH + api/allocations/{id} +

    +

    Headers

    +
    + Content-Type   +  +   +   + +
    +

    Example: application/json

    +
    +
    + Accept   +  +   +   + +
    +

    Example: application/json

    +
    +

    URL Parameters

    +
    + id   +string  +   +   + +
    +

    Allocation UUID. Example: 550e8400-e29b-41d4-a716-446655440000

    +
    +

    Body Parameters

    +
    + allocated_hours   +numeric  +   +   + +
    +

    Hours to allocate (must be >= 0). Example: 60

    +
    +
    + +

    Delete an allocation

    + +

    +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"
    +}
    + 
    +
    + + +
    +

    + Request    + +    + +

    +

    + DELETE + api/allocations/{id} +

    +

    Headers

    +
    + Content-Type   +  +   +   + +
    +

    Example: application/json

    +
    +
    + Accept   +  +   +   + +
    +

    Example: application/json

    +
    +

    URL Parameters

    +
    + id   +string  +   +   + +
    +

    Allocation UUID. Example: 550e8400-e29b-41d4-a716-446655440000

    +
    +
    + +

    Bulk create allocations

    + +

    +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
    +        }
    +    ]
    +}
    + 
    +
    + + +
    +

    + Request    + +    + +

    +

    + POST + api/allocations/bulk +

    +

    Headers

    +
    + Content-Type   +  +   +   + +
    +

    Example: application/json

    +
    +
    + Accept   +  +   +   + +
    +

    Example: application/json

    +
    +

    Body Parameters

    +
    +
    + + allocations   +string[]  +   +   +
    +

    Array of allocations.

    +
    +
    + project_id   +string  +   +   + +
    +

    Must be a valid UUID. The id of an existing record in the projects table. Example: 6ff8f7f6-1eb3-3525-be4a-3932c805afed

    +
    +
    + team_member_id   +string  +   +   + +
    +

    Must be a valid UUID. The id of an existing record in the team_members table. Example: 6b72fe4a-5b40-307c-bc24-f79acf9a1bb9

    +
    +
    + month   +string  +   +   + +
    +

    Must be a valid date in the format Y-m. Example: 2026-02

    +
    +
    + allocated_hours   +number  +   +   + +
    +

    Must be at least 0. Example: 77

    +
    +
    +

    Team Members