diff --git a/PostFundManagement.ConsoleUI/.env.example b/PostFundManagement.ConsoleUI/.env.example
index d2c8f7c..0f6d60e 100644
--- a/PostFundManagement.ConsoleUI/.env.example
+++ b/PostFundManagement.ConsoleUI/.env.example
@@ -17,6 +17,12 @@ VITE_OIDC_CLIENT_SECRET="secret_71f88570a46eb22eb5270b47f69e3332c84d8db85d008f02
# PFM Backend API Base URL
VITE_API_BASE_URL="https://api.ifri.khongisa.co.za/"
+# Protocol / SSL Settings for local development
+# HTTPS is enabled by default when running locally via @vitejs/plugin-basic-ssl.
+# Set to 'false' if you ever need to run local dev over plain HTTP.
+HTTPS="true"
+VITE_ENABLE_HTTPS="true"
+
# HasherSettings for HashIds compatibility with backend pfm-api
VITE_HASHER_MIN_LENGTH="11"
VITE_HASHER_SALT="TGlnaHQgQ2hhcm1zLCBNaWRyYW5kQm9va3MgbWFrZSBsb3RzIG9mIG1vbmV5IGFuZCBhcmUgYSBzdWNjZXNzZnVsIHZpcmFsIHN0b3J5IGluIFNvdXRoIEFmcmljYQ=="
diff --git a/PostFundManagement.ConsoleUI/all_endpoints.json b/PostFundManagement.ConsoleUI/all_endpoints.json
new file mode 100644
index 0000000..1b2763e
--- /dev/null
+++ b/PostFundManagement.ConsoleUI/all_endpoints.json
@@ -0,0 +1,3268 @@
+[
+ {
+ "tag": "Users",
+ "method": "GET",
+ "path": "/v1/api/users/{userId}",
+ "opId": "getuser",
+ "desc": "Gets the metadata of a single user.",
+ "params": [
+ {
+ "name": "userId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "format": "uuid"
+ }
+ }
+ ],
+ "reqBody": null,
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "404": {
+ "desc": "Not Found",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Users",
+ "method": "POST",
+ "path": "/v1/api/users/{page}/{pageSize}",
+ "opId": "getusers",
+ "desc": "Paginated, searchable directory of system users. Supports filtering by ActivityStatus, searching by Email, and sorting",
+ "params": [
+ {
+ "name": "page",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": "integer",
+ "format": "int32",
+ "default": 1
+ }
+ },
+ {
+ "name": "pageSize",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": "integer",
+ "format": "int32",
+ "default": 100
+ }
+ }
+ ],
+ "reqBody": {
+ "required": true,
+ "contentType": "application/json; version=1",
+ "schema": {
+ "$ref": "#/components/schemas/UserFilterRequest"
+ }
+ },
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "404": {
+ "desc": "Not Found",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Users",
+ "method": "POST",
+ "path": "/v1/api/integration/sync",
+ "opId": "syncuser",
+ "desc": "Trigger integration and data synchronization in the background",
+ "params": [],
+ "reqBody": null,
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": null
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Users",
+ "method": "GET",
+ "path": "/v1/api/users/{userId}/status/{status}",
+ "opId": "updateuserstatus",
+ "desc": "Changes the Activity status of the user in the system",
+ "params": [
+ {
+ "name": "userId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "format": "uuid"
+ }
+ },
+ {
+ "name": "status",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/ActivityStatus"
+ }
+ }
+ ],
+ "reqBody": null,
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": null
+ },
+ "400": {
+ "desc": "Bad Request",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "404": {
+ "desc": "Not Found",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Projects",
+ "method": "POST",
+ "path": "/v1/api/projects/activities",
+ "opId": "createactivity",
+ "desc": "Add a new activity under project (M04)",
+ "params": [],
+ "reqBody": {
+ "required": true,
+ "contentType": "application/json; version=1",
+ "schema": {
+ "$ref": "#/components/schemas/CreateActivityRequest"
+ }
+ },
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": null
+ },
+ "400": {
+ "desc": "Bad Request",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Projects",
+ "method": "POST",
+ "path": "/v1/api/projects/programme",
+ "opId": "createprogramme",
+ "desc": "Create programme",
+ "params": [],
+ "reqBody": {
+ "required": true,
+ "contentType": "application/json; version=1",
+ "schema": {
+ "$ref": "#/components/schemas/CreateProgrammeRequest"
+ }
+ },
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": null
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "403": {
+ "desc": "Forbidden",
+ "schema": null
+ },
+ "404": {
+ "desc": "Not Found",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Projects",
+ "method": "PUT",
+ "path": "/v1/api/projects/programme",
+ "opId": "updateprogramme",
+ "desc": "Update existing programme",
+ "params": [],
+ "reqBody": {
+ "required": true,
+ "contentType": "application/json; version=1",
+ "schema": {
+ "$ref": "#/components/schemas/UpdateProgrammeRequest"
+ }
+ },
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": null
+ },
+ "400": {
+ "desc": "Bad Request",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "403": {
+ "desc": "Forbidden",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Projects",
+ "method": "POST",
+ "path": "/v1/api/projects",
+ "opId": "createproject",
+ "desc": "Create a new project (M04)",
+ "params": [],
+ "reqBody": {
+ "required": true,
+ "contentType": "application/json; version=1",
+ "schema": {
+ "$ref": "#/components/schemas/CreateProjectRequest"
+ }
+ },
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": null
+ },
+ "400": {
+ "desc": "Bad Request",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "404": {
+ "desc": "Not Found",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Projects",
+ "method": "GET",
+ "path": "/v1/api/projects",
+ "opId": "getprojects",
+ "desc": "Get a list of projects",
+ "params": [
+ {
+ "name": "page",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int32",
+ "default": 1
+ }
+ },
+ {
+ "name": "pageSize",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int32",
+ "default": 100
+ }
+ }
+ ],
+ "reqBody": null,
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Project"
+ }
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Projects",
+ "method": "PUT",
+ "path": "/v1/api/projects",
+ "opId": "updateproject",
+ "desc": "Update project details",
+ "params": [],
+ "reqBody": {
+ "required": true,
+ "contentType": "application/json; version=1",
+ "schema": {
+ "$ref": "#/components/schemas/UpdateProjectRequest"
+ }
+ },
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": null
+ },
+ "400": {
+ "desc": "Bad Request",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Projects",
+ "method": "GET",
+ "path": "/v1/api/projects/{projectId}/activities",
+ "opId": "getactivities",
+ "desc": "Get a list of activities for a project",
+ "params": [
+ {
+ "name": "projectId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "reqBody": null,
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Activity"
+ }
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "403": {
+ "desc": "Forbidden",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Projects",
+ "method": "GET",
+ "path": "/v1/api/projects/activities/{activityId}",
+ "opId": "getactivity",
+ "desc": "Gets a specific activity by it's id",
+ "params": [
+ {
+ "name": "activityId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "reqBody": null,
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": {
+ "$ref": "#/components/schemas/Activity"
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "403": {
+ "desc": "Forbidden",
+ "schema": null
+ },
+ "404": {
+ "desc": "Not Found",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Projects",
+ "method": "GET",
+ "path": "/v1/api/projects/milestones/{milestoneId}",
+ "opId": "getmilestone",
+ "desc": "Gets a specific milestone",
+ "params": [
+ {
+ "name": "milestoneId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "reqBody": null,
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": {
+ "$ref": "#/components/schemas/Milestone"
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "403": {
+ "desc": "Forbidden",
+ "schema": null
+ },
+ "404": {
+ "desc": "Not Found",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Projects",
+ "method": "GET",
+ "path": "/v1/api/projects/milestones/{page}/{pageSize}",
+ "opId": "getmilestones",
+ "desc": "Get a list of all milestones",
+ "params": [
+ {
+ "name": "page",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": "integer",
+ "format": "int32",
+ "default": 1
+ }
+ },
+ {
+ "name": "pageSize",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": "integer",
+ "format": "int32",
+ "default": 100
+ }
+ }
+ ],
+ "reqBody": null,
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Milestone"
+ }
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Projects",
+ "method": "GET",
+ "path": "/v1/api/projects/programme/{programmeId}",
+ "opId": "getprogramme",
+ "desc": "Get specific programme",
+ "params": [
+ {
+ "name": "programmeId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "reqBody": null,
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": {
+ "$ref": "#/components/schemas/Programme"
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "403": {
+ "desc": "Forbidden",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Projects",
+ "method": "GET",
+ "path": "/v1/api/projects/{programmeId}/programme",
+ "opId": "getprogrammeprojects",
+ "desc": "Get a list of all projects under a programme",
+ "params": [
+ {
+ "name": "programmeId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "reqBody": null,
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Project"
+ }
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "403": {
+ "desc": "Forbidden",
+ "schema": null
+ },
+ "404": {
+ "desc": "Not Found",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Projects",
+ "method": "GET",
+ "path": "/v1/api/projects/programme/{page}/{pageSize}",
+ "opId": "getprogrammesprojects",
+ "desc": "Get a list of all programmes",
+ "params": [
+ {
+ "name": "page",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": "integer",
+ "format": "int32",
+ "default": 1
+ }
+ },
+ {
+ "name": "pageSize",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": "integer",
+ "format": "int32",
+ "default": 100
+ }
+ }
+ ],
+ "reqBody": null,
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Programme"
+ }
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "403": {
+ "desc": "Forbidden",
+ "schema": null
+ },
+ "404": {
+ "desc": "Not Found",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Projects",
+ "method": "GET",
+ "path": "/v1/api/projects/{projectId}",
+ "opId": "getproject",
+ "desc": "Get project by ID",
+ "params": [
+ {
+ "name": "projectId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "reqBody": null,
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Project"
+ }
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "403": {
+ "desc": "Forbidden",
+ "schema": null
+ },
+ "404": {
+ "desc": "Not Found",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Projects",
+ "method": "GET",
+ "path": "/v1/api/projects/indicators/project/{projectId}",
+ "opId": "getprojectindicators",
+ "desc": "Get get a detailed list of project indicators",
+ "params": [
+ {
+ "name": "projectId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "reqBody": null,
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Indicator"
+ }
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "403": {
+ "desc": "Forbidden",
+ "schema": null
+ },
+ "404": {
+ "desc": "Not Found",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Projects",
+ "method": "GET",
+ "path": "/v1/api/projects/{projectId}/milestones",
+ "opId": "getprojectmilestones",
+ "desc": "Get a list of milestones for a project",
+ "params": [
+ {
+ "name": "projectId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "reqBody": null,
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Milestone"
+ }
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "403": {
+ "desc": "Forbidden",
+ "schema": null
+ },
+ "404": {
+ "desc": "Not Found",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Projects",
+ "method": "GET",
+ "path": "/v1/api/projects/{projectId}/risks",
+ "opId": "getprojectrisks",
+ "desc": "Get a list of risks for a project",
+ "params": [
+ {
+ "name": "projectId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "reqBody": null,
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Risk"
+ }
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "403": {
+ "desc": "Forbidden",
+ "schema": null
+ },
+ "404": {
+ "desc": "Not Found",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Projects",
+ "method": "PUT",
+ "path": "/v1/api/projects/milestone",
+ "opId": "updatemilestone",
+ "desc": "Update project milestone details",
+ "params": [],
+ "reqBody": {
+ "required": true,
+ "contentType": "application/json; version=1",
+ "schema": {
+ "$ref": "#/components/schemas/UpdateMilestoneRequest"
+ }
+ },
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": null
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Projects",
+ "method": "PUT",
+ "path": "/v1/api/projects/status",
+ "opId": "updateprojectstatus",
+ "desc": "Update project status (M04)",
+ "params": [],
+ "reqBody": {
+ "required": true,
+ "contentType": "application/json; version=1",
+ "schema": {
+ "$ref": "#/components/schemas/UpdateProjectStatusRequest"
+ }
+ },
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": null
+ },
+ "400": {
+ "desc": "Bad Request",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Grants",
+ "method": "POST",
+ "path": "/v1/api/grants/awards",
+ "opId": "createaward",
+ "desc": "Register an approved funding award (M01)",
+ "params": [],
+ "reqBody": {
+ "required": true,
+ "contentType": "application/json; version=1",
+ "schema": {
+ "$ref": "#/components/schemas/CreateAwardRequest"
+ }
+ },
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": null
+ },
+ "400": {
+ "desc": "Bad Request",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "404": {
+ "desc": "Not Found",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Grants",
+ "method": "GET",
+ "path": "/v1/api/grants/awards",
+ "opId": "getawards",
+ "desc": "Get a list of awards",
+ "params": [
+ {
+ "name": "page",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int32",
+ "default": 1
+ }
+ },
+ {
+ "name": "pageSize",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int32",
+ "default": 10
+ }
+ }
+ ],
+ "reqBody": null,
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Award"
+ }
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Grants",
+ "method": "POST",
+ "path": "/v1/api/grants/budgets",
+ "opId": "createbudget",
+ "desc": "Create a new budget line (M05)",
+ "params": [],
+ "reqBody": {
+ "required": true,
+ "contentType": "application/json; version=1",
+ "schema": {
+ "$ref": "#/components/schemas/CreateBudgetRequest"
+ }
+ },
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": null
+ },
+ "400": {
+ "desc": "Bad Request",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "404": {
+ "desc": "Not Found",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Grants",
+ "method": "GET",
+ "path": "/v1/api/grants/budgets",
+ "opId": "getbudgets",
+ "desc": "Get all budgets",
+ "params": [
+ {
+ "name": "page",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int32",
+ "default": 1
+ }
+ },
+ {
+ "name": "pageSize",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int32",
+ "default": 100
+ }
+ }
+ ],
+ "reqBody": null,
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Budget"
+ }
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Grants",
+ "method": "POST",
+ "path": "/v1/api/grants/contracts",
+ "opId": "createcontract",
+ "desc": "Create a contract record from an approved award (M02)",
+ "params": [],
+ "reqBody": {
+ "required": true,
+ "contentType": "application/json; version=1",
+ "schema": {
+ "$ref": "#/components/schemas/CreateContractRequest"
+ }
+ },
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": null
+ },
+ "400": {
+ "desc": "Bad Request",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Grants",
+ "method": "GET",
+ "path": "/v1/api/grants/contracts",
+ "opId": "getcontracts",
+ "desc": "Get all contracts",
+ "params": [
+ {
+ "name": "page",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int32",
+ "default": 1
+ }
+ },
+ {
+ "name": "pageSize",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int32",
+ "default": 100
+ }
+ }
+ ],
+ "reqBody": null,
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Contract"
+ }
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Grants",
+ "method": "PUT",
+ "path": "/v1/api/grants/contracts",
+ "opId": "updatecontract",
+ "desc": "Updates a specific contract",
+ "params": [],
+ "reqBody": {
+ "required": true,
+ "contentType": "application/json; version=1",
+ "schema": {
+ "$ref": "#/components/schemas/UpdateContractRequest"
+ }
+ },
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": null
+ },
+ "400": {
+ "desc": "Bad Request",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "404": {
+ "desc": "Not Found",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Grants",
+ "method": "POST",
+ "path": "/v1/api/grants/contracts/execute",
+ "opId": "executecontract",
+ "desc": "Execute the contract and lock the contractual version",
+ "params": [],
+ "reqBody": {
+ "required": true,
+ "contentType": "application/json; version=1",
+ "schema": {
+ "$ref": "#/components/schemas/ExecuteContractRequest"
+ }
+ },
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": null
+ },
+ "400": {
+ "desc": "Bad Request",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Grants",
+ "method": "GET",
+ "path": "/v1/api/grants/awards/{awardId}",
+ "opId": "getaward",
+ "desc": "Get award by ID",
+ "params": [
+ {
+ "name": "awardId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "reqBody": null,
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": {
+ "$ref": "#/components/schemas/Award"
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "403": {
+ "desc": "Forbidden",
+ "schema": null
+ },
+ "404": {
+ "desc": "Not Found",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Grants",
+ "method": "GET",
+ "path": "/v1/api/grants/budgets/{budgetId}",
+ "opId": "getbudget",
+ "desc": "Get budget by ID",
+ "params": [
+ {
+ "name": "budgetId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "reqBody": null,
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": {
+ "$ref": "#/components/schemas/Budget"
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "403": {
+ "desc": "Forbidden",
+ "schema": null
+ },
+ "404": {
+ "desc": "Not Found",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Grants",
+ "method": "GET",
+ "path": "/v1/api/grants/contracts/{contractId}",
+ "opId": "getcontract",
+ "desc": "Get contract by ID",
+ "params": [
+ {
+ "name": "contractId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "reqBody": null,
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": {
+ "$ref": "#/components/schemas/Contract"
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "403": {
+ "desc": "Forbidden",
+ "schema": null
+ },
+ "404": {
+ "desc": "Not Found",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Grants",
+ "method": "POST",
+ "path": "/v1/api/grants/contracts/{contractId}",
+ "opId": "uploadcontract",
+ "desc": "Uploads a contract file securely",
+ "params": [
+ {
+ "name": "contractId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "reqBody": null,
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": null
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "403": {
+ "desc": "Forbidden",
+ "schema": null
+ },
+ "404": {
+ "desc": "Not Found",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Grants",
+ "method": "GET",
+ "path": "/v1/api/grants/contracts/{contractId}/securelink",
+ "opId": "getcontracturl",
+ "desc": "Gets a secure download URL, has expiry period (default 15min)",
+ "params": [
+ {
+ "name": "contractId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "reqBody": null,
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": {
+ "type": "string"
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "403": {
+ "desc": "Forbidden",
+ "schema": null
+ },
+ "404": {
+ "desc": "Not Found",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Governance",
+ "method": "POST",
+ "path": "/v1/api/governance/organisations",
+ "opId": "createorganisation",
+ "desc": "Create a new organization profile",
+ "params": [],
+ "reqBody": {
+ "required": true,
+ "contentType": "application/json; version=1",
+ "schema": {
+ "$ref": "#/components/schemas/CreateOrganisationRequest"
+ }
+ },
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": null
+ },
+ "400": {
+ "desc": "Bad Request",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Governance",
+ "method": "GET",
+ "path": "/v1/api/governance/organisations",
+ "opId": "getorganisations",
+ "desc": "Get a paginated directory of organizations",
+ "params": [
+ {
+ "name": "page",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int32",
+ "default": 1
+ }
+ },
+ {
+ "name": "pageSize",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int32",
+ "default": 100
+ }
+ }
+ ],
+ "reqBody": null,
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Organisation"
+ }
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Governance",
+ "method": "PUT",
+ "path": "/v1/api/governance/organisations",
+ "opId": "updateorganisation",
+ "desc": "Update an existing organization profile",
+ "params": [],
+ "reqBody": {
+ "required": true,
+ "contentType": "application/json; version=1",
+ "schema": {
+ "$ref": "#/components/schemas/UpdateOrganisationRequest"
+ }
+ },
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": null
+ },
+ "400": {
+ "desc": "Bad Request",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "404": {
+ "desc": "Not Found",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Governance",
+ "method": "POST",
+ "path": "/v1/api/governance/organisations/portfolio",
+ "opId": "createportfolio",
+ "desc": "Create a new organization portfolio",
+ "params": [],
+ "reqBody": {
+ "required": true,
+ "contentType": "application/json; version=1",
+ "schema": {
+ "$ref": "#/components/schemas/CreatePortfolioRequest"
+ }
+ },
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": null
+ },
+ "400": {
+ "desc": "Bad Request",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "403": {
+ "desc": "Forbidden",
+ "schema": null
+ },
+ "404": {
+ "desc": "Not Found",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Governance",
+ "method": "PUT",
+ "path": "/v1/api/governance/organisations/portfolio",
+ "opId": "updateportfolio",
+ "desc": "Update an existing organization portfolio",
+ "params": [],
+ "reqBody": {
+ "required": true,
+ "contentType": "application/json; version=1",
+ "schema": {
+ "$ref": "#/components/schemas/UpdatePortfolioRequest"
+ }
+ },
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": null
+ },
+ "400": {
+ "desc": "Bad Request",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "403": {
+ "desc": "Forbidden",
+ "schema": null
+ },
+ "404": {
+ "desc": "Not Found",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Governance",
+ "method": "GET",
+ "path": "/v1/api/governance/dashboard",
+ "opId": "dashboard",
+ "desc": "Get executive portfolio dashboard metrics and KPIs",
+ "params": [],
+ "reqBody": null,
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": {
+ "$ref": "#/components/schemas/Dashboard"
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Governance",
+ "method": "GET",
+ "path": "/v1/api/governance/instruments/{programmeId}",
+ "opId": "getinstruments",
+ "desc": "Get all funding instruments",
+ "params": [
+ {
+ "name": "programmeId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "reqBody": null,
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Instrument"
+ }
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "403": {
+ "desc": "Forbidden",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Governance",
+ "method": "GET",
+ "path": "/v1/api/governance/organisations/{organisationId}",
+ "opId": "getorganisation",
+ "desc": "Get organization profile by ID",
+ "params": [
+ {
+ "name": "organisationId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "reqBody": null,
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": {
+ "$ref": "#/components/schemas/Organisation"
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "403": {
+ "desc": "Forbidden",
+ "schema": null
+ },
+ "404": {
+ "desc": "Not Found",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Governance",
+ "method": "GET",
+ "path": "/v1/api/governance/portfolios/{portfolioId}",
+ "opId": "getportfolio",
+ "desc": "Get a specific portfolios",
+ "params": [
+ {
+ "name": "portfolioId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "reqBody": null,
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Portfolio"
+ }
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "403": {
+ "desc": "Forbidden",
+ "schema": null
+ },
+ "404": {
+ "desc": "Not Found",
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Portfolio"
+ }
+ }
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Governance",
+ "method": "GET",
+ "path": "/v1/api/governance/portfolios/{portfolioId}/programmes",
+ "opId": "getportfolioprogrammes",
+ "desc": "Get a programme's portfolios",
+ "params": [
+ {
+ "name": "portfolioId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "reqBody": null,
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Programme"
+ }
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "403": {
+ "desc": "Forbidden",
+ "schema": null
+ },
+ "404": {
+ "desc": "Not Found",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Governance",
+ "method": "GET",
+ "path": "/v1/api/governance/organisations/{organisationId}/portfolio",
+ "opId": "getportfolios",
+ "desc": "Get all portfolios",
+ "params": [
+ {
+ "name": "organisationId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "reqBody": null,
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Portfolio"
+ }
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "403": {
+ "desc": "Forbidden",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Governance",
+ "method": "GET",
+ "path": "/v1/api/governance/programmes/{portfolioId}",
+ "opId": "getprogrammes",
+ "desc": "Get all programmes",
+ "params": [
+ {
+ "name": "portfolioId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "reqBody": null,
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Programme"
+ }
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "403": {
+ "desc": "Forbidden",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Execution",
+ "method": "POST",
+ "path": "/v1/api/execution/disbursements/{disbursementId}/approve",
+ "opId": "approvedisbursement",
+ "desc": "Approve disbursement and release payment (M06)",
+ "params": [
+ {
+ "name": "disbursementId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "reqBody": null,
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": null
+ },
+ "400": {
+ "desc": "Bad Request",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "403": {
+ "desc": "Forbidden",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Execution",
+ "method": "POST",
+ "path": "/v1/api/execution/disbursements",
+ "opId": "createdisbursement",
+ "desc": "Create a new disbursement schedule (M06)",
+ "params": [],
+ "reqBody": {
+ "required": true,
+ "contentType": "application/json; version=1",
+ "schema": {
+ "$ref": "#/components/schemas/CreateDisbursementRequest"
+ }
+ },
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": null
+ },
+ "400": {
+ "desc": "Bad Request",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "404": {
+ "desc": "Not Found",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Execution",
+ "method": "GET",
+ "path": "/v1/api/execution/disbursements",
+ "opId": "getdisbursements",
+ "desc": "Get a list of disbursements",
+ "params": [
+ {
+ "name": "page",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int32",
+ "default": 1
+ }
+ },
+ {
+ "name": "pageSize",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int32",
+ "default": 10
+ }
+ }
+ ],
+ "reqBody": null,
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Disbursement"
+ }
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "404": {
+ "desc": "Not Found",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Execution",
+ "method": "PUT",
+ "path": "/v1/api/execution/disbursements",
+ "opId": "updatedisbursement",
+ "desc": "Update the details of a specific disbursement",
+ "params": [],
+ "reqBody": {
+ "required": true,
+ "contentType": "application/json; version=1",
+ "schema": {
+ "$ref": "#/components/schemas/UpdateDisbursementRequest"
+ }
+ },
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": null
+ },
+ "400": {
+ "desc": "Bad Request",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Execution",
+ "method": "POST",
+ "path": "/v1/api/execution/risks",
+ "opId": "createrisk",
+ "desc": "Add risk to risk register (M09)",
+ "params": [],
+ "reqBody": {
+ "required": true,
+ "contentType": "application/json; version=1",
+ "schema": {
+ "$ref": "#/components/schemas/CreateRiskRequest"
+ }
+ },
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": null
+ },
+ "400": {
+ "desc": "Bad Request",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "404": {
+ "desc": "Not Found",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Execution",
+ "method": "GET",
+ "path": "/v1/api/execution/risks",
+ "opId": "getrisks",
+ "desc": "Get a list of all risks",
+ "params": [
+ {
+ "name": "page",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int32",
+ "default": 1
+ }
+ },
+ {
+ "name": "pageSize",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int32",
+ "default": 100
+ }
+ }
+ ],
+ "reqBody": null,
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": null
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Execution",
+ "method": "PUT",
+ "path": "/v1/api/execution/risks",
+ "opId": "updaterisk",
+ "desc": "Update the details of a specific risk",
+ "params": [],
+ "reqBody": {
+ "required": true,
+ "contentType": "application/json; version=1",
+ "schema": {
+ "$ref": "#/components/schemas/UpdateRiskRequest"
+ }
+ },
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": null
+ },
+ "400": {
+ "desc": "Bad Request",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Execution",
+ "method": "GET",
+ "path": "/v1/api/execution/disbursements/{disbursementId}",
+ "opId": "getdisbursement",
+ "desc": "Get a specific disbursement",
+ "params": [
+ {
+ "name": "disbursementId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "reqBody": null,
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": {
+ "$ref": "#/components/schemas/Disbursement"
+ }
+ },
+ "400": {
+ "desc": "Bad Request",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "404": {
+ "desc": "Not Found",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Execution",
+ "method": "GET",
+ "path": "/v1/api/execution/risks/{riskId}",
+ "opId": "getrisk",
+ "desc": "Get a specific risk",
+ "params": [
+ {
+ "name": "riskId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "reqBody": null,
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": {
+ "$ref": "#/components/schemas/Risk"
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "404": {
+ "desc": "Not Found",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Evidence & M&E",
+ "method": "PUT",
+ "path": "/v1/api/evidence/file",
+ "opId": "createevidence",
+ "desc": "Upload supporting evidence file to S3 and register metadata (M14)",
+ "params": [],
+ "reqBody": null,
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": null
+ },
+ "400": {
+ "desc": "Bad Request",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Evidence & M&E",
+ "method": "POST",
+ "path": "/v1/api/evidence/indicators",
+ "opId": "createindicator",
+ "desc": "Define indicators and baselines/targets (M08)",
+ "params": [],
+ "reqBody": {
+ "required": true,
+ "contentType": "application/json; version=1",
+ "schema": {
+ "$ref": "#/components/schemas/CreateIndicatorRequest"
+ }
+ },
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": null
+ },
+ "400": {
+ "desc": "Bad Request",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "404": {
+ "desc": "Not Found",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Evidence & M&E",
+ "method": "POST",
+ "path": "/v1/api/evidence/milestones",
+ "opId": "createmilestonerequest",
+ "desc": "Define contractual milestones (M07)",
+ "params": [],
+ "reqBody": {
+ "required": true,
+ "contentType": "application/json; version=1",
+ "schema": {
+ "$ref": "#/components/schemas/CreateMilestoneRequest"
+ }
+ },
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": null
+ },
+ "400": {
+ "desc": "Bad Request",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Evidence & M&E",
+ "method": "GET",
+ "path": "/v1/api/evidence/{evidenceId}",
+ "opId": "getevidence",
+ "desc": "Get a specific evidence record and assiciated file URLs",
+ "params": [
+ {
+ "name": "evidenceId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "reqBody": null,
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": {
+ "$ref": "#/components/schemas/Evidence"
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "403": {
+ "desc": "Forbidden",
+ "schema": null
+ },
+ "404": {
+ "desc": "Not Found",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Evidence & M&E",
+ "method": "GET",
+ "path": "/v1/api/evidence/files/{page}/{pageSize}",
+ "opId": "getevidencefiles",
+ "desc": "Get a list of all uploaded evidence files metadata",
+ "params": [
+ {
+ "name": "page",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": "integer",
+ "format": "int32",
+ "default": 1
+ }
+ },
+ {
+ "name": "pageSize",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": "integer",
+ "format": "int32",
+ "default": 100
+ }
+ }
+ ],
+ "reqBody": null,
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Evidence"
+ }
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Grants",
+ "method": "GET",
+ "path": "/v1/api/evidence/{evidenceId}/securelink",
+ "opId": "getevidenceurl",
+ "desc": "Gets a secure download URL of the evidence file, URL expires (default: 15min)",
+ "params": [
+ {
+ "name": "evidenceId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "reqBody": null,
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": {
+ "type": "string"
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "403": {
+ "desc": "Forbidden",
+ "schema": null
+ },
+ "404": {
+ "desc": "Not Found",
+ "schema": {
+ "type": "string"
+ }
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Evidence & M&E",
+ "method": "GET",
+ "path": "/v1/api/evidence/indicators/{indicatorId}",
+ "opId": "getindicator",
+ "desc": "Get a specific indicator.",
+ "params": [
+ {
+ "name": "indicatorId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "reqBody": null,
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": {
+ "$ref": "#/components/schemas/Indicator"
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "403": {
+ "desc": "Forbidden",
+ "schema": null
+ },
+ "404": {
+ "desc": "Not Found",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Evidence & M&E",
+ "method": "GET",
+ "path": "/v1/api/evidence/indicators/{page}/{pageSize}",
+ "opId": "getindicators",
+ "desc": "Get a list of indicators",
+ "params": [
+ {
+ "name": "page",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": "integer",
+ "format": "int32",
+ "default": 1
+ }
+ },
+ {
+ "name": "pageSize",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": "integer",
+ "format": "int32",
+ "default": 100
+ }
+ }
+ ],
+ "reqBody": null,
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Indicator"
+ }
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Evidence & M&E",
+ "method": "GET",
+ "path": "/v1/api/evidence/milestone/{milestoneId}",
+ "opId": "getmilestoneevidence",
+ "desc": "Get a list of all uploaded evidence metadata for a specific mliestone",
+ "params": [
+ {
+ "name": "milestoneId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "reqBody": null,
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Evidence"
+ }
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "403": {
+ "desc": "Forbidden",
+ "schema": null
+ },
+ "404": {
+ "desc": "Not Found",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Evidence & M&E",
+ "method": "GET",
+ "path": "/v1/api/evidence/{projectId}",
+ "opId": "getprojectevidence",
+ "desc": "Get a list of all uploaded evidence metadata for a specific project",
+ "params": [
+ {
+ "name": "projectId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "reqBody": null,
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Evidence"
+ }
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "403": {
+ "desc": "Forbidden",
+ "schema": null
+ },
+ "404": {
+ "desc": "Not Found",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Evidence & M&E",
+ "method": "POST",
+ "path": "/v1/api/evidence/indicators/{indicatorId}/actuals/{actualAmount}",
+ "opId": "recordindicatoramount",
+ "desc": "Record actual values for indicators (M08)",
+ "params": [
+ {
+ "name": "indicatorId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "actualAmount",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
+ "type": "number",
+ "format": "double"
+ }
+ }
+ ],
+ "reqBody": null,
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": null
+ },
+ "400": {
+ "desc": "Bad Request",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "403": {
+ "desc": "Forbidden",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Evidence & M&E",
+ "method": "PUT",
+ "path": "/v1/api/evidence",
+ "opId": "updateevidence",
+ "desc": "Updates a specific evidence record metadata",
+ "params": [],
+ "reqBody": {
+ "required": true,
+ "contentType": "application/json; version=1",
+ "schema": {
+ "$ref": "#/components/schemas/UpdateEvidenceRequest"
+ }
+ },
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": null
+ },
+ "400": {
+ "desc": "Bad Request",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "403": {
+ "desc": "Forbidden",
+ "schema": null
+ },
+ "404": {
+ "desc": "Not Found",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Communications",
+ "method": "GET",
+ "path": "/v1/api/communications/invites/applicant/{applicantUserId}",
+ "opId": "getapplicantinvite",
+ "desc": "Gets an applicant's invite",
+ "params": [
+ {
+ "name": "applicantUserId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "format": "uuid"
+ }
+ }
+ ],
+ "reqBody": null,
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": {
+ "$ref": "#/components/schemas/Invite"
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "404": {
+ "desc": "Not Found",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Communications",
+ "method": "GET",
+ "path": "/v1/api/communications/invites/{applicantUserId}",
+ "opId": "getapplicantinvites",
+ "desc": "Gets an applicant's invite list",
+ "params": [
+ {
+ "name": "applicantUserId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "format": "uuid"
+ }
+ }
+ ],
+ "reqBody": null,
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Invite"
+ }
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "404": {
+ "desc": "Not Found",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Communications",
+ "method": "GET",
+ "path": "/v1/api/communications/invites/{page}/{pageSize}",
+ "opId": "getinvites",
+ "desc": "Get a list of sent invitations",
+ "params": [
+ {
+ "name": "page",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": "integer",
+ "format": "int32",
+ "default": 1
+ }
+ },
+ {
+ "name": "pageSize",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": "integer",
+ "format": "int32",
+ "default": 100
+ }
+ }
+ ],
+ "reqBody": null,
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Invite"
+ }
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ {
+ "tag": "Communications",
+ "method": "POST",
+ "path": "/v1/api/communications/invite",
+ "opId": "sendinvite",
+ "desc": "Trigger an invite to an organization or candidate (specifying NotificationPlatform)",
+ "params": [],
+ "reqBody": {
+ "required": true,
+ "contentType": "application/json; version=1",
+ "schema": {
+ "$ref": "#/components/schemas/InviteRequest"
+ }
+ },
+ "responses": {
+ "200": {
+ "desc": "OK",
+ "schema": {
+ "$ref": "#/components/schemas/Invite"
+ }
+ },
+ "400": {
+ "desc": "Bad Request",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ },
+ "401": {
+ "desc": "Unauthorized",
+ "schema": null
+ },
+ "500": {
+ "desc": "Internal Server Error",
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ }
+]
\ No newline at end of file
diff --git a/PostFundManagement.ConsoleUI/bun.lock b/PostFundManagement.ConsoleUI/bun.lock
index 98b5beb..da15f2a 100644
--- a/PostFundManagement.ConsoleUI/bun.lock
+++ b/PostFundManagement.ConsoleUI/bun.lock
@@ -28,6 +28,7 @@
"devDependencies": {
"@types/express": "^4.17.21",
"@types/node": "^22.14.0",
+ "@vitejs/plugin-basic-ssl": "^2.3.0",
"autoprefixer": "^10.4.21",
"esbuild": "^0.25.0",
"tsx": "^4.21.0",
@@ -282,6 +283,8 @@
"@types/serve-static": ["@types/serve-static@1.15.10", "", { "dependencies": { "@types/http-errors": "*", "@types/node": "*", "@types/send": "<1" } }, "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw=="],
+ "@vitejs/plugin-basic-ssl": ["@vitejs/plugin-basic-ssl@2.3.0", "", { "peerDependencies": { "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-bdyo8rB3NnQbikdMpHaML9Z1OZPBu6fFOBo+OtxsBlvMJtysWskmBcnbIDhUqgC8tcxNv/a+BcV5U+2nQMm1OQ=="],
+
"@vitejs/plugin-react": ["@vitejs/plugin-react@5.2.0", "", { "dependencies": { "@babel/core": "^7.29.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-rc.3", "@types/babel__core": "^7.20.5", "react-refresh": "^0.18.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw=="],
"accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="],
diff --git a/PostFundManagement.ConsoleUI/openapi.json b/PostFundManagement.ConsoleUI/openapi.json
new file mode 100644
index 0000000..423173e
--- /dev/null
+++ b/PostFundManagement.ConsoleUI/openapi.json
@@ -0,0 +1,6077 @@
+{
+ "openapi": "3.1.1",
+ "info": {
+ "title": "PostFundManagement.Api | v1",
+ "version": "1.0.0"
+ },
+ "servers": [
+ {
+ "url": "http://api.ifri.khongisa.co.za/"
+ }
+ ],
+ "paths": {
+ "/v1/api/users/{userId}": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "description": "Gets the metadata of a single user.",
+ "operationId": "getuser",
+ "parameters": [
+ {
+ "name": "userId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "format": "uuid"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ { }
+ ]
+ }
+ },
+ "/v1/api/users/{page}/{pageSize}": {
+ "post": {
+ "tags": [
+ "Users"
+ ],
+ "description": "Paginated, searchable directory of system users. Supports filtering by ActivityStatus, searching by Email, and sorting",
+ "operationId": "getusers",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": "integer",
+ "format": "int32",
+ "default": 1
+ }
+ },
+ {
+ "name": "pageSize",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": "integer",
+ "format": "int32",
+ "default": 100
+ }
+ }
+ ],
+ "requestBody": {
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/UserFilterRequest"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ { }
+ ]
+ }
+ },
+ "/v1/api/integration/sync": {
+ "post": {
+ "tags": [
+ "Users"
+ ],
+ "description": "Trigger integration and data synchronization in the background",
+ "operationId": "syncuser",
+ "responses": {
+ "200": {
+ "description": "OK"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ { }
+ ]
+ }
+ },
+ "/v1/api/users/{userId}/status/{status}": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "description": "Changes the Activity status of the user in the system",
+ "operationId": "updateuserstatus",
+ "parameters": [
+ {
+ "name": "userId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "format": "uuid"
+ }
+ },
+ {
+ "name": "status",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/ActivityStatus"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "400": {
+ "description": "Bad Request",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ { }
+ ]
+ }
+ },
+ "/v1/api/tools/hashid/{id64}": {
+ "get": {
+ "tags": [
+ "Tools"
+ ],
+ "description": "Hashes an int64 identifier",
+ "operationId": "encodeid",
+ "parameters": [
+ {
+ "name": "id64",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": "integer",
+ "format": "int64"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "400": {
+ "description": "Bad Request",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ { }
+ ]
+ }
+ },
+ "/v1/api/projects/activities": {
+ "post": {
+ "tags": [
+ "Projects"
+ ],
+ "description": "Add a new activity under project (M04)",
+ "operationId": "createactivity",
+ "requestBody": {
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/CreateActivityRequest"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "description": "OK"
+ },
+ "400": {
+ "description": "Bad Request",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized"
+ }
+ },
+ "security": [
+ { }
+ ]
+ }
+ },
+ "/v1/api/projects/programme": {
+ "post": {
+ "tags": [
+ "Projects"
+ ],
+ "description": "Create programme",
+ "operationId": "createprogramme",
+ "requestBody": {
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/CreateProgrammeRequest"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "description": "OK"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ { }
+ ]
+ },
+ "put": {
+ "tags": [
+ "Projects"
+ ],
+ "description": "Update existing programme",
+ "operationId": "updateprogramme",
+ "requestBody": {
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateProgrammeRequest"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "description": "OK"
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "400": {
+ "description": "Bad Request",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ { }
+ ]
+ }
+ },
+ "/v1/api/projects": {
+ "post": {
+ "tags": [
+ "Projects"
+ ],
+ "description": "Create a new project (M04)",
+ "operationId": "createproject",
+ "requestBody": {
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/CreateProjectRequest"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "description": "OK"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "400": {
+ "description": "Bad Request",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized"
+ }
+ },
+ "security": [
+ { }
+ ]
+ },
+ "get": {
+ "tags": [
+ "Projects"
+ ],
+ "description": "Get a list of projects",
+ "operationId": "getprojects",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "schema": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int32",
+ "default": 1
+ }
+ },
+ {
+ "name": "pageSize",
+ "in": "query",
+ "schema": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int32",
+ "default": 100
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Project"
+ }
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized"
+ }
+ },
+ "security": [
+ { }
+ ]
+ },
+ "put": {
+ "tags": [
+ "Projects"
+ ],
+ "description": "Update project details",
+ "operationId": "updateproject",
+ "requestBody": {
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateProjectRequest"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "description": "OK"
+ },
+ "400": {
+ "description": "Bad Request",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized"
+ }
+ },
+ "security": [
+ { }
+ ]
+ }
+ },
+ "/v1/api/projects/{projectId}/activities": {
+ "get": {
+ "tags": [
+ "Projects"
+ ],
+ "description": "Get a list of activities for a project",
+ "operationId": "getactivities",
+ "parameters": [
+ {
+ "name": "projectId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Activity"
+ }
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ { }
+ ]
+ }
+ },
+ "/v1/api/projects/activities/{activityId}": {
+ "get": {
+ "tags": [
+ "Projects"
+ ],
+ "description": "Gets a specific activity by it's id",
+ "operationId": "getactivity",
+ "parameters": [
+ {
+ "name": "activityId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/Activity"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ { }
+ ]
+ }
+ },
+ "/v1/api/projects/milestones/{milestoneId}": {
+ "get": {
+ "tags": [
+ "Projects"
+ ],
+ "description": "Gets a specific milestone",
+ "operationId": "getmilestone",
+ "parameters": [
+ {
+ "name": "milestoneId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/Milestone"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ { }
+ ]
+ }
+ },
+ "/v1/api/projects/milestones/{page}/{pageSize}": {
+ "get": {
+ "tags": [
+ "Projects"
+ ],
+ "description": "Get a list of all milestones",
+ "operationId": "getmilestones",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": "integer",
+ "format": "int32",
+ "default": 1
+ }
+ },
+ {
+ "name": "pageSize",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": "integer",
+ "format": "int32",
+ "default": 100
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Milestone"
+ }
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized"
+ }
+ },
+ "security": [
+ { }
+ ]
+ }
+ },
+ "/v1/api/projects/programme/{programmeId}": {
+ "get": {
+ "tags": [
+ "Projects"
+ ],
+ "description": "Get specific programme",
+ "operationId": "getprogramme",
+ "parameters": [
+ {
+ "name": "programmeId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/Programme"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ { }
+ ]
+ }
+ },
+ "/v1/api/projects/{programmeId}/programme": {
+ "get": {
+ "tags": [
+ "Projects"
+ ],
+ "description": "Get a list of all projects under a programme",
+ "operationId": "getprogrammeprojects",
+ "parameters": [
+ {
+ "name": "programmeId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Project"
+ }
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ { }
+ ]
+ }
+ },
+ "/v1/api/projects/programme/{page}/{pageSize}": {
+ "get": {
+ "tags": [
+ "Projects"
+ ],
+ "description": "Get a list of all programmes",
+ "operationId": "getprogrammesprojects",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": "integer",
+ "format": "int32",
+ "default": 1
+ }
+ },
+ {
+ "name": "pageSize",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": "integer",
+ "format": "int32",
+ "default": 100
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Programme"
+ }
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ { }
+ ]
+ }
+ },
+ "/v1/api/projects/{projectId}": {
+ "get": {
+ "tags": [
+ "Projects"
+ ],
+ "description": "Get project by ID",
+ "operationId": "getproject",
+ "parameters": [
+ {
+ "name": "projectId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Project"
+ }
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ { }
+ ]
+ }
+ },
+ "/v1/api/projects/indicators/project/{projectId}": {
+ "get": {
+ "tags": [
+ "Projects"
+ ],
+ "description": "Get get a detailed list of project indicators",
+ "operationId": "getprojectindicators",
+ "parameters": [
+ {
+ "name": "projectId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Indicator"
+ }
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ { }
+ ]
+ }
+ },
+ "/v1/api/projects/{projectId}/milestones": {
+ "get": {
+ "tags": [
+ "Projects"
+ ],
+ "description": "Get a list of milestones for a project",
+ "operationId": "getprojectmilestones",
+ "parameters": [
+ {
+ "name": "projectId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Milestone"
+ }
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ { }
+ ]
+ }
+ },
+ "/v1/api/projects/{projectId}/risks": {
+ "get": {
+ "tags": [
+ "Projects"
+ ],
+ "description": "Get a list of risks for a project",
+ "operationId": "getprojectrisks",
+ "parameters": [
+ {
+ "name": "projectId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Risk"
+ }
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ { }
+ ]
+ }
+ },
+ "/v1/api/projects/milestone": {
+ "put": {
+ "tags": [
+ "Projects"
+ ],
+ "description": "Update project milestone details",
+ "operationId": "updatemilestone",
+ "requestBody": {
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateMilestoneRequest"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "description": "OK"
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized"
+ }
+ },
+ "security": [
+ { }
+ ]
+ }
+ },
+ "/v1/api/projects/status": {
+ "put": {
+ "tags": [
+ "Projects"
+ ],
+ "description": "Update project status (M04)",
+ "operationId": "updateprojectstatus",
+ "requestBody": {
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateProjectStatusRequest"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "description": "OK"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "400": {
+ "description": "Bad Request",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ { }
+ ]
+ }
+ },
+ "/v1/api/grants/awards": {
+ "post": {
+ "tags": [
+ "Grants"
+ ],
+ "description": "Register an approved funding award (M01)",
+ "operationId": "createaward",
+ "requestBody": {
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/CreateAwardRequest"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "description": "OK"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "400": {
+ "description": "Bad Request",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ { }
+ ]
+ },
+ "get": {
+ "tags": [
+ "Grants"
+ ],
+ "description": "Get a list of awards",
+ "operationId": "getawards",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "schema": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int32",
+ "default": 1
+ }
+ },
+ {
+ "name": "pageSize",
+ "in": "query",
+ "schema": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int32",
+ "default": 10
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Award"
+ }
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized"
+ }
+ },
+ "security": [
+ { }
+ ]
+ }
+ },
+ "/v1/api/grants/budgets": {
+ "post": {
+ "tags": [
+ "Grants"
+ ],
+ "description": "Create a new budget line (M05)",
+ "operationId": "createbudget",
+ "requestBody": {
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/CreateBudgetRequest"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "description": "OK"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "400": {
+ "description": "Bad Request",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized"
+ }
+ },
+ "security": [
+ { }
+ ]
+ },
+ "get": {
+ "tags": [
+ "Grants"
+ ],
+ "description": "Get all budgets",
+ "operationId": "getbudgets",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "schema": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int32",
+ "default": 1
+ }
+ },
+ {
+ "name": "pageSize",
+ "in": "query",
+ "schema": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int32",
+ "default": 100
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Budget"
+ }
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized"
+ }
+ },
+ "security": [
+ { }
+ ]
+ }
+ },
+ "/v1/api/grants/contracts": {
+ "post": {
+ "tags": [
+ "Grants"
+ ],
+ "description": "Create a contract record from an approved award (M02)",
+ "operationId": "createcontract",
+ "requestBody": {
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/CreateContractRequest"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "description": "OK"
+ },
+ "400": {
+ "description": "Bad Request",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized"
+ }
+ },
+ "security": [
+ { }
+ ]
+ },
+ "get": {
+ "tags": [
+ "Grants"
+ ],
+ "description": "Get all contracts",
+ "operationId": "getcontracts",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "schema": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int32",
+ "default": 1
+ }
+ },
+ {
+ "name": "pageSize",
+ "in": "query",
+ "schema": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int32",
+ "default": 100
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Contract"
+ }
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized"
+ }
+ },
+ "security": [
+ { }
+ ]
+ },
+ "put": {
+ "tags": [
+ "Grants"
+ ],
+ "description": "Updates a specific contract",
+ "operationId": "updatecontract",
+ "requestBody": {
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateContractRequest"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "description": "OK"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "400": {
+ "description": "Bad Request",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ { }
+ ]
+ }
+ },
+ "/v1/api/grants/contracts/execute": {
+ "post": {
+ "tags": [
+ "Grants"
+ ],
+ "description": "Execute the contract and lock the contractual version",
+ "operationId": "executecontract",
+ "requestBody": {
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ExecuteContractRequest"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "description": "OK"
+ },
+ "400": {
+ "description": "Bad Request",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized"
+ }
+ },
+ "security": [
+ { }
+ ]
+ }
+ },
+ "/v1/api/grants/awards/{awardId}": {
+ "get": {
+ "tags": [
+ "Grants"
+ ],
+ "description": "Get award by ID",
+ "operationId": "getaward",
+ "parameters": [
+ {
+ "name": "awardId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/Award"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ { }
+ ]
+ }
+ },
+ "/v1/api/grants/budgets/{budgetId}": {
+ "get": {
+ "tags": [
+ "Grants"
+ ],
+ "description": "Get budget by ID",
+ "operationId": "getbudget",
+ "parameters": [
+ {
+ "name": "budgetId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/Budget"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ { }
+ ]
+ }
+ },
+ "/v1/api/grants/contracts/{contractId}": {
+ "get": {
+ "tags": [
+ "Grants"
+ ],
+ "description": "Get contract by ID",
+ "operationId": "getcontract",
+ "parameters": [
+ {
+ "name": "contractId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/Contract"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ { }
+ ]
+ },
+ "post": {
+ "tags": [
+ "Grants"
+ ],
+ "description": "Uploads a contract file securely",
+ "operationId": "uploadcontract",
+ "parameters": [
+ {
+ "name": "contractId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK"
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ { }
+ ]
+ }
+ },
+ "/v1/api/grants/contracts/{contractId}/securelink": {
+ "get": {
+ "tags": [
+ "Grants"
+ ],
+ "description": "Gets a secure download URL, has expiry period (default 15min)",
+ "operationId": "getcontracturl",
+ "parameters": [
+ {
+ "name": "contractId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ { }
+ ]
+ }
+ },
+ "/v1/api/governance/organisations": {
+ "post": {
+ "tags": [
+ "Governance"
+ ],
+ "description": "Create a new organization profile",
+ "operationId": "createorganisation",
+ "requestBody": {
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/CreateOrganisationRequest"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "description": "OK"
+ },
+ "400": {
+ "description": "Bad Request",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized"
+ }
+ },
+ "security": [
+ { }
+ ]
+ },
+ "get": {
+ "tags": [
+ "Governance"
+ ],
+ "description": "Get a paginated directory of organizations",
+ "operationId": "getorganisations",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "schema": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int32",
+ "default": 1
+ }
+ },
+ {
+ "name": "pageSize",
+ "in": "query",
+ "schema": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int32",
+ "default": 100
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Organisation"
+ }
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized"
+ }
+ },
+ "security": [
+ { }
+ ]
+ },
+ "put": {
+ "tags": [
+ "Governance"
+ ],
+ "description": "Update an existing organization profile",
+ "operationId": "updateorganisation",
+ "requestBody": {
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateOrganisationRequest"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "description": "OK"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "400": {
+ "description": "Bad Request",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized"
+ }
+ },
+ "security": [
+ { }
+ ]
+ }
+ },
+ "/v1/api/governance/organisations/portfolio": {
+ "post": {
+ "tags": [
+ "Governance"
+ ],
+ "description": "Create a new organization portfolio",
+ "operationId": "createportfolio",
+ "requestBody": {
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/CreatePortfolioRequest"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "description": "OK"
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "404": {
+ "description": "Not Found",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized"
+ }
+ },
+ "security": [
+ { }
+ ]
+ },
+ "put": {
+ "tags": [
+ "Governance"
+ ],
+ "description": "Update an existing organization portfolio",
+ "operationId": "updateportfolio",
+ "requestBody": {
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdatePortfolioRequest"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "description": "OK"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "400": {
+ "description": "Bad Request",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ { }
+ ]
+ }
+ },
+ "/v1/api/governance/dashboard": {
+ "get": {
+ "tags": [
+ "Governance"
+ ],
+ "description": "Get executive portfolio dashboard metrics and KPIs",
+ "operationId": "dashboard",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/Dashboard"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ { }
+ ]
+ }
+ },
+ "/v1/api/governance/instruments/{programmeId}": {
+ "get": {
+ "tags": [
+ "Governance"
+ ],
+ "description": "Get all funding instruments",
+ "operationId": "getinstruments",
+ "parameters": [
+ {
+ "name": "programmeId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Instrument"
+ }
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ { }
+ ]
+ }
+ },
+ "/v1/api/governance/organisations/{organisationId}": {
+ "get": {
+ "tags": [
+ "Governance"
+ ],
+ "description": "Get organization profile by ID",
+ "operationId": "getorganisation",
+ "parameters": [
+ {
+ "name": "organisationId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/Organisation"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ { }
+ ]
+ }
+ },
+ "/v1/api/governance/portfolios/{portfolioId}": {
+ "get": {
+ "tags": [
+ "Governance"
+ ],
+ "description": "Get a specific portfolios",
+ "operationId": "getportfolio",
+ "parameters": [
+ {
+ "name": "portfolioId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Portfolio"
+ }
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Portfolio"
+ }
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ { }
+ ]
+ }
+ },
+ "/v1/api/governance/portfolios/{portfolioId}/programmes": {
+ "get": {
+ "tags": [
+ "Governance"
+ ],
+ "description": "Get a programme's portfolios",
+ "operationId": "getportfolioprogrammes",
+ "parameters": [
+ {
+ "name": "portfolioId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Programme"
+ }
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ { }
+ ]
+ }
+ },
+ "/v1/api/governance/organisations/{organisationId}/portfolio": {
+ "get": {
+ "tags": [
+ "Governance"
+ ],
+ "description": "Get all portfolios",
+ "operationId": "getportfolios",
+ "parameters": [
+ {
+ "name": "organisationId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Portfolio"
+ }
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ { }
+ ]
+ }
+ },
+ "/v1/api/governance/programmes/{portfolioId}": {
+ "get": {
+ "tags": [
+ "Governance"
+ ],
+ "description": "Get all programmes",
+ "operationId": "getprogrammes",
+ "parameters": [
+ {
+ "name": "portfolioId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Programme"
+ }
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ { }
+ ]
+ }
+ },
+ "/v1/api/execution/disbursements/{disbursementId}/approve": {
+ "post": {
+ "tags": [
+ "Execution"
+ ],
+ "description": "Approve disbursement and release payment (M06)",
+ "operationId": "approvedisbursement",
+ "parameters": [
+ {
+ "name": "disbursementId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "400": {
+ "description": "Bad Request",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ { }
+ ]
+ }
+ },
+ "/v1/api/execution/disbursements": {
+ "post": {
+ "tags": [
+ "Execution"
+ ],
+ "description": "Create a new disbursement schedule (M06)",
+ "operationId": "createdisbursement",
+ "requestBody": {
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/CreateDisbursementRequest"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "description": "OK"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "400": {
+ "description": "Bad Request",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ { }
+ ]
+ },
+ "get": {
+ "tags": [
+ "Execution"
+ ],
+ "description": "Get a list of disbursements",
+ "operationId": "getdisbursements",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "schema": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int32",
+ "default": 1
+ }
+ },
+ {
+ "name": "pageSize",
+ "in": "query",
+ "schema": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int32",
+ "default": 10
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Disbursement"
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ { }
+ ]
+ },
+ "put": {
+ "tags": [
+ "Execution"
+ ],
+ "description": "Update the details of a specific disbursement",
+ "operationId": "updatedisbursement",
+ "requestBody": {
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateDisbursementRequest"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "description": "OK"
+ },
+ "400": {
+ "description": "Bad Request",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized"
+ }
+ },
+ "security": [
+ { }
+ ]
+ }
+ },
+ "/v1/api/execution/risks": {
+ "post": {
+ "tags": [
+ "Execution"
+ ],
+ "description": "Add risk to risk register (M09)",
+ "operationId": "createrisk",
+ "requestBody": {
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/CreateRiskRequest"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "description": "OK"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "400": {
+ "description": "Bad Request",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ { }
+ ]
+ },
+ "get": {
+ "tags": [
+ "Execution"
+ ],
+ "description": "Get a list of all risks",
+ "operationId": "getrisks",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "query",
+ "schema": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int32",
+ "default": 1
+ }
+ },
+ {
+ "name": "pageSize",
+ "in": "query",
+ "schema": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int32",
+ "default": 100
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ { }
+ ]
+ },
+ "put": {
+ "tags": [
+ "Execution"
+ ],
+ "description": "Update the details of a specific risk",
+ "operationId": "updaterisk",
+ "requestBody": {
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateRiskRequest"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "description": "OK"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "400": {
+ "description": "Bad Request",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ { }
+ ]
+ }
+ },
+ "/v1/api/execution/disbursements/{disbursementId}": {
+ "get": {
+ "tags": [
+ "Execution"
+ ],
+ "description": "Get a specific disbursement",
+ "operationId": "getdisbursement",
+ "parameters": [
+ {
+ "name": "disbursementId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/Disbursement"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "400": {
+ "description": "Bad Request",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ { }
+ ]
+ }
+ },
+ "/v1/api/execution/risks/{riskId}": {
+ "get": {
+ "tags": [
+ "Execution"
+ ],
+ "description": "Get a specific risk",
+ "operationId": "getrisk",
+ "parameters": [
+ {
+ "name": "riskId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/Risk"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ { }
+ ]
+ }
+ },
+ "/v1/api/evidence/file": {
+ "put": {
+ "tags": [
+ "Evidence & M&E"
+ ],
+ "description": "Upload supporting evidence file to S3 and register metadata (M14)",
+ "operationId": "createevidence",
+ "responses": {
+ "200": {
+ "description": "OK"
+ },
+ "400": {
+ "description": "Bad Request",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized"
+ }
+ },
+ "security": [
+ { }
+ ]
+ }
+ },
+ "/v1/api/evidence/indicators": {
+ "post": {
+ "tags": [
+ "Evidence & M&E"
+ ],
+ "description": "Define indicators and baselines/targets (M08)",
+ "operationId": "createindicator",
+ "requestBody": {
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/CreateIndicatorRequest"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "description": "OK"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "400": {
+ "description": "Bad Request",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ { }
+ ]
+ }
+ },
+ "/v1/api/evidence/milestones": {
+ "post": {
+ "tags": [
+ "Evidence & M&E"
+ ],
+ "description": "Define contractual milestones (M07)",
+ "operationId": "createmilestonerequest",
+ "requestBody": {
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/CreateMilestoneRequest"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "description": "OK"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "400": {
+ "description": "Bad Request",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ { }
+ ]
+ }
+ },
+ "/v1/api/evidence/{evidenceId}": {
+ "get": {
+ "tags": [
+ "Evidence & M&E"
+ ],
+ "description": "Get a specific evidence record and assiciated file URLs",
+ "operationId": "getevidence",
+ "parameters": [
+ {
+ "name": "evidenceId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/Evidence"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ { }
+ ]
+ }
+ },
+ "/v1/api/evidence/files/{page}/{pageSize}": {
+ "get": {
+ "tags": [
+ "Evidence & M&E"
+ ],
+ "description": "Get a list of all uploaded evidence files metadata",
+ "operationId": "getevidencefiles",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": "integer",
+ "format": "int32",
+ "default": 1
+ }
+ },
+ {
+ "name": "pageSize",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": "integer",
+ "format": "int32",
+ "default": 100
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Evidence"
+ }
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized"
+ }
+ },
+ "security": [
+ { }
+ ]
+ }
+ },
+ "/v1/api/evidence/{evidenceId}/securelink": {
+ "get": {
+ "tags": [
+ "Grants"
+ ],
+ "description": "Gets a secure download URL of the evidence file, URL expires (default: 15min)",
+ "operationId": "getevidenceurl",
+ "parameters": [
+ {
+ "name": "evidenceId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ { }
+ ]
+ }
+ },
+ "/v1/api/evidence/indicators/{indicatorId}": {
+ "get": {
+ "tags": [
+ "Evidence & M&E"
+ ],
+ "description": "Get a specific indicator.",
+ "operationId": "getindicator",
+ "parameters": [
+ {
+ "name": "indicatorId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/Indicator"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ { }
+ ]
+ }
+ },
+ "/v1/api/evidence/indicators/{page}/{pageSize}": {
+ "get": {
+ "tags": [
+ "Evidence & M&E"
+ ],
+ "description": "Get a list of indicators",
+ "operationId": "getindicators",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": "integer",
+ "format": "int32",
+ "default": 1
+ }
+ },
+ {
+ "name": "pageSize",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": "integer",
+ "format": "int32",
+ "default": 100
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Indicator"
+ }
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized"
+ }
+ },
+ "security": [
+ { }
+ ]
+ }
+ },
+ "/v1/api/evidence/milestone/{milestoneId}": {
+ "get": {
+ "tags": [
+ "Evidence & M&E"
+ ],
+ "description": "Get a list of all uploaded evidence metadata for a specific mliestone",
+ "operationId": "getmilestoneevidence",
+ "parameters": [
+ {
+ "name": "milestoneId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Evidence"
+ }
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ { }
+ ]
+ }
+ },
+ "/v1/api/evidence/{projectId}": {
+ "get": {
+ "tags": [
+ "Evidence & M&E"
+ ],
+ "description": "Get a list of all uploaded evidence metadata for a specific project",
+ "operationId": "getprojectevidence",
+ "parameters": [
+ {
+ "name": "projectId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Evidence"
+ }
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ { }
+ ]
+ }
+ },
+ "/v1/api/evidence/indicators/{indicatorId}/actuals/{actualAmount}": {
+ "post": {
+ "tags": [
+ "Evidence & M&E"
+ ],
+ "description": "Record actual values for indicators (M08)",
+ "operationId": "recordindicatoramount",
+ "parameters": [
+ {
+ "name": "indicatorId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "actualAmount",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
+ "type": "number",
+ "format": "double"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "400": {
+ "description": "Bad Request",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ { }
+ ]
+ }
+ },
+ "/v1/api/evidence": {
+ "put": {
+ "tags": [
+ "Evidence & M&E"
+ ],
+ "description": "Updates a specific evidence record metadata",
+ "operationId": "updateevidence",
+ "requestBody": {
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateEvidenceRequest"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "description": "OK"
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "400": {
+ "description": "Bad Request",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ { }
+ ]
+ }
+ },
+ "/v1/api/communications/invites/applicant/{applicantUserId}": {
+ "get": {
+ "tags": [
+ "Communications"
+ ],
+ "description": "Gets an applicant's invite",
+ "operationId": "getapplicantinvite",
+ "parameters": [
+ {
+ "name": "applicantUserId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "format": "uuid"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/Invite"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ { }
+ ]
+ }
+ },
+ "/v1/api/communications/invites/{applicantUserId}": {
+ "get": {
+ "tags": [
+ "Communications"
+ ],
+ "description": "Gets an applicant's invite list",
+ "operationId": "getapplicantinvites",
+ "parameters": [
+ {
+ "name": "applicantUserId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "format": "uuid"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Invite"
+ }
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ { }
+ ]
+ }
+ },
+ "/v1/api/communications/invites/{page}/{pageSize}": {
+ "get": {
+ "tags": [
+ "Communications"
+ ],
+ "description": "Get a list of sent invitations",
+ "operationId": "getinvites",
+ "parameters": [
+ {
+ "name": "page",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": "integer",
+ "format": "int32",
+ "default": 1
+ }
+ },
+ {
+ "name": "pageSize",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": "integer",
+ "format": "int32",
+ "default": 100
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Invite"
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ { }
+ ]
+ }
+ },
+ "/v1/api/communications/invite": {
+ "post": {
+ "tags": [
+ "Communications"
+ ],
+ "description": "Trigger an invite to an organization or candidate (specifying NotificationPlatform)",
+ "operationId": "sendinvite",
+ "requestBody": {
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/InviteRequest"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/Invite"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json; version=1": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized"
+ }
+ },
+ "security": [
+ { }
+ ]
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "Activity": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "projectId": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "null",
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "milestoneId": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "null",
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "createdBy": {
+ "type": "string",
+ "format": "uuid"
+ },
+ "updatedBy": {
+ "type": [
+ "null",
+ "string"
+ ],
+ "format": "uuid"
+ },
+ "approvedBy": {
+ "type": [
+ "null",
+ "string"
+ ],
+ "format": "uuid"
+ },
+ "createdAt": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "updatedAt": {
+ "type": [
+ "null",
+ "string"
+ ],
+ "format": "date-time"
+ },
+ "category": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "status": {
+ "$ref": "#/components/schemas/ApprovalStatus"
+ },
+ "amount": {
+ "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
+ "type": [
+ "number",
+ "string"
+ ],
+ "format": "double"
+ },
+ "title": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "description": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "performedAt": {
+ "type": [
+ "null",
+ "string"
+ ],
+ "format": "date-time"
+ }
+ }
+ },
+ "ActivityStatus": {
+ "type": "integer"
+ },
+ "ApprovalStatus": {
+ "type": "integer"
+ },
+ "Award": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "organisationId": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "programmeId": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "projectId": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "null",
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "createdBy": {
+ "type": "string",
+ "format": "uuid"
+ },
+ "updatedBy": {
+ "type": [
+ "null",
+ "string"
+ ],
+ "format": "uuid"
+ },
+ "approvedBy": {
+ "type": [
+ "null",
+ "string"
+ ],
+ "format": "uuid"
+ },
+ "createdAt": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "updatedAt": {
+ "type": [
+ "null",
+ "string"
+ ],
+ "format": "date-time"
+ },
+ "status": {
+ "$ref": "#/components/schemas/ApprovalStatus"
+ },
+ "amount": {
+ "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
+ "type": [
+ "number",
+ "string"
+ ],
+ "format": "double"
+ }
+ }
+ },
+ "Budget": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "projectId": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "createdBy": {
+ "type": "string",
+ "format": "uuid"
+ },
+ "approvedBy": {
+ "type": [
+ "null",
+ "string"
+ ],
+ "format": "uuid"
+ },
+ "createdAt": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "updatedAt": {
+ "type": [
+ "null",
+ "string"
+ ],
+ "format": "date-time"
+ },
+ "name": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "amount": {
+ "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
+ "type": [
+ "number",
+ "string"
+ ],
+ "format": "double"
+ }
+ }
+ },
+ "Contract": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "awardId": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "externalSignatureId": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "signedDocumentUrl": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "effectiveAt": {
+ "type": [
+ "null",
+ "string"
+ ],
+ "format": "date-time"
+ },
+ "expiresAt": {
+ "type": [
+ "null",
+ "string"
+ ],
+ "format": "date-time"
+ },
+ "totalValue": {
+ "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
+ "type": [
+ "number",
+ "string"
+ ],
+ "format": "double"
+ },
+ "status": {
+ "$ref": "#/components/schemas/ContractStatus"
+ },
+ "createdBy": {
+ "type": "string",
+ "format": "uuid"
+ },
+ "createdAt": {
+ "type": "string",
+ "format": "date-time"
+ }
+ }
+ },
+ "ContractStatus": {
+ "type": "integer"
+ },
+ "CreateActivityRequest": {
+ "required": [
+ "projectId",
+ "milestoneId",
+ "category",
+ "amount",
+ "title",
+ "description",
+ "performedAt"
+ ],
+ "type": "object",
+ "properties": {
+ "projectId": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "milestoneId": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "null",
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "category": {
+ "type": "string"
+ },
+ "amount": {
+ "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
+ "type": [
+ "number",
+ "string"
+ ],
+ "format": "double"
+ },
+ "title": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "performedAt": {
+ "type": [
+ "null",
+ "string"
+ ],
+ "format": "date-time"
+ }
+ }
+ },
+ "CreateAwardRequest": {
+ "required": [
+ "organisationId",
+ "programmeId",
+ "amount"
+ ],
+ "type": "object",
+ "properties": {
+ "organisationId": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "programmeId": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "amount": {
+ "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
+ "type": [
+ "number",
+ "string"
+ ],
+ "format": "double"
+ }
+ }
+ },
+ "CreateBudgetRequest": {
+ "required": [
+ "projectId",
+ "name",
+ "amount"
+ ],
+ "type": "object",
+ "properties": {
+ "projectId": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "name": {
+ "type": "string"
+ },
+ "amount": {
+ "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
+ "type": [
+ "number",
+ "string"
+ ],
+ "format": "double"
+ }
+ }
+ },
+ "CreateContractRequest": {
+ "required": [
+ "awardId",
+ "totalValue",
+ "effectiveAt",
+ "expiresAt"
+ ],
+ "type": "object",
+ "properties": {
+ "awardId": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "totalValue": {
+ "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
+ "type": [
+ "number",
+ "string"
+ ],
+ "format": "double"
+ },
+ "effectiveAt": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "expiresAt": {
+ "type": "string",
+ "format": "date-time"
+ }
+ }
+ },
+ "CreateDisbursementRequest": {
+ "required": [
+ "projectId",
+ "milestoneId",
+ "amount"
+ ],
+ "type": "object",
+ "properties": {
+ "projectId": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "milestoneId": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "null",
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "amount": {
+ "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
+ "type": [
+ "number",
+ "string"
+ ],
+ "format": "double"
+ }
+ }
+ },
+ "CreateIndicatorRequest": {
+ "required": [
+ "projectId",
+ "name",
+ "unitOfMeasure",
+ "baselineAmount",
+ "targetAmount"
+ ],
+ "type": "object",
+ "properties": {
+ "projectId": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "name": {
+ "type": "string"
+ },
+ "unitOfMeasure": {
+ "$ref": "#/components/schemas/UnitOfMeasure"
+ },
+ "baselineAmount": {
+ "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
+ "type": [
+ "number",
+ "string"
+ ],
+ "format": "double"
+ },
+ "targetAmount": {
+ "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
+ "type": [
+ "number",
+ "string"
+ ],
+ "format": "double"
+ }
+ }
+ },
+ "CreateMilestoneRequest": {
+ "required": [
+ "projectId",
+ "dueAt",
+ "name",
+ "priority"
+ ],
+ "type": "object",
+ "properties": {
+ "projectId": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "dueAt": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "name": {
+ "type": "string"
+ },
+ "priority": {
+ "$ref": "#/components/schemas/Priority"
+ }
+ }
+ },
+ "CreateOrganisationRequest": {
+ "required": [
+ "name",
+ "registrationNo",
+ "email"
+ ],
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "registrationNo": {
+ "type": "string"
+ },
+ "email": {
+ "type": "string"
+ },
+ "type": {
+ "$ref": "#/components/schemas/OrganisationType"
+ }
+ }
+ },
+ "CreatePortfolioRequest": {
+ "required": [
+ "name",
+ "description",
+ "organisationId",
+ "ownerId"
+ ],
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "organisationId": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "ownerId": {
+ "type": "string",
+ "format": "uuid"
+ }
+ }
+ },
+ "CreateProgrammeRequest": {
+ "required": [
+ "portfolioId",
+ "name",
+ "ownerId"
+ ],
+ "type": "object",
+ "properties": {
+ "portfolioId": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "name": {
+ "type": "string"
+ },
+ "ownerId": {
+ "type": "string",
+ "format": "uuid"
+ }
+ }
+ },
+ "CreateProjectRequest": {
+ "required": [
+ "programmeId",
+ "name",
+ "description",
+ "sector",
+ "projectType"
+ ],
+ "type": "object",
+ "properties": {
+ "programmeId": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "name": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "sector": {
+ "$ref": "#/components/schemas/IndustrySector"
+ },
+ "projectType": {
+ "$ref": "#/components/schemas/ProjectType"
+ }
+ }
+ },
+ "CreateRiskRequest": {
+ "required": [
+ "projectId",
+ "likelihood",
+ "impact",
+ "name",
+ "description"
+ ],
+ "type": "object",
+ "properties": {
+ "projectId": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "likelihood": {
+ "$ref": "#/components/schemas/RiskLikelihood"
+ },
+ "impact": {
+ "$ref": "#/components/schemas/RiskImpact"
+ },
+ "name": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ }
+ }
+ },
+ "Dashboard": {
+ "required": [
+ "portfolioValue",
+ "activeAwards",
+ "disbursed",
+ "onTrackPercentage",
+ "atRiskCount",
+ "beneficiariesReached"
+ ],
+ "type": "object",
+ "properties": {
+ "portfolioValue": {
+ "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
+ "type": [
+ "number",
+ "string"
+ ],
+ "format": "double"
+ },
+ "activeAwards": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int32"
+ },
+ "disbursed": {
+ "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
+ "type": [
+ "number",
+ "string"
+ ],
+ "format": "double"
+ },
+ "onTrackPercentage": {
+ "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?$",
+ "type": [
+ "number",
+ "string"
+ ],
+ "format": "double"
+ },
+ "atRiskCount": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int32"
+ },
+ "beneficiariesReached": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int32"
+ }
+ }
+ },
+ "Disbursement": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "projectId": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "milestoneId": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "null",
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "createdBy": {
+ "type": "string",
+ "format": "uuid"
+ },
+ "updatedBy": {
+ "type": [
+ "null",
+ "string"
+ ],
+ "format": "uuid"
+ },
+ "createdAt": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "updatedAt": {
+ "type": [
+ "null",
+ "string"
+ ],
+ "format": "date-time"
+ },
+ "amount": {
+ "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
+ "type": [
+ "number",
+ "string"
+ ],
+ "format": "double"
+ },
+ "status": {
+ "$ref": "#/components/schemas/ApprovalStatus"
+ }
+ }
+ },
+ "Evidence": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "projectId": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "milestoneId": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "indicatorId": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "activityId": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "createdBy": {
+ "type": "string",
+ "format": "uuid"
+ },
+ "updatedBy": {
+ "type": [
+ "null",
+ "string"
+ ],
+ "format": "uuid"
+ },
+ "createdAt": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "updatedAt": {
+ "type": [
+ "null",
+ "string"
+ ],
+ "format": "date-time"
+ },
+ "version": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int32"
+ },
+ "type": {
+ "$ref": "#/components/schemas/EvidenceType"
+ },
+ "documentUrl": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "status": {
+ "$ref": "#/components/schemas/ApprovalStatus"
+ }
+ }
+ },
+ "EvidenceType": {
+ "type": "integer"
+ },
+ "ExecuteContractRequest": {
+ "required": [
+ "contractId",
+ "externalSignatureId",
+ "signedDocumentUrl"
+ ],
+ "type": "object",
+ "properties": {
+ "contractId": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "externalSignatureId": {
+ "type": "string"
+ },
+ "signedDocumentUrl": {
+ "type": "string"
+ }
+ }
+ },
+ "Indicator": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "projectId": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "createdBy": {
+ "type": "string",
+ "format": "uuid"
+ },
+ "createdAt": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "name": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "unitOfMeasure": {
+ "oneOf": [
+ {
+ "type": "null"
+ },
+ {
+ "$ref": "#/components/schemas/UnitOfMeasure"
+ }
+ ]
+ },
+ "baselineAmount": {
+ "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
+ "type": [
+ "number",
+ "string"
+ ],
+ "format": "double"
+ },
+ "targetAmount": {
+ "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
+ "type": [
+ "number",
+ "string"
+ ],
+ "format": "double"
+ },
+ "actualAmount": {
+ "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
+ "type": [
+ "number",
+ "string"
+ ],
+ "format": "double"
+ }
+ }
+ },
+ "IndustrySector": {
+ "type": "integer"
+ },
+ "Instrument": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "programmeId": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "code": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "name": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "description": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "status": {
+ "$ref": "#/components/schemas/ActivityStatus"
+ },
+ "createdBy": {
+ "type": "string",
+ "format": "uuid"
+ },
+ "createdAt": {
+ "type": "string",
+ "format": "date-time"
+ }
+ }
+ },
+ "Invite": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "organisationId": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "invitedOrganisationId": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "awardId": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "contractId": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "recipient": {
+ "type": "string",
+ "format": "uuid"
+ },
+ "createdBy": {
+ "type": "string",
+ "format": "uuid"
+ },
+ "updatedBy": {
+ "type": [
+ "null",
+ "string"
+ ],
+ "format": "uuid"
+ },
+ "createdAt": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "updatedAt": {
+ "type": [
+ "null",
+ "string"
+ ],
+ "format": "date-time"
+ }
+ }
+ },
+ "InviteRequest": {
+ "required": [
+ "organisationId",
+ "invitedOrganisationId",
+ "awardId",
+ "contractId",
+ "recipient",
+ "message",
+ "platform"
+ ],
+ "type": "object",
+ "properties": {
+ "organisationId": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "invitedOrganisationId": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "awardId": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "contractId": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "recipient": {
+ "type": "string",
+ "format": "uuid"
+ },
+ "message": {
+ "type": "string"
+ },
+ "platform": {
+ "$ref": "#/components/schemas/NotificationPlatform"
+ }
+ }
+ },
+ "Milestone": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "projectId": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "createdBy": {
+ "type": "string",
+ "format": "uuid"
+ },
+ "updatedBy": {
+ "type": [
+ "null",
+ "string"
+ ],
+ "format": "uuid"
+ },
+ "createdAt": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "updatedAt": {
+ "type": [
+ "null",
+ "string"
+ ],
+ "format": "date-time"
+ },
+ "dueAt": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "name": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "status": {
+ "$ref": "#/components/schemas/ApprovalStatus"
+ },
+ "priority": {
+ "$ref": "#/components/schemas/Priority"
+ }
+ }
+ },
+ "NotificationPlatform": {
+ "type": "integer"
+ },
+ "Organisation": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "createdBy": {
+ "type": "string",
+ "format": "uuid"
+ },
+ "createdAt": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "registrationNo": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "name": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "email": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "type": {
+ "$ref": "#/components/schemas/OrganisationType"
+ },
+ "status": {
+ "$ref": "#/components/schemas/ActivityStatus"
+ }
+ }
+ },
+ "OrganisationType": {
+ "type": "integer",
+ "default": 5
+ },
+ "Portfolio": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "organisationId": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "ownedBy": {
+ "type": "string",
+ "format": "uuid"
+ },
+ "createdBy": {
+ "type": "string",
+ "format": "uuid"
+ },
+ "createdAt": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "name": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "description": {
+ "type": [
+ "null",
+ "string"
+ ]
+ }
+ }
+ },
+ "Priority": {
+ "type": "integer"
+ },
+ "ProblemDetails": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "title": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "status": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "null",
+ "integer",
+ "string"
+ ],
+ "format": "int32"
+ },
+ "detail": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "instance": {
+ "type": [
+ "null",
+ "string"
+ ]
+ }
+ }
+ },
+ "Programme": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "portfolioId": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "ownedBy": {
+ "type": "string",
+ "format": "uuid"
+ },
+ "createdBy": {
+ "type": "string",
+ "format": "uuid"
+ },
+ "updatedBy": {
+ "type": [
+ "null",
+ "string"
+ ],
+ "format": "uuid"
+ },
+ "createdAt": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "updatedAt": {
+ "type": [
+ "null",
+ "string"
+ ],
+ "format": "date-time"
+ },
+ "name": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "status": {
+ "$ref": "#/components/schemas/ApprovalStatus"
+ }
+ }
+ },
+ "Project": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "programmeId": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "createdBy": {
+ "type": "string",
+ "format": "uuid"
+ },
+ "updatedBy": {
+ "type": [
+ "null",
+ "string"
+ ],
+ "format": "uuid"
+ },
+ "createdAt": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "updatedAt": {
+ "type": [
+ "null",
+ "string"
+ ],
+ "format": "date-time"
+ },
+ "startedAt": {
+ "type": [
+ "null",
+ "string"
+ ],
+ "format": "date-time"
+ },
+ "endedAt": {
+ "type": [
+ "null",
+ "string"
+ ],
+ "format": "date-time"
+ },
+ "sector": {
+ "$ref": "#/components/schemas/IndustrySector"
+ },
+ "projectType": {
+ "$ref": "#/components/schemas/ProjectType"
+ },
+ "name": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "description": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "status": {
+ "$ref": "#/components/schemas/ApprovalStatus"
+ }
+ }
+ },
+ "ProjectType": {
+ "type": "integer"
+ },
+ "Risk": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "projectId": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "createdBy": {
+ "type": "string",
+ "format": "uuid"
+ },
+ "createdAt": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "likelihood": {
+ "$ref": "#/components/schemas/RiskLikelihood"
+ },
+ "impact": {
+ "$ref": "#/components/schemas/RiskImpact"
+ },
+ "name": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "description": {
+ "type": [
+ "null",
+ "string"
+ ]
+ }
+ }
+ },
+ "RiskImpact": {
+ "type": "integer"
+ },
+ "RiskLikelihood": {
+ "type": "integer"
+ },
+ "UnitOfMeasure": {
+ "type": "integer"
+ },
+ "UpdateContractRequest": {
+ "required": [
+ "contractId",
+ "status"
+ ],
+ "type": "object",
+ "properties": {
+ "contractId": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "status": {
+ "$ref": "#/components/schemas/ContractStatus"
+ }
+ }
+ },
+ "UpdateDisbursementRequest": {
+ "required": [
+ "disbursementId",
+ "milestoneId",
+ "amount"
+ ],
+ "type": "object",
+ "properties": {
+ "disbursementId": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "milestoneId": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "null",
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "amount": {
+ "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$",
+ "type": [
+ "number",
+ "string"
+ ],
+ "format": "double"
+ }
+ }
+ },
+ "UpdateEvidenceRequest": {
+ "required": [
+ "evidenceId",
+ "version",
+ "evidenceType",
+ "status"
+ ],
+ "type": "object",
+ "properties": {
+ "evidenceId": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "version": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int32"
+ },
+ "evidenceType": {
+ "$ref": "#/components/schemas/EvidenceType"
+ },
+ "status": {
+ "$ref": "#/components/schemas/ApprovalStatus"
+ }
+ }
+ },
+ "UpdateMilestoneRequest": {
+ "required": [
+ "milestoneId",
+ "dueAt",
+ "name",
+ "priority",
+ "status"
+ ],
+ "type": "object",
+ "properties": {
+ "milestoneId": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "dueAt": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "name": {
+ "type": "string"
+ },
+ "priority": {
+ "$ref": "#/components/schemas/Priority"
+ },
+ "status": {
+ "$ref": "#/components/schemas/ApprovalStatus"
+ }
+ }
+ },
+ "UpdateOrganisationRequest": {
+ "required": [
+ "organisationId",
+ "name",
+ "registrationNo",
+ "email",
+ "type"
+ ],
+ "type": "object",
+ "properties": {
+ "organisationId": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "name": {
+ "type": "string"
+ },
+ "registrationNo": {
+ "type": "string"
+ },
+ "email": {
+ "type": "string"
+ },
+ "type": {
+ "$ref": "#/components/schemas/OrganisationType"
+ }
+ }
+ },
+ "UpdatePortfolioRequest": {
+ "required": [
+ "portfolioId",
+ "name",
+ "description",
+ "ownerId"
+ ],
+ "type": "object",
+ "properties": {
+ "portfolioId": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "name": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "ownerId": {
+ "type": "string",
+ "format": "uuid"
+ }
+ }
+ },
+ "UpdateProgrammeRequest": {
+ "required": [
+ "programmeId",
+ "name",
+ "ownerId",
+ "status"
+ ],
+ "type": "object",
+ "properties": {
+ "programmeId": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "name": {
+ "type": "string"
+ },
+ "ownerId": {
+ "type": "string",
+ "format": "uuid"
+ },
+ "status": {
+ "$ref": "#/components/schemas/ApprovalStatus"
+ }
+ }
+ },
+ "UpdateProjectRequest": {
+ "required": [
+ "projectId",
+ "name",
+ "description",
+ "startedAt",
+ "endedAt",
+ "sector",
+ "projectType"
+ ],
+ "type": "object",
+ "properties": {
+ "projectId": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "name": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "startedAt": {
+ "type": [
+ "null",
+ "string"
+ ],
+ "format": "date-time"
+ },
+ "endedAt": {
+ "type": [
+ "null",
+ "string"
+ ],
+ "format": "date-time"
+ },
+ "sector": {
+ "$ref": "#/components/schemas/IndustrySector"
+ },
+ "projectType": {
+ "$ref": "#/components/schemas/ProjectType"
+ }
+ }
+ },
+ "UpdateProjectStatusRequest": {
+ "required": [
+ "projectId",
+ "status"
+ ],
+ "type": "object",
+ "properties": {
+ "projectId": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "status": {
+ "$ref": "#/components/schemas/ApprovalStatus"
+ }
+ }
+ },
+ "UpdateRiskRequest": {
+ "required": [
+ "riskId",
+ "likelihood",
+ "impact",
+ "name",
+ "description"
+ ],
+ "type": "object",
+ "properties": {
+ "riskId": {
+ "pattern": "^-?(?:0|[1-9]\\d*)$",
+ "type": [
+ "integer",
+ "string"
+ ],
+ "format": "int64"
+ },
+ "likelihood": {
+ "$ref": "#/components/schemas/RiskLikelihood"
+ },
+ "impact": {
+ "$ref": "#/components/schemas/RiskImpact"
+ },
+ "name": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ }
+ }
+ },
+ "User": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string",
+ "format": "uuid"
+ },
+ "lastLoginAt": {
+ "type": [
+ "null",
+ "string"
+ ],
+ "format": "date-time"
+ },
+ "email": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "status": {
+ "$ref": "#/components/schemas/ActivityStatus"
+ }
+ }
+ },
+ "UserFilterRequest": {
+ "type": "object",
+ "properties": {
+ "status": {
+ "oneOf": [
+ {
+ "type": "null"
+ },
+ {
+ "$ref": "#/components/schemas/ActivityStatus"
+ }
+ ]
+ },
+ "email": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "sortBy": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "sortDescending": {
+ "type": "boolean",
+ "default": false
+ }
+ }
+ }
+ },
+ "securitySchemes": {
+ "Bearer": {
+ "type": "http",
+ "description": "JWT Authorization header using the Bearer scheme",
+ "scheme": "bearer"
+ },
+ "OAuth2": {
+ "type": "oauth2",
+ "description": "Interactive OAuth2 flow for authentication and authorization",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "https://sts.security.khongisa.co.za/connect/authorize",
+ "tokenUrl": "https://sts.security.khongisa.co.za/connect/token",
+ "scopes": {
+ "openid": "OpenID Connect",
+ "profile": "User Profile",
+ "roles": "User Roles",
+ "email": "User Email",
+ "address": "User Address",
+ "pfm-api-dev": "PFM API Scope"
+ }
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ {
+ "name": "Users"
+ },
+ {
+ "name": "Tools"
+ },
+ {
+ "name": "Projects"
+ },
+ {
+ "name": "Grants"
+ },
+ {
+ "name": "Governance"
+ },
+ {
+ "name": "Execution"
+ },
+ {
+ "name": "Evidence & M&E"
+ },
+ {
+ "name": "Communications"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/PostFundManagement.ConsoleUI/openapi_models.txt b/PostFundManagement.ConsoleUI/openapi_models.txt
new file mode 100644
index 0000000..21575ba
--- /dev/null
+++ b/PostFundManagement.ConsoleUI/openapi_models.txt
@@ -0,0 +1,434 @@
+=== ALL SCHEMAS WITH PROPERTIES ===
+
+Model: Activity
+ Required: []
+ id: integer,string (format: int64, nullable: false)
+ projectId: null,integer,string (format: int64, nullable: false)
+ milestoneId: null,integer,string (format: int64, nullable: false)
+ createdBy: string (format: uuid, nullable: false)
+ updatedBy: null,string (format: uuid, nullable: false)
+ approvedBy: null,string (format: uuid, nullable: false)
+ createdAt: string (format: date-time, nullable: false)
+ updatedAt: null,string (format: date-time, nullable: false)
+ category: null,string (format: -, nullable: false)
+ status: ApprovalStatus (format: -, nullable: false)
+ amount: number,string (format: double, nullable: false)
+ title: null,string (format: -, nullable: false)
+ description: null,string (format: -, nullable: false)
+ performedAt: null,string (format: date-time, nullable: false)
+
+Model: Award
+ Required: []
+ id: integer,string (format: int64, nullable: false)
+ organisationId: integer,string (format: int64, nullable: false)
+ programmeId: integer,string (format: int64, nullable: false)
+ projectId: null,integer,string (format: int64, nullable: false)
+ createdBy: string (format: uuid, nullable: false)
+ updatedBy: null,string (format: uuid, nullable: false)
+ approvedBy: null,string (format: uuid, nullable: false)
+ createdAt: string (format: date-time, nullable: false)
+ updatedAt: null,string (format: date-time, nullable: false)
+ status: ApprovalStatus (format: -, nullable: false)
+ amount: number,string (format: double, nullable: false)
+
+Model: Budget
+ Required: []
+ id: integer,string (format: int64, nullable: false)
+ projectId: integer,string (format: int64, nullable: false)
+ createdBy: string (format: uuid, nullable: false)
+ approvedBy: null,string (format: uuid, nullable: false)
+ createdAt: string (format: date-time, nullable: false)
+ updatedAt: null,string (format: date-time, nullable: false)
+ name: null,string (format: -, nullable: false)
+ amount: number,string (format: double, nullable: false)
+
+Model: Contract
+ Required: []
+ id: integer,string (format: int64, nullable: false)
+ awardId: integer,string (format: int64, nullable: false)
+ externalSignatureId: null,string (format: -, nullable: false)
+ signedDocumentUrl: null,string (format: -, nullable: false)
+ effectiveAt: null,string (format: date-time, nullable: false)
+ expiresAt: null,string (format: date-time, nullable: false)
+ totalValue: number,string (format: double, nullable: false)
+ status: ContractStatus (format: -, nullable: false)
+ createdBy: string (format: uuid, nullable: false)
+ createdAt: string (format: date-time, nullable: false)
+
+Model: CreateActivityRequest
+ Required: [
+ 'projectId',
+ 'milestoneId',
+ 'category',
+ 'amount',
+ 'title',
+ 'description',
+ 'performedAt'
+]
+ projectId*: integer,string (format: int64, nullable: false)
+ milestoneId*: null,integer,string (format: int64, nullable: false)
+ category*: string (format: -, nullable: false)
+ amount*: number,string (format: double, nullable: false)
+ title*: string (format: -, nullable: false)
+ description*: string (format: -, nullable: false)
+ performedAt*: null,string (format: date-time, nullable: false)
+
+Model: CreateAwardRequest
+ Required: [ 'organisationId', 'programmeId', 'amount' ]
+ organisationId*: integer,string (format: int64, nullable: false)
+ programmeId*: integer,string (format: int64, nullable: false)
+ amount*: number,string (format: double, nullable: false)
+
+Model: CreateBudgetRequest
+ Required: [ 'projectId', 'name', 'amount' ]
+ projectId*: integer,string (format: int64, nullable: false)
+ name*: string (format: -, nullable: false)
+ amount*: number,string (format: double, nullable: false)
+
+Model: CreateContractRequest
+ Required: [ 'awardId', 'totalValue', 'effectiveAt', 'expiresAt' ]
+ awardId*: integer,string (format: int64, nullable: false)
+ totalValue*: number,string (format: double, nullable: false)
+ effectiveAt*: string (format: date-time, nullable: false)
+ expiresAt*: string (format: date-time, nullable: false)
+
+Model: CreateDisbursementRequest
+ Required: [ 'projectId', 'milestoneId', 'amount' ]
+ projectId*: integer,string (format: int64, nullable: false)
+ milestoneId*: null,integer,string (format: int64, nullable: false)
+ amount*: number,string (format: double, nullable: false)
+
+Model: CreateIndicatorRequest
+ Required: [
+ 'projectId',
+ 'name',
+ 'unitOfMeasure',
+ 'baselineAmount',
+ 'targetAmount'
+]
+ projectId*: integer,string (format: int64, nullable: false)
+ name*: string (format: -, nullable: false)
+ unitOfMeasure*: UnitOfMeasure (format: -, nullable: false)
+ baselineAmount*: number,string (format: double, nullable: false)
+ targetAmount*: number,string (format: double, nullable: false)
+
+Model: CreateMilestoneRequest
+ Required: [ 'projectId', 'dueAt', 'name', 'priority' ]
+ projectId*: integer,string (format: int64, nullable: false)
+ dueAt*: string (format: date-time, nullable: false)
+ name*: string (format: -, nullable: false)
+ priority*: Priority (format: -, nullable: false)
+
+Model: CreateOrganisationRequest
+ Required: [ 'name', 'registrationNo', 'email' ]
+ name*: string (format: -, nullable: false)
+ registrationNo*: string (format: -, nullable: false)
+ email*: string (format: -, nullable: false)
+ type: OrganisationType (format: -, nullable: false)
+
+Model: CreatePortfolioRequest
+ Required: [ 'name', 'description', 'organisationId', 'ownerId' ]
+ name*: string (format: -, nullable: false)
+ description*: string (format: -, nullable: false)
+ organisationId*: integer,string (format: int64, nullable: false)
+ ownerId*: string (format: uuid, nullable: false)
+
+Model: CreateProgrammeRequest
+ Required: [ 'portfolioId', 'name', 'ownerId' ]
+ portfolioId*: integer,string (format: int64, nullable: false)
+ name*: string (format: -, nullable: false)
+ ownerId*: string (format: uuid, nullable: false)
+
+Model: CreateProjectRequest
+ Required: [ 'programmeId', 'name', 'description', 'sector', 'projectType' ]
+ programmeId*: integer,string (format: int64, nullable: false)
+ name*: string (format: -, nullable: false)
+ description*: string (format: -, nullable: false)
+ sector*: IndustrySector (format: -, nullable: false)
+ projectType*: ProjectType (format: -, nullable: false)
+
+Model: CreateRiskRequest
+ Required: [ 'projectId', 'likelihood', 'impact', 'name', 'description' ]
+ projectId*: integer,string (format: int64, nullable: false)
+ likelihood*: RiskLikelihood (format: -, nullable: false)
+ impact*: RiskImpact (format: -, nullable: false)
+ name*: string (format: -, nullable: false)
+ description*: string (format: -, nullable: false)
+
+Model: Dashboard
+ Required: [
+ 'portfolioValue',
+ 'activeAwards',
+ 'disbursed',
+ 'onTrackPercentage',
+ 'atRiskCount',
+ 'beneficiariesReached'
+]
+ portfolioValue*: number,string (format: double, nullable: false)
+ activeAwards*: integer,string (format: int32, nullable: false)
+ disbursed*: number,string (format: double, nullable: false)
+ onTrackPercentage*: number,string (format: double, nullable: false)
+ atRiskCount*: integer,string (format: int32, nullable: false)
+ beneficiariesReached*: integer,string (format: int32, nullable: false)
+
+Model: Disbursement
+ Required: []
+ id: integer,string (format: int64, nullable: false)
+ projectId: integer,string (format: int64, nullable: false)
+ milestoneId: null,integer,string (format: int64, nullable: false)
+ createdBy: string (format: uuid, nullable: false)
+ updatedBy: null,string (format: uuid, nullable: false)
+ createdAt: string (format: date-time, nullable: false)
+ updatedAt: null,string (format: date-time, nullable: false)
+ amount: number,string (format: double, nullable: false)
+ status: ApprovalStatus (format: -, nullable: false)
+
+Model: Evidence
+ Required: []
+ id: integer,string (format: int64, nullable: false)
+ projectId: integer,string (format: int64, nullable: false)
+ milestoneId: integer,string (format: int64, nullable: false)
+ indicatorId: integer,string (format: int64, nullable: false)
+ activityId: integer,string (format: int64, nullable: false)
+ createdBy: string (format: uuid, nullable: false)
+ updatedBy: null,string (format: uuid, nullable: false)
+ createdAt: string (format: date-time, nullable: false)
+ updatedAt: null,string (format: date-time, nullable: false)
+ version: integer,string (format: int32, nullable: false)
+ type: EvidenceType (format: -, nullable: false)
+ documentUrl: null,string (format: -, nullable: false)
+ status: ApprovalStatus (format: -, nullable: false)
+
+Model: ExecuteContractRequest
+ Required: [ 'contractId', 'externalSignatureId', 'signedDocumentUrl' ]
+ contractId*: integer,string (format: int64, nullable: false)
+ externalSignatureId*: string (format: -, nullable: false)
+ signedDocumentUrl*: string (format: -, nullable: false)
+
+Model: Indicator
+ Required: []
+ id: integer,string (format: int64, nullable: false)
+ projectId: integer,string (format: int64, nullable: false)
+ createdBy: string (format: uuid, nullable: false)
+ createdAt: string (format: date-time, nullable: false)
+ name: null,string (format: -, nullable: false)
+ unitOfMeasure: any (format: -, nullable: false)
+ baselineAmount: number,string (format: double, nullable: false)
+ targetAmount: number,string (format: double, nullable: false)
+ actualAmount: number,string (format: double, nullable: false)
+
+Model: Instrument
+ Required: []
+ id: integer,string (format: int64, nullable: false)
+ programmeId: integer,string (format: int64, nullable: false)
+ code: null,string (format: -, nullable: false)
+ name: null,string (format: -, nullable: false)
+ description: null,string (format: -, nullable: false)
+ status: ActivityStatus (format: -, nullable: false)
+ createdBy: string (format: uuid, nullable: false)
+ createdAt: string (format: date-time, nullable: false)
+
+Model: Invite
+ Required: []
+ id: integer,string (format: int64, nullable: false)
+ organisationId: integer,string (format: int64, nullable: false)
+ invitedOrganisationId: integer,string (format: int64, nullable: false)
+ awardId: integer,string (format: int64, nullable: false)
+ contractId: integer,string (format: int64, nullable: false)
+ recipient: string (format: uuid, nullable: false)
+ createdBy: string (format: uuid, nullable: false)
+ updatedBy: null,string (format: uuid, nullable: false)
+ createdAt: string (format: date-time, nullable: false)
+ updatedAt: null,string (format: date-time, nullable: false)
+
+Model: InviteRequest
+ Required: [
+ 'organisationId',
+ 'invitedOrganisationId',
+ 'awardId',
+ 'contractId',
+ 'recipient',
+ 'message',
+ 'platform'
+]
+ organisationId*: integer,string (format: int64, nullable: false)
+ invitedOrganisationId*: integer,string (format: int64, nullable: false)
+ awardId*: integer,string (format: int64, nullable: false)
+ contractId*: integer,string (format: int64, nullable: false)
+ recipient*: string (format: uuid, nullable: false)
+ message*: string (format: -, nullable: false)
+ platform*: NotificationPlatform (format: -, nullable: false)
+
+Model: Milestone
+ Required: []
+ id: integer,string (format: int64, nullable: false)
+ projectId: integer,string (format: int64, nullable: false)
+ createdBy: string (format: uuid, nullable: false)
+ updatedBy: null,string (format: uuid, nullable: false)
+ createdAt: string (format: date-time, nullable: false)
+ updatedAt: null,string (format: date-time, nullable: false)
+ dueAt: string (format: date-time, nullable: false)
+ name: null,string (format: -, nullable: false)
+ status: ApprovalStatus (format: -, nullable: false)
+ priority: Priority (format: -, nullable: false)
+
+Model: Organisation
+ Required: []
+ id: integer,string (format: int64, nullable: false)
+ createdBy: string (format: uuid, nullable: false)
+ createdAt: string (format: date-time, nullable: false)
+ registrationNo: null,string (format: -, nullable: false)
+ name: null,string (format: -, nullable: false)
+ email: null,string (format: -, nullable: false)
+ type: OrganisationType (format: -, nullable: false)
+ status: ActivityStatus (format: -, nullable: false)
+
+Model: Portfolio
+ Required: []
+ id: integer,string (format: int64, nullable: false)
+ organisationId: integer,string (format: int64, nullable: false)
+ ownedBy: string (format: uuid, nullable: false)
+ createdBy: string (format: uuid, nullable: false)
+ createdAt: string (format: date-time, nullable: false)
+ name: null,string (format: -, nullable: false)
+ description: null,string (format: -, nullable: false)
+
+Model: ProblemDetails
+ Required: []
+ type: null,string (format: -, nullable: false)
+ title: null,string (format: -, nullable: false)
+ status: null,integer,string (format: int32, nullable: false)
+ detail: null,string (format: -, nullable: false)
+ instance: null,string (format: -, nullable: false)
+
+Model: Programme
+ Required: []
+ id: integer,string (format: int64, nullable: false)
+ portfolioId: integer,string (format: int64, nullable: false)
+ ownedBy: string (format: uuid, nullable: false)
+ createdBy: string (format: uuid, nullable: false)
+ updatedBy: null,string (format: uuid, nullable: false)
+ createdAt: string (format: date-time, nullable: false)
+ updatedAt: null,string (format: date-time, nullable: false)
+ name: null,string (format: -, nullable: false)
+ status: ApprovalStatus (format: -, nullable: false)
+
+Model: Project
+ Required: []
+ id: integer,string (format: int64, nullable: false)
+ programmeId: integer,string (format: int64, nullable: false)
+ createdBy: string (format: uuid, nullable: false)
+ updatedBy: null,string (format: uuid, nullable: false)
+ createdAt: string (format: date-time, nullable: false)
+ updatedAt: null,string (format: date-time, nullable: false)
+ startedAt: null,string (format: date-time, nullable: false)
+ endedAt: null,string (format: date-time, nullable: false)
+ sector: IndustrySector (format: -, nullable: false)
+ projectType: ProjectType (format: -, nullable: false)
+ name: null,string (format: -, nullable: false)
+ description: null,string (format: -, nullable: false)
+ status: ApprovalStatus (format: -, nullable: false)
+
+Model: Risk
+ Required: []
+ id: integer,string (format: int64, nullable: false)
+ projectId: integer,string (format: int64, nullable: false)
+ createdBy: string (format: uuid, nullable: false)
+ createdAt: string (format: date-time, nullable: false)
+ likelihood: RiskLikelihood (format: -, nullable: false)
+ impact: RiskImpact (format: -, nullable: false)
+ name: null,string (format: -, nullable: false)
+ description: null,string (format: -, nullable: false)
+
+Model: UpdateContractRequest
+ Required: [ 'contractId', 'status' ]
+ contractId*: integer,string (format: int64, nullable: false)
+ status*: ContractStatus (format: -, nullable: false)
+
+Model: UpdateDisbursementRequest
+ Required: [ 'disbursementId', 'milestoneId', 'amount' ]
+ disbursementId*: integer,string (format: int64, nullable: false)
+ milestoneId*: null,integer,string (format: int64, nullable: false)
+ amount*: number,string (format: double, nullable: false)
+
+Model: UpdateEvidenceRequest
+ Required: [ 'evidenceId', 'version', 'evidenceType', 'status' ]
+ evidenceId*: integer,string (format: int64, nullable: false)
+ version*: integer,string (format: int32, nullable: false)
+ evidenceType*: EvidenceType (format: -, nullable: false)
+ status*: ApprovalStatus (format: -, nullable: false)
+
+Model: UpdateMilestoneRequest
+ Required: [ 'milestoneId', 'dueAt', 'name', 'priority', 'status' ]
+ milestoneId*: integer,string (format: int64, nullable: false)
+ dueAt*: string (format: date-time, nullable: false)
+ name*: string (format: -, nullable: false)
+ priority*: Priority (format: -, nullable: false)
+ status*: ApprovalStatus (format: -, nullable: false)
+
+Model: UpdateOrganisationRequest
+ Required: [ 'organisationId', 'name', 'registrationNo', 'email', 'type' ]
+ organisationId*: integer,string (format: int64, nullable: false)
+ name*: string (format: -, nullable: false)
+ registrationNo*: string (format: -, nullable: false)
+ email*: string (format: -, nullable: false)
+ type*: OrganisationType (format: -, nullable: false)
+
+Model: UpdatePortfolioRequest
+ Required: [ 'portfolioId', 'name', 'description', 'ownerId' ]
+ portfolioId*: integer,string (format: int64, nullable: false)
+ name*: string (format: -, nullable: false)
+ description*: string (format: -, nullable: false)
+ ownerId*: string (format: uuid, nullable: false)
+
+Model: UpdateProgrammeRequest
+ Required: [ 'programmeId', 'name', 'ownerId', 'status' ]
+ programmeId*: integer,string (format: int64, nullable: false)
+ name*: string (format: -, nullable: false)
+ ownerId*: string (format: uuid, nullable: false)
+ status*: ApprovalStatus (format: -, nullable: false)
+
+Model: UpdateProjectRequest
+ Required: [
+ 'projectId',
+ 'name',
+ 'description',
+ 'startedAt',
+ 'endedAt',
+ 'sector',
+ 'projectType'
+]
+ projectId*: integer,string (format: int64, nullable: false)
+ name*: string (format: -, nullable: false)
+ description*: string (format: -, nullable: false)
+ startedAt*: null,string (format: date-time, nullable: false)
+ endedAt*: null,string (format: date-time, nullable: false)
+ sector*: IndustrySector (format: -, nullable: false)
+ projectType*: ProjectType (format: -, nullable: false)
+
+Model: UpdateProjectStatusRequest
+ Required: [ 'projectId', 'status' ]
+ projectId*: integer,string (format: int64, nullable: false)
+ status*: ApprovalStatus (format: -, nullable: false)
+
+Model: UpdateRiskRequest
+ Required: [ 'riskId', 'likelihood', 'impact', 'name', 'description' ]
+ riskId*: integer,string (format: int64, nullable: false)
+ likelihood*: RiskLikelihood (format: -, nullable: false)
+ impact*: RiskImpact (format: -, nullable: false)
+ name*: string (format: -, nullable: false)
+ description*: string (format: -, nullable: false)
+
+Model: User
+ Required: []
+ id: string (format: uuid, nullable: false)
+ lastLoginAt: null,string (format: date-time, nullable: false)
+ email: null,string (format: -, nullable: false)
+ status: ActivityStatus (format: -, nullable: false)
+
+Model: UserFilterRequest
+ Required: []
+ status: any (format: -, nullable: false)
+ email: null,string (format: -, nullable: false)
+ sortBy: null,string (format: -, nullable: false)
+ sortDescending: boolean (format: -, nullable: false)
diff --git a/PostFundManagement.ConsoleUI/openapi_summary.txt b/PostFundManagement.ConsoleUI/openapi_summary.txt
new file mode 100644
index 0000000..c5a262b
--- /dev/null
+++ b/PostFundManagement.ConsoleUI/openapi_summary.txt
@@ -0,0 +1,919 @@
+=== COMPONENT SCHEMAS ===
+Activity, ActivityStatus, ApprovalStatus, Award, Budget, Contract, ContractStatus, CreateActivityRequest, CreateAwardRequest, CreateBudgetRequest, CreateContractRequest, CreateDisbursementRequest, CreateIndicatorRequest, CreateMilestoneRequest, CreateOrganisationRequest, CreatePortfolioRequest, CreateProgrammeRequest, CreateProjectRequest, CreateRiskRequest, Dashboard, Disbursement, Evidence, EvidenceType, ExecuteContractRequest, Indicator, IndustrySector, Instrument, Invite, InviteRequest, Milestone, NotificationPlatform, Organisation, OrganisationType, Portfolio, Priority, ProblemDetails, Programme, Project, ProjectType, Risk, RiskImpact, RiskLikelihood, UnitOfMeasure, UpdateContractRequest, UpdateDisbursementRequest, UpdateEvidenceRequest, UpdateMilestoneRequest, UpdateOrganisationRequest, UpdatePortfolioRequest, UpdateProgrammeRequest, UpdateProjectRequest, UpdateProjectStatusRequest, UpdateRiskRequest, User, UserFilterRequest
+
+=== DETAILED ENDPOINTS (EXCLUDING TOOLS) ===
+
+------------------------------------------------------------
+GET /v1/api/users/{userId} [Tag: Users]
+OperationId: getuser
+Description: Gets the metadata of a single user.
+Parameters:
+ - userId (in: path, required: true) schema: {"type":"string","format":"uuid"}
+Responses:
+ [200] application/json; version=1 -> {"$ref":"#/components/schemas/User"}
+ [401] (No content)
+ [404] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+POST /v1/api/users/{page}/{pageSize} [Tag: Users]
+OperationId: getusers
+Description: Paginated, searchable directory of system users. Supports filtering by ActivityStatus, searching by Email, and sorting
+Parameters:
+ - page (in: path, required: true) schema: {"pattern":"^-?(?:0|[1-9]\\d*)$","type":"integer","format":"int32","default":1}
+ - pageSize (in: path, required: true) schema: {"pattern":"^-?(?:0|[1-9]\\d*)$","type":"integer","format":"int32","default":100}
+Request Body: required=true
+ Content-Type: application/json; version=1 -> Schema: {"$ref":"#/components/schemas/UserFilterRequest"}
+Responses:
+ [200] application/json; version=1 -> {"type":"array","items":{"$ref":"#/components/schemas/User"}}
+ [401] (No content)
+ [404] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+POST /v1/api/integration/sync [Tag: Users]
+OperationId: syncuser
+Description: Trigger integration and data synchronization in the background
+Responses:
+ [200] (No content)
+ [401] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+GET /v1/api/users/{userId}/status/{status} [Tag: Users]
+OperationId: updateuserstatus
+Description: Changes the Activity status of the user in the system
+Parameters:
+ - userId (in: path, required: true) schema: {"type":"string","format":"uuid"}
+ - status (in: path, required: true) schema: {"$ref":"#/components/schemas/ActivityStatus"}
+Responses:
+ [200] (No content)
+ [400] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+ [401] (No content)
+ [404] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+POST /v1/api/projects/activities [Tag: Projects]
+OperationId: createactivity
+Description: Add a new activity under project (M04)
+Request Body: required=true
+ Content-Type: application/json; version=1 -> Schema: {"$ref":"#/components/schemas/CreateActivityRequest"}
+Responses:
+ [200] (No content)
+ [400] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+ [401] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+POST /v1/api/projects/programme [Tag: Projects]
+OperationId: createprogramme
+Description: Create programme
+Request Body: required=true
+ Content-Type: application/json; version=1 -> Schema: {"$ref":"#/components/schemas/CreateProgrammeRequest"}
+Responses:
+ [200] (No content)
+ [401] (No content)
+ [403] (No content)
+ [404] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+PUT /v1/api/projects/programme [Tag: Projects]
+OperationId: updateprogramme
+Description: Update existing programme
+Request Body: required=true
+ Content-Type: application/json; version=1 -> Schema: {"$ref":"#/components/schemas/UpdateProgrammeRequest"}
+Responses:
+ [200] (No content)
+ [400] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+ [401] (No content)
+ [403] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+POST /v1/api/projects [Tag: Projects]
+OperationId: createproject
+Description: Create a new project (M04)
+Request Body: required=true
+ Content-Type: application/json; version=1 -> Schema: {"$ref":"#/components/schemas/CreateProjectRequest"}
+Responses:
+ [200] (No content)
+ [400] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+ [401] (No content)
+ [404] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+GET /v1/api/projects [Tag: Projects]
+OperationId: getprojects
+Description: Get a list of projects
+Parameters:
+ - page (in: query, required: false) schema: {"pattern":"^-?(?:0|[1-9]\\d*)$","type":["integer","string"],"format":"int32","default":1}
+ - pageSize (in: query, required: false) schema: {"pattern":"^-?(?:0|[1-9]\\d*)$","type":["integer","string"],"format":"int32","default":100}
+Responses:
+ [200] application/json; version=1 -> {"type":"array","items":{"$ref":"#/components/schemas/Project"}}
+ [401] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+PUT /v1/api/projects [Tag: Projects]
+OperationId: updateproject
+Description: Update project details
+Request Body: required=true
+ Content-Type: application/json; version=1 -> Schema: {"$ref":"#/components/schemas/UpdateProjectRequest"}
+Responses:
+ [200] (No content)
+ [400] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+ [401] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+GET /v1/api/projects/{projectId}/activities [Tag: Projects]
+OperationId: getactivities
+Description: Get a list of activities for a project
+Parameters:
+ - projectId (in: path, required: true) schema: {"type":"string"}
+Responses:
+ [200] application/json; version=1 -> {"type":"array","items":{"$ref":"#/components/schemas/Activity"}}
+ [401] (No content)
+ [403] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+GET /v1/api/projects/activities/{activityId} [Tag: Projects]
+OperationId: getactivity
+Description: Gets a specific activity by it's id
+Parameters:
+ - activityId (in: path, required: true) schema: {"type":"string"}
+Responses:
+ [200] application/json; version=1 -> {"$ref":"#/components/schemas/Activity"}
+ [401] (No content)
+ [403] (No content)
+ [404] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+GET /v1/api/projects/milestones/{milestoneId} [Tag: Projects]
+OperationId: getmilestone
+Description: Gets a specific milestone
+Parameters:
+ - milestoneId (in: path, required: true) schema: {"type":"string"}
+Responses:
+ [200] application/json; version=1 -> {"$ref":"#/components/schemas/Milestone"}
+ [401] (No content)
+ [403] (No content)
+ [404] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+GET /v1/api/projects/milestones/{page}/{pageSize} [Tag: Projects]
+OperationId: getmilestones
+Description: Get a list of all milestones
+Parameters:
+ - page (in: path, required: true) schema: {"pattern":"^-?(?:0|[1-9]\\d*)$","type":"integer","format":"int32","default":1}
+ - pageSize (in: path, required: true) schema: {"pattern":"^-?(?:0|[1-9]\\d*)$","type":"integer","format":"int32","default":100}
+Responses:
+ [200] application/json; version=1 -> {"type":"array","items":{"$ref":"#/components/schemas/Milestone"}}
+ [401] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+GET /v1/api/projects/programme/{programmeId} [Tag: Projects]
+OperationId: getprogramme
+Description: Get specific programme
+Parameters:
+ - programmeId (in: path, required: true) schema: {"type":"string"}
+Responses:
+ [200] application/json; version=1 -> {"$ref":"#/components/schemas/Programme"}
+ [401] (No content)
+ [403] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+GET /v1/api/projects/{programmeId}/programme [Tag: Projects]
+OperationId: getprogrammeprojects
+Description: Get a list of all projects under a programme
+Parameters:
+ - programmeId (in: path, required: true) schema: {"type":"string"}
+Responses:
+ [200] application/json; version=1 -> {"type":"array","items":{"$ref":"#/components/schemas/Project"}}
+ [401] (No content)
+ [403] (No content)
+ [404] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+GET /v1/api/projects/programme/{page}/{pageSize} [Tag: Projects]
+OperationId: getprogrammesprojects
+Description: Get a list of all programmes
+Parameters:
+ - page (in: path, required: true) schema: {"pattern":"^-?(?:0|[1-9]\\d*)$","type":"integer","format":"int32","default":1}
+ - pageSize (in: path, required: true) schema: {"pattern":"^-?(?:0|[1-9]\\d*)$","type":"integer","format":"int32","default":100}
+Responses:
+ [200] application/json; version=1 -> {"type":"array","items":{"$ref":"#/components/schemas/Programme"}}
+ [401] (No content)
+ [403] (No content)
+ [404] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+GET /v1/api/projects/{projectId} [Tag: Projects]
+OperationId: getproject
+Description: Get project by ID
+Parameters:
+ - projectId (in: path, required: true) schema: {"type":"string"}
+Responses:
+ [200] application/json; version=1 -> {"type":"array","items":{"$ref":"#/components/schemas/Project"}}
+ [401] (No content)
+ [403] (No content)
+ [404] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+GET /v1/api/projects/indicators/project/{projectId} [Tag: Projects]
+OperationId: getprojectindicators
+Description: Get get a detailed list of project indicators
+Parameters:
+ - projectId (in: path, required: true) schema: {"type":"string"}
+Responses:
+ [200] application/json; version=1 -> {"type":"array","items":{"$ref":"#/components/schemas/Indicator"}}
+ [401] (No content)
+ [403] (No content)
+ [404] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+GET /v1/api/projects/{projectId}/milestones [Tag: Projects]
+OperationId: getprojectmilestones
+Description: Get a list of milestones for a project
+Parameters:
+ - projectId (in: path, required: true) schema: {"type":"string"}
+Responses:
+ [200] application/json; version=1 -> {"type":"array","items":{"$ref":"#/components/schemas/Milestone"}}
+ [401] (No content)
+ [403] (No content)
+ [404] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+GET /v1/api/projects/{projectId}/risks [Tag: Projects]
+OperationId: getprojectrisks
+Description: Get a list of risks for a project
+Parameters:
+ - projectId (in: path, required: true) schema: {"type":"string"}
+Responses:
+ [200] application/json; version=1 -> {"type":"array","items":{"$ref":"#/components/schemas/Risk"}}
+ [401] (No content)
+ [403] (No content)
+ [404] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+PUT /v1/api/projects/milestone [Tag: Projects]
+OperationId: updatemilestone
+Description: Update project milestone details
+Request Body: required=true
+ Content-Type: application/json; version=1 -> Schema: {"$ref":"#/components/schemas/UpdateMilestoneRequest"}
+Responses:
+ [200] (No content)
+ [401] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+PUT /v1/api/projects/status [Tag: Projects]
+OperationId: updateprojectstatus
+Description: Update project status (M04)
+Request Body: required=true
+ Content-Type: application/json; version=1 -> Schema: {"$ref":"#/components/schemas/UpdateProjectStatusRequest"}
+Responses:
+ [200] (No content)
+ [400] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+ [401] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+POST /v1/api/grants/awards [Tag: Grants]
+OperationId: createaward
+Description: Register an approved funding award (M01)
+Request Body: required=true
+ Content-Type: application/json; version=1 -> Schema: {"$ref":"#/components/schemas/CreateAwardRequest"}
+Responses:
+ [200] (No content)
+ [400] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+ [401] (No content)
+ [404] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+GET /v1/api/grants/awards [Tag: Grants]
+OperationId: getawards
+Description: Get a list of awards
+Parameters:
+ - page (in: query, required: false) schema: {"pattern":"^-?(?:0|[1-9]\\d*)$","type":["integer","string"],"format":"int32","default":1}
+ - pageSize (in: query, required: false) schema: {"pattern":"^-?(?:0|[1-9]\\d*)$","type":["integer","string"],"format":"int32","default":10}
+Responses:
+ [200] application/json; version=1 -> {"type":"array","items":{"$ref":"#/components/schemas/Award"}}
+ [401] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+POST /v1/api/grants/budgets [Tag: Grants]
+OperationId: createbudget
+Description: Create a new budget line (M05)
+Request Body: required=true
+ Content-Type: application/json; version=1 -> Schema: {"$ref":"#/components/schemas/CreateBudgetRequest"}
+Responses:
+ [200] (No content)
+ [400] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+ [401] (No content)
+ [404] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+GET /v1/api/grants/budgets [Tag: Grants]
+OperationId: getbudgets
+Description: Get all budgets
+Parameters:
+ - page (in: query, required: false) schema: {"pattern":"^-?(?:0|[1-9]\\d*)$","type":["integer","string"],"format":"int32","default":1}
+ - pageSize (in: query, required: false) schema: {"pattern":"^-?(?:0|[1-9]\\d*)$","type":["integer","string"],"format":"int32","default":100}
+Responses:
+ [200] application/json; version=1 -> {"type":"array","items":{"$ref":"#/components/schemas/Budget"}}
+ [401] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+POST /v1/api/grants/contracts [Tag: Grants]
+OperationId: createcontract
+Description: Create a contract record from an approved award (M02)
+Request Body: required=true
+ Content-Type: application/json; version=1 -> Schema: {"$ref":"#/components/schemas/CreateContractRequest"}
+Responses:
+ [200] (No content)
+ [400] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+ [401] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+GET /v1/api/grants/contracts [Tag: Grants]
+OperationId: getcontracts
+Description: Get all contracts
+Parameters:
+ - page (in: query, required: false) schema: {"pattern":"^-?(?:0|[1-9]\\d*)$","type":["integer","string"],"format":"int32","default":1}
+ - pageSize (in: query, required: false) schema: {"pattern":"^-?(?:0|[1-9]\\d*)$","type":["integer","string"],"format":"int32","default":100}
+Responses:
+ [200] application/json; version=1 -> {"type":"array","items":{"$ref":"#/components/schemas/Contract"}}
+ [401] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+PUT /v1/api/grants/contracts [Tag: Grants]
+OperationId: updatecontract
+Description: Updates a specific contract
+Request Body: required=true
+ Content-Type: application/json; version=1 -> Schema: {"$ref":"#/components/schemas/UpdateContractRequest"}
+Responses:
+ [200] (No content)
+ [400] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+ [401] (No content)
+ [404] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+POST /v1/api/grants/contracts/execute [Tag: Grants]
+OperationId: executecontract
+Description: Execute the contract and lock the contractual version
+Request Body: required=true
+ Content-Type: application/json; version=1 -> Schema: {"$ref":"#/components/schemas/ExecuteContractRequest"}
+Responses:
+ [200] (No content)
+ [400] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+ [401] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+GET /v1/api/grants/awards/{awardId} [Tag: Grants]
+OperationId: getaward
+Description: Get award by ID
+Parameters:
+ - awardId (in: path, required: true) schema: {"type":"string"}
+Responses:
+ [200] application/json; version=1 -> {"$ref":"#/components/schemas/Award"}
+ [401] (No content)
+ [403] (No content)
+ [404] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+GET /v1/api/grants/budgets/{budgetId} [Tag: Grants]
+OperationId: getbudget
+Description: Get budget by ID
+Parameters:
+ - budgetId (in: path, required: true) schema: {"type":"string"}
+Responses:
+ [200] application/json; version=1 -> {"$ref":"#/components/schemas/Budget"}
+ [401] (No content)
+ [403] (No content)
+ [404] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+GET /v1/api/grants/contracts/{contractId} [Tag: Grants]
+OperationId: getcontract
+Description: Get contract by ID
+Parameters:
+ - contractId (in: path, required: true) schema: {"type":"string"}
+Responses:
+ [200] application/json; version=1 -> {"$ref":"#/components/schemas/Contract"}
+ [401] (No content)
+ [403] (No content)
+ [404] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+POST /v1/api/grants/contracts/{contractId} [Tag: Grants]
+OperationId: uploadcontract
+Description: Uploads a contract file securely
+Parameters:
+ - contractId (in: path, required: true) schema: {"type":"string"}
+Responses:
+ [200] (No content)
+ [401] (No content)
+ [403] (No content)
+ [404] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+GET /v1/api/grants/contracts/{contractId}/securelink [Tag: Grants]
+OperationId: getcontracturl
+Description: Gets a secure download URL, has expiry period (default 15min)
+Parameters:
+ - contractId (in: path, required: true) schema: {"type":"string"}
+Responses:
+ [200] application/json; version=1 -> {"type":"string"}
+ [401] (No content)
+ [403] (No content)
+ [404] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+POST /v1/api/governance/organisations [Tag: Governance]
+OperationId: createorganisation
+Description: Create a new organization profile
+Request Body: required=true
+ Content-Type: application/json; version=1 -> Schema: {"$ref":"#/components/schemas/CreateOrganisationRequest"}
+Responses:
+ [200] (No content)
+ [400] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+ [401] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+GET /v1/api/governance/organisations [Tag: Governance]
+OperationId: getorganisations
+Description: Get a paginated directory of organizations
+Parameters:
+ - page (in: query, required: false) schema: {"pattern":"^-?(?:0|[1-9]\\d*)$","type":["integer","string"],"format":"int32","default":1}
+ - pageSize (in: query, required: false) schema: {"pattern":"^-?(?:0|[1-9]\\d*)$","type":["integer","string"],"format":"int32","default":100}
+Responses:
+ [200] application/json; version=1 -> {"type":"array","items":{"$ref":"#/components/schemas/Organisation"}}
+ [401] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+PUT /v1/api/governance/organisations [Tag: Governance]
+OperationId: updateorganisation
+Description: Update an existing organization profile
+Request Body: required=true
+ Content-Type: application/json; version=1 -> Schema: {"$ref":"#/components/schemas/UpdateOrganisationRequest"}
+Responses:
+ [200] (No content)
+ [400] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+ [401] (No content)
+ [404] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+POST /v1/api/governance/organisations/portfolio [Tag: Governance]
+OperationId: createportfolio
+Description: Create a new organization portfolio
+Request Body: required=true
+ Content-Type: application/json; version=1 -> Schema: {"$ref":"#/components/schemas/CreatePortfolioRequest"}
+Responses:
+ [200] (No content)
+ [400] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+ [401] (No content)
+ [403] (No content)
+ [404] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+PUT /v1/api/governance/organisations/portfolio [Tag: Governance]
+OperationId: updateportfolio
+Description: Update an existing organization portfolio
+Request Body: required=true
+ Content-Type: application/json; version=1 -> Schema: {"$ref":"#/components/schemas/UpdatePortfolioRequest"}
+Responses:
+ [200] (No content)
+ [400] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+ [401] (No content)
+ [403] (No content)
+ [404] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+GET /v1/api/governance/dashboard [Tag: Governance]
+OperationId: dashboard
+Description: Get executive portfolio dashboard metrics and KPIs
+Responses:
+ [200] application/json; version=1 -> {"$ref":"#/components/schemas/Dashboard"}
+ [401] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+GET /v1/api/governance/instruments/{programmeId} [Tag: Governance]
+OperationId: getinstruments
+Description: Get all funding instruments
+Parameters:
+ - programmeId (in: path, required: true) schema: {"type":"string"}
+Responses:
+ [200] application/json; version=1 -> {"type":"array","items":{"$ref":"#/components/schemas/Instrument"}}
+ [401] (No content)
+ [403] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+GET /v1/api/governance/organisations/{organisationId} [Tag: Governance]
+OperationId: getorganisation
+Description: Get organization profile by ID
+Parameters:
+ - organisationId (in: path, required: true) schema: {"type":"string"}
+Responses:
+ [200] application/json; version=1 -> {"$ref":"#/components/schemas/Organisation"}
+ [401] (No content)
+ [403] (No content)
+ [404] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+GET /v1/api/governance/portfolios/{portfolioId} [Tag: Governance]
+OperationId: getportfolio
+Description: Get a specific portfolios
+Parameters:
+ - portfolioId (in: path, required: true) schema: {"type":"string"}
+Responses:
+ [200] application/json; version=1 -> {"type":"array","items":{"$ref":"#/components/schemas/Portfolio"}}
+ [401] (No content)
+ [403] (No content)
+ [404] application/json; version=1 -> {"type":"array","items":{"$ref":"#/components/schemas/Portfolio"}}
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+GET /v1/api/governance/portfolios/{portfolioId}/programmes [Tag: Governance]
+OperationId: getportfolioprogrammes
+Description: Get a programme's portfolios
+Parameters:
+ - portfolioId (in: path, required: true) schema: {"type":"string"}
+Responses:
+ [200] application/json; version=1 -> {"type":"array","items":{"$ref":"#/components/schemas/Programme"}}
+ [401] (No content)
+ [403] (No content)
+ [404] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+GET /v1/api/governance/organisations/{organisationId}/portfolio [Tag: Governance]
+OperationId: getportfolios
+Description: Get all portfolios
+Parameters:
+ - organisationId (in: path, required: true) schema: {"type":"string"}
+Responses:
+ [200] application/json; version=1 -> {"type":"array","items":{"$ref":"#/components/schemas/Portfolio"}}
+ [401] (No content)
+ [403] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+GET /v1/api/governance/programmes/{portfolioId} [Tag: Governance]
+OperationId: getprogrammes
+Description: Get all programmes
+Parameters:
+ - portfolioId (in: path, required: true) schema: {"type":"string"}
+Responses:
+ [200] application/json; version=1 -> {"type":"array","items":{"$ref":"#/components/schemas/Programme"}}
+ [401] (No content)
+ [403] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+POST /v1/api/execution/disbursements/{disbursementId}/approve [Tag: Execution]
+OperationId: approvedisbursement
+Description: Approve disbursement and release payment (M06)
+Parameters:
+ - disbursementId (in: path, required: true) schema: {"type":"string"}
+Responses:
+ [200] (No content)
+ [400] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+ [401] (No content)
+ [403] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+POST /v1/api/execution/disbursements [Tag: Execution]
+OperationId: createdisbursement
+Description: Create a new disbursement schedule (M06)
+Request Body: required=true
+ Content-Type: application/json; version=1 -> Schema: {"$ref":"#/components/schemas/CreateDisbursementRequest"}
+Responses:
+ [200] (No content)
+ [400] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+ [401] (No content)
+ [404] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+GET /v1/api/execution/disbursements [Tag: Execution]
+OperationId: getdisbursements
+Description: Get a list of disbursements
+Parameters:
+ - page (in: query, required: false) schema: {"pattern":"^-?(?:0|[1-9]\\d*)$","type":["integer","string"],"format":"int32","default":1}
+ - pageSize (in: query, required: false) schema: {"pattern":"^-?(?:0|[1-9]\\d*)$","type":["integer","string"],"format":"int32","default":10}
+Responses:
+ [200] application/json; version=1 -> {"type":"array","items":{"$ref":"#/components/schemas/Disbursement"}}
+ [401] (No content)
+ [404] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+PUT /v1/api/execution/disbursements [Tag: Execution]
+OperationId: updatedisbursement
+Description: Update the details of a specific disbursement
+Request Body: required=true
+ Content-Type: application/json; version=1 -> Schema: {"$ref":"#/components/schemas/UpdateDisbursementRequest"}
+Responses:
+ [200] (No content)
+ [400] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+ [401] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+POST /v1/api/execution/risks [Tag: Execution]
+OperationId: createrisk
+Description: Add risk to risk register (M09)
+Request Body: required=true
+ Content-Type: application/json; version=1 -> Schema: {"$ref":"#/components/schemas/CreateRiskRequest"}
+Responses:
+ [200] (No content)
+ [400] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+ [401] (No content)
+ [404] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+GET /v1/api/execution/risks [Tag: Execution]
+OperationId: getrisks
+Description: Get a list of all risks
+Parameters:
+ - page (in: query, required: false) schema: {"pattern":"^-?(?:0|[1-9]\\d*)$","type":["integer","string"],"format":"int32","default":1}
+ - pageSize (in: query, required: false) schema: {"pattern":"^-?(?:0|[1-9]\\d*)$","type":["integer","string"],"format":"int32","default":100}
+Responses:
+ [200] (No content)
+ [401] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+PUT /v1/api/execution/risks [Tag: Execution]
+OperationId: updaterisk
+Description: Update the details of a specific risk
+Request Body: required=true
+ Content-Type: application/json; version=1 -> Schema: {"$ref":"#/components/schemas/UpdateRiskRequest"}
+Responses:
+ [200] (No content)
+ [400] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+ [401] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+GET /v1/api/execution/disbursements/{disbursementId} [Tag: Execution]
+OperationId: getdisbursement
+Description: Get a specific disbursement
+Parameters:
+ - disbursementId (in: path, required: true) schema: {"type":"string"}
+Responses:
+ [200] application/json; version=1 -> {"$ref":"#/components/schemas/Disbursement"}
+ [400] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+ [401] (No content)
+ [404] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+GET /v1/api/execution/risks/{riskId} [Tag: Execution]
+OperationId: getrisk
+Description: Get a specific risk
+Parameters:
+ - riskId (in: path, required: true) schema: {"type":"string"}
+Responses:
+ [200] application/json; version=1 -> {"$ref":"#/components/schemas/Risk"}
+ [401] (No content)
+ [404] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+PUT /v1/api/evidence/file [Tag: Evidence & M&E]
+OperationId: createevidence
+Description: Upload supporting evidence file to S3 and register metadata (M14)
+Responses:
+ [200] (No content)
+ [400] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+ [401] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+POST /v1/api/evidence/indicators [Tag: Evidence & M&E]
+OperationId: createindicator
+Description: Define indicators and baselines/targets (M08)
+Request Body: required=true
+ Content-Type: application/json; version=1 -> Schema: {"$ref":"#/components/schemas/CreateIndicatorRequest"}
+Responses:
+ [200] (No content)
+ [400] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+ [401] (No content)
+ [404] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+POST /v1/api/evidence/milestones [Tag: Evidence & M&E]
+OperationId: createmilestonerequest
+Description: Define contractual milestones (M07)
+Request Body: required=true
+ Content-Type: application/json; version=1 -> Schema: {"$ref":"#/components/schemas/CreateMilestoneRequest"}
+Responses:
+ [200] (No content)
+ [400] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+ [401] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+GET /v1/api/evidence/{evidenceId} [Tag: Evidence & M&E]
+OperationId: getevidence
+Description: Get a specific evidence record and assiciated file URLs
+Parameters:
+ - evidenceId (in: path, required: true) schema: {"type":"string"}
+Responses:
+ [200] application/json; version=1 -> {"$ref":"#/components/schemas/Evidence"}
+ [401] (No content)
+ [403] (No content)
+ [404] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+GET /v1/api/evidence/files/{page}/{pageSize} [Tag: Evidence & M&E]
+OperationId: getevidencefiles
+Description: Get a list of all uploaded evidence files metadata
+Parameters:
+ - page (in: path, required: true) schema: {"pattern":"^-?(?:0|[1-9]\\d*)$","type":"integer","format":"int32","default":1}
+ - pageSize (in: path, required: true) schema: {"pattern":"^-?(?:0|[1-9]\\d*)$","type":"integer","format":"int32","default":100}
+Responses:
+ [200] application/json; version=1 -> {"type":"array","items":{"$ref":"#/components/schemas/Evidence"}}
+ [401] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+GET /v1/api/evidence/{evidenceId}/securelink [Tag: Grants]
+OperationId: getevidenceurl
+Description: Gets a secure download URL of the evidence file, URL expires (default: 15min)
+Parameters:
+ - evidenceId (in: path, required: true) schema: {"type":"string"}
+Responses:
+ [200] application/json; version=1 -> {"type":"string"}
+ [401] (No content)
+ [403] (No content)
+ [404] application/json; version=1 -> {"type":"string"}
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+GET /v1/api/evidence/indicators/{indicatorId} [Tag: Evidence & M&E]
+OperationId: getindicator
+Description: Get a specific indicator.
+Parameters:
+ - indicatorId (in: path, required: true) schema: {"type":"string"}
+Responses:
+ [200] application/json; version=1 -> {"$ref":"#/components/schemas/Indicator"}
+ [401] (No content)
+ [403] (No content)
+ [404] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+GET /v1/api/evidence/indicators/{page}/{pageSize} [Tag: Evidence & M&E]
+OperationId: getindicators
+Description: Get a list of indicators
+Parameters:
+ - page (in: path, required: true) schema: {"pattern":"^-?(?:0|[1-9]\\d*)$","type":"integer","format":"int32","default":1}
+ - pageSize (in: path, required: true) schema: {"pattern":"^-?(?:0|[1-9]\\d*)$","type":"integer","format":"int32","default":100}
+Responses:
+ [200] application/json; version=1 -> {"type":"array","items":{"$ref":"#/components/schemas/Indicator"}}
+ [401] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+GET /v1/api/evidence/milestone/{milestoneId} [Tag: Evidence & M&E]
+OperationId: getmilestoneevidence
+Description: Get a list of all uploaded evidence metadata for a specific mliestone
+Parameters:
+ - milestoneId (in: path, required: true) schema: {"type":"string"}
+Responses:
+ [200] application/json; version=1 -> {"type":"array","items":{"$ref":"#/components/schemas/Evidence"}}
+ [401] (No content)
+ [403] (No content)
+ [404] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+GET /v1/api/evidence/{projectId} [Tag: Evidence & M&E]
+OperationId: getprojectevidence
+Description: Get a list of all uploaded evidence metadata for a specific project
+Parameters:
+ - projectId (in: path, required: true) schema: {"type":"string"}
+Responses:
+ [200] application/json; version=1 -> {"type":"array","items":{"$ref":"#/components/schemas/Evidence"}}
+ [401] (No content)
+ [403] (No content)
+ [404] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+POST /v1/api/evidence/indicators/{indicatorId}/actuals/{actualAmount} [Tag: Evidence & M&E]
+OperationId: recordindicatoramount
+Description: Record actual values for indicators (M08)
+Parameters:
+ - indicatorId (in: path, required: true) schema: {"type":"string"}
+ - actualAmount (in: path, required: true) schema: {"pattern":"^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$","type":"number","format":"double"}
+Responses:
+ [200] (No content)
+ [400] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+ [401] (No content)
+ [403] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+PUT /v1/api/evidence [Tag: Evidence & M&E]
+OperationId: updateevidence
+Description: Updates a specific evidence record metadata
+Request Body: required=true
+ Content-Type: application/json; version=1 -> Schema: {"$ref":"#/components/schemas/UpdateEvidenceRequest"}
+Responses:
+ [200] (No content)
+ [400] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+ [401] (No content)
+ [403] (No content)
+ [404] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+GET /v1/api/communications/invites/applicant/{applicantUserId} [Tag: Communications]
+OperationId: getapplicantinvite
+Description: Gets an applicant's invite
+Parameters:
+ - applicantUserId (in: path, required: true) schema: {"type":"string","format":"uuid"}
+Responses:
+ [200] application/json; version=1 -> {"$ref":"#/components/schemas/Invite"}
+ [401] (No content)
+ [404] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+GET /v1/api/communications/invites/{applicantUserId} [Tag: Communications]
+OperationId: getapplicantinvites
+Description: Gets an applicant's invite list
+Parameters:
+ - applicantUserId (in: path, required: true) schema: {"type":"string","format":"uuid"}
+Responses:
+ [200] application/json; version=1 -> {"type":"array","items":{"$ref":"#/components/schemas/Invite"}}
+ [401] (No content)
+ [404] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+GET /v1/api/communications/invites/{page}/{pageSize} [Tag: Communications]
+OperationId: getinvites
+Description: Get a list of sent invitations
+Parameters:
+ - page (in: path, required: true) schema: {"pattern":"^-?(?:0|[1-9]\\d*)$","type":"integer","format":"int32","default":1}
+ - pageSize (in: path, required: true) schema: {"pattern":"^-?(?:0|[1-9]\\d*)$","type":"integer","format":"int32","default":100}
+Responses:
+ [200] application/json; version=1 -> {"type":"array","items":{"$ref":"#/components/schemas/Invite"}}
+ [401] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+
+------------------------------------------------------------
+POST /v1/api/communications/invite [Tag: Communications]
+OperationId: sendinvite
+Description: Trigger an invite to an organization or candidate (specifying NotificationPlatform)
+Request Body: required=true
+ Content-Type: application/json; version=1 -> Schema: {"$ref":"#/components/schemas/InviteRequest"}
+Responses:
+ [200] application/json; version=1 -> {"$ref":"#/components/schemas/Invite"}
+ [400] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
+ [401] (No content)
+ [500] application/json; version=1 -> {"$ref":"#/components/schemas/ProblemDetails"}
diff --git a/PostFundManagement.ConsoleUI/package-lock.json b/PostFundManagement.ConsoleUI/package-lock.json
new file mode 100644
index 0000000..c6aa72f
--- /dev/null
+++ b/PostFundManagement.ConsoleUI/package-lock.json
@@ -0,0 +1,2882 @@
+{
+ "name": "react-example",
+ "version": "0.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "react-example",
+ "version": "0.0.0",
+ "dependencies": {
+ "@google/genai": "^2.4.0",
+ "@hookform/resolvers": "^5.9.1",
+ "@tailwindcss/vite": "^4.1.14",
+ "@tanstack/react-query": "^5.102.8",
+ "@vitejs/plugin-basic-ssl": "^2.3.0",
+ "@vitejs/plugin-react": "^5.0.4",
+ "axios": "^1.20.0",
+ "dotenv": "^17.2.3",
+ "express": "^4.21.2",
+ "hashids": "^2.3.0",
+ "lucide-react": "^0.546.0",
+ "motion": "^12.23.24",
+ "oidc-client-ts": "^3.5.0",
+ "react": "^19.0.1",
+ "react-dom": "^19.0.1",
+ "react-hook-form": "^7.87.0",
+ "react-router-dom": "^7.18.3",
+ "tailwindcss": "^4.1.14",
+ "vite": "^6.2.3",
+ "zod": "^3.25.76"
+ },
+ "devDependencies": {
+ "@types/express": "^4.17.21",
+ "@types/node": "^22.14.0",
+ "autoprefixer": "^10.4.21",
+ "esbuild": "^0.25.0",
+ "tsx": "^4.21.0",
+ "typescript": "~5.8.2"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.29.7",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.29.7",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.29.7",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.29.7",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.7",
+ "@babel/generator": "^7.29.7",
+ "@babel/helper-compilation-targets": "^7.29.7",
+ "@babel/helper-module-transforms": "^7.29.7",
+ "@babel/helpers": "^7.29.7",
+ "@babel/parser": "^7.29.7",
+ "@babel/template": "^7.29.7",
+ "@babel/traverse": "^7.29.7",
+ "@babel/types": "^7.29.7",
+ "@jridgewell/remapping": "^2.3.5",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/core/node_modules/debug": {
+ "version": "4.4.3",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@babel/core/node_modules/debug/node_modules/ms": {
+ "version": "2.1.3",
+ "license": "MIT"
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.29.8",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.29.8",
+ "@babel/types": "^7.29.8",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.29.7",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.29.7",
+ "@babel/helper-validator-option": "^7.29.7",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "7.29.7",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.29.7",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.29.7",
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.29.7",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.29.7",
+ "@babel/helper-validator-identifier": "^7.29.7",
+ "@babel/traverse": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.29.7",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.29.7",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.29.7",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.29.7",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.29.7",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.29.7",
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.29.8",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.29.8"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-self": {
+ "version": "7.29.7",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-source": {
+ "version": "7.29.7",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.29.7",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.7",
+ "@babel/parser": "^7.29.7",
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.29.8",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.7",
+ "@babel/generator": "^7.29.8",
+ "@babel/helper-globals": "^7.29.7",
+ "@babel/parser": "^7.29.8",
+ "@babel/template": "^7.29.7",
+ "@babel/types": "^7.29.8",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse/node_modules/debug": {
+ "version": "4.4.3",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@babel/traverse/node_modules/debug/node_modules/ms": {
+ "version": "2.1.3",
+ "license": "MIT"
+ },
+ "node_modules/@babel/types": {
+ "version": "7.29.8",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.29.7",
+ "@babel/helper-validator-identifier": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.25.12",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@google/genai": {
+ "version": "2.21.0",
+ "hasInstallScript": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "google-auth-library": "^10.3.0",
+ "p-retry": "^4.6.2",
+ "protobufjs": "^7.5.4",
+ "ws": "^8.18.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "@modelcontextprotocol/sdk": "^1.25.2"
+ },
+ "peerDependenciesMeta": {
+ "@modelcontextprotocol/sdk": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@hookform/resolvers": {
+ "version": "5.9.1",
+ "license": "MIT",
+ "dependencies": {
+ "@standard-schema/utils": "^0.3.0"
+ },
+ "peerDependencies": {
+ "@sinclair/typebox": ">=0.25.24",
+ "@standard-schema/spec": "^1.0.0",
+ "@typeschema/main": ">=0.13.7",
+ "@vinejs/vine": "^2.0.0 || ^3.0.0 || ^4.0.0",
+ "ajv": "^8.12.0",
+ "ajv-errors": "^3.0.0",
+ "ajv-formats": "^2.1.1",
+ "arktype": "^2.0.0",
+ "ata-validator": "^1.2.0",
+ "class-transformer": ">=0.4.0",
+ "class-validator": ">=0.12.0",
+ "computed-types": "^1.0.0",
+ "effect": "^3.10.3",
+ "fluentvalidation-ts": "^3.0.0",
+ "fp-ts": "^2.7.0",
+ "io-ts": "^2.0.0",
+ "joi": "^17.0.0 || ^18.0.0",
+ "nope-validator": ">=0.12.0",
+ "react-hook-form": "^7.55.0",
+ "superstruct": ">=0.12.0",
+ "typanion": "^3.3.2",
+ "valibot": ">=0.31.0 || ^1.0.0-beta.4 || ^1.0.0-rc",
+ "vest": ">=6.0.0",
+ "yup": "^1.0.0",
+ "zod": "^3.25.0 || ^4.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@sinclair/typebox": {
+ "optional": true
+ },
+ "@standard-schema/spec": {
+ "optional": true
+ },
+ "@typeschema/main": {
+ "optional": true
+ },
+ "@vinejs/vine": {
+ "optional": true
+ },
+ "ajv": {
+ "optional": true
+ },
+ "ajv-errors": {
+ "optional": true
+ },
+ "ajv-formats": {
+ "optional": true
+ },
+ "arktype": {
+ "optional": true
+ },
+ "ata-validator": {
+ "optional": true
+ },
+ "class-transformer": {
+ "optional": true
+ },
+ "class-validator": {
+ "optional": true
+ },
+ "computed-types": {
+ "optional": true
+ },
+ "effect": {
+ "optional": true
+ },
+ "fluentvalidation-ts": {
+ "optional": true
+ },
+ "fp-ts": {
+ "optional": true
+ },
+ "io-ts": {
+ "optional": true
+ },
+ "joi": {
+ "optional": true
+ },
+ "nope-validator": {
+ "optional": true
+ },
+ "superstruct": {
+ "optional": true
+ },
+ "typanion": {
+ "optional": true
+ },
+ "valibot": {
+ "optional": true
+ },
+ "vest": {
+ "optional": true
+ },
+ "yup": {
+ "optional": true
+ },
+ "zod": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.6.0",
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@napi-rs/lzma-linux-x64-gnu": {
+ "version": "1.5.1",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^22.20 || ^24.12 || >=25"
+ }
+ },
+ "node_modules/@protobufjs/aspromise": {
+ "version": "1.1.2",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/base64": {
+ "version": "1.1.2",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/codegen": {
+ "version": "2.0.5",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/eventemitter": {
+ "version": "1.1.1",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/fetch": {
+ "version": "1.1.1",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@protobufjs/aspromise": "^1.1.1"
+ }
+ },
+ "node_modules/@protobufjs/float": {
+ "version": "1.0.2",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/path": {
+ "version": "1.1.2",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/pool": {
+ "version": "1.1.0",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/utf8": {
+ "version": "1.1.2",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.0-rc.3",
+ "license": "MIT"
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.63.1",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.63.1",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@standard-schema/utils": {
+ "version": "0.3.0",
+ "license": "MIT"
+ },
+ "node_modules/@tailwindcss/node": {
+ "version": "4.3.3",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/remapping": "^2.3.5",
+ "enhanced-resolve": "^5.24.1",
+ "jiti": "^2.7.0",
+ "lightningcss": "1.32.0",
+ "magic-string": "^0.30.21",
+ "source-map-js": "^1.2.1",
+ "tailwindcss": "4.3.3"
+ }
+ },
+ "node_modules/@tailwindcss/oxide": {
+ "version": "4.3.3",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 20"
+ },
+ "optionalDependencies": {
+ "@tailwindcss/oxide-android-arm64": "4.3.3",
+ "@tailwindcss/oxide-darwin-arm64": "4.3.3",
+ "@tailwindcss/oxide-darwin-x64": "4.3.3",
+ "@tailwindcss/oxide-freebsd-x64": "4.3.3",
+ "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.3",
+ "@tailwindcss/oxide-linux-arm64-gnu": "4.3.3",
+ "@tailwindcss/oxide-linux-arm64-musl": "4.3.3",
+ "@tailwindcss/oxide-linux-x64-gnu": "4.3.3",
+ "@tailwindcss/oxide-linux-x64-musl": "4.3.3",
+ "@tailwindcss/oxide-wasm32-wasi": "4.3.3",
+ "@tailwindcss/oxide-win32-arm64-msvc": "4.3.3",
+ "@tailwindcss/oxide-win32-x64-msvc": "4.3.3"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-gnu": {
+ "version": "4.3.3",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-musl": {
+ "version": "4.3.3",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/vite": {
+ "version": "4.3.3",
+ "license": "MIT",
+ "dependencies": {
+ "@tailwindcss/node": "4.3.3",
+ "@tailwindcss/oxide": "4.3.3",
+ "tailwindcss": "4.3.3"
+ },
+ "peerDependencies": {
+ "vite": "^5.2.0 || ^6 || ^7 || ^8"
+ }
+ },
+ "node_modules/@tanstack/query-core": {
+ "version": "5.102.8",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
+ "node_modules/@tanstack/react-query": {
+ "version": "5.102.8",
+ "license": "MIT",
+ "dependencies": {
+ "@tanstack/query-core": "5.102.8"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "react": "^18 || ^19"
+ }
+ },
+ "node_modules/@types/babel__core": {
+ "version": "7.20.5",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
+ }
+ },
+ "node_modules/@types/babel__generator": {
+ "version": "7.27.0",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__template": {
+ "version": "7.4.4",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__traverse": {
+ "version": "7.28.0",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.2"
+ }
+ },
+ "node_modules/@types/body-parser": {
+ "version": "1.19.6",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/connect": "*",
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/body-parser/node_modules/@types/node": {
+ "version": "26.4.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~8.3.0"
+ }
+ },
+ "node_modules/@types/body-parser/node_modules/@types/node/node_modules/undici-types": {
+ "version": "8.3.0",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/connect": {
+ "version": "3.4.38",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/connect/node_modules/@types/node": {
+ "version": "26.4.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~8.3.0"
+ }
+ },
+ "node_modules/@types/connect/node_modules/@types/node/node_modules/undici-types": {
+ "version": "8.3.0",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.9",
+ "license": "MIT"
+ },
+ "node_modules/@types/express": {
+ "version": "4.17.25",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/body-parser": "*",
+ "@types/express-serve-static-core": "^4.17.33",
+ "@types/qs": "*",
+ "@types/serve-static": "^1"
+ }
+ },
+ "node_modules/@types/express-serve-static-core": {
+ "version": "4.19.9",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "@types/qs": "*",
+ "@types/range-parser": "*",
+ "@types/send": "*"
+ }
+ },
+ "node_modules/@types/express-serve-static-core/node_modules/@types/node": {
+ "version": "26.4.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~8.3.0"
+ }
+ },
+ "node_modules/@types/express-serve-static-core/node_modules/@types/node/node_modules/undici-types": {
+ "version": "8.3.0",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/http-errors": {
+ "version": "2.0.5",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/mime": {
+ "version": "1.3.5",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/node": {
+ "version": "22.20.1",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~6.21.0"
+ }
+ },
+ "node_modules/@types/qs": {
+ "version": "6.15.1",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/range-parser": {
+ "version": "1.2.7",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/retry": {
+ "version": "0.12.0",
+ "license": "MIT"
+ },
+ "node_modules/@types/send": {
+ "version": "1.2.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/send/node_modules/@types/node": {
+ "version": "26.4.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~8.3.0"
+ }
+ },
+ "node_modules/@types/send/node_modules/@types/node/node_modules/undici-types": {
+ "version": "8.3.0",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/serve-static": {
+ "version": "1.15.10",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/http-errors": "*",
+ "@types/node": "*",
+ "@types/send": "<1"
+ }
+ },
+ "node_modules/@types/serve-static/node_modules/@types/node": {
+ "version": "26.4.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~8.3.0"
+ }
+ },
+ "node_modules/@types/serve-static/node_modules/@types/node/node_modules/undici-types": {
+ "version": "8.3.0",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/serve-static/node_modules/@types/send": {
+ "version": "0.17.6",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/mime": "^1",
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@vitejs/plugin-basic-ssl": {
+ "version": "2.3.0",
+ "license": "MIT",
+ "engines": {
+ "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "5.2.0",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.29.0",
+ "@babel/plugin-transform-react-jsx-self": "^7.27.1",
+ "@babel/plugin-transform-react-jsx-source": "^7.27.1",
+ "@rolldown/pluginutils": "1.0.0-rc.3",
+ "@types/babel__core": "^7.20.5",
+ "react-refresh": "^0.18.0"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "peerDependencies": {
+ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/accepts": {
+ "version": "1.3.8",
+ "license": "MIT",
+ "dependencies": {
+ "mime-types": "~2.1.34",
+ "negotiator": "0.6.3"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/agent-base": {
+ "version": "6.0.2",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6.0.0"
+ }
+ },
+ "node_modules/agent-base/node_modules/debug": {
+ "version": "4.4.3",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/agent-base/node_modules/debug/node_modules/ms": {
+ "version": "2.1.3",
+ "license": "MIT"
+ },
+ "node_modules/array-flatten": {
+ "version": "1.1.1",
+ "license": "MIT"
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "license": "MIT"
+ },
+ "node_modules/autoprefixer": {
+ "version": "10.5.5",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/autoprefixer"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "browserslist": "^4.28.9",
+ "caniuse-lite": "^1.0.30001810",
+ "fraction.js": "^5.3.4",
+ "picocolors": "^1.1.1",
+ "postcss-value-parser": "^4.2.0"
+ },
+ "bin": {
+ "autoprefixer": "bin/autoprefixer"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ },
+ "peerDependencies": {
+ "postcss": "^8.1.0"
+ }
+ },
+ "node_modules/axios": {
+ "version": "1.20.0",
+ "license": "MIT",
+ "dependencies": {
+ "follow-redirects": "^1.16.0",
+ "form-data": "^4.0.6",
+ "https-proxy-agent": "^5.0.1",
+ "proxy-from-env": "^2.1.0"
+ }
+ },
+ "node_modules/base64-js": {
+ "version": "1.5.1",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.11.21",
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.cjs"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/bignumber.js": {
+ "version": "9.3.1",
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/body-parser": {
+ "version": "1.20.6",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "~3.1.2",
+ "content-type": "~1.0.5",
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "~1.2.0",
+ "http-errors": "~2.0.1",
+ "iconv-lite": "~0.4.24",
+ "on-finished": "~2.4.1",
+ "qs": "~6.15.1",
+ "raw-body": "~2.5.3",
+ "type-is": "~1.6.18",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.28.9",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "baseline-browser-mapping": "^2.11.20",
+ "caniuse-lite": "^1.0.30001810",
+ "electron-to-chromium": "^1.5.420",
+ "node-releases": "^2.0.54",
+ "update-browserslist-db": "^1.3.2"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/buffer-equal-constant-time": {
+ "version": "1.0.1",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/bytes": {
+ "version": "3.1.2",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/call-bound": {
+ "version": "1.0.4",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "get-intrinsic": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001810",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "license": "MIT",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/content-disposition": {
+ "version": "0.5.4",
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "5.2.1"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/content-type": {
+ "version": "1.0.5",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "license": "MIT"
+ },
+ "node_modules/cookie": {
+ "version": "0.7.2",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie-signature": {
+ "version": "1.0.7",
+ "license": "MIT"
+ },
+ "node_modules/data-uri-to-buffer": {
+ "version": "4.0.1",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 12"
+ }
+ },
+ "node_modules/debug": {
+ "version": "2.6.9",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/depd": {
+ "version": "2.0.0",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/destroy": {
+ "version": "1.2.0",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/detect-libc": {
+ "version": "2.1.2",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/dotenv": {
+ "version": "17.4.2",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://dotenvx.com"
+ }
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/ecdsa-sig-formatter": {
+ "version": "1.0.11",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/ee-first": {
+ "version": "1.1.1",
+ "license": "MIT"
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.422",
+ "license": "ISC"
+ },
+ "node_modules/encodeurl": {
+ "version": "2.0.0",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/enhanced-resolve": {
+ "version": "5.24.5",
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.4",
+ "tapable": "^2.3.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.2",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-set-tostringtag": {
+ "version": "2.1.0",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.25.12",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.25.12",
+ "@esbuild/android-arm": "0.25.12",
+ "@esbuild/android-arm64": "0.25.12",
+ "@esbuild/android-x64": "0.25.12",
+ "@esbuild/darwin-arm64": "0.25.12",
+ "@esbuild/darwin-x64": "0.25.12",
+ "@esbuild/freebsd-arm64": "0.25.12",
+ "@esbuild/freebsd-x64": "0.25.12",
+ "@esbuild/linux-arm": "0.25.12",
+ "@esbuild/linux-arm64": "0.25.12",
+ "@esbuild/linux-ia32": "0.25.12",
+ "@esbuild/linux-loong64": "0.25.12",
+ "@esbuild/linux-mips64el": "0.25.12",
+ "@esbuild/linux-ppc64": "0.25.12",
+ "@esbuild/linux-riscv64": "0.25.12",
+ "@esbuild/linux-s390x": "0.25.12",
+ "@esbuild/linux-x64": "0.25.12",
+ "@esbuild/netbsd-arm64": "0.25.12",
+ "@esbuild/netbsd-x64": "0.25.12",
+ "@esbuild/openbsd-arm64": "0.25.12",
+ "@esbuild/openbsd-x64": "0.25.12",
+ "@esbuild/openharmony-arm64": "0.25.12",
+ "@esbuild/sunos-x64": "0.25.12",
+ "@esbuild/win32-arm64": "0.25.12",
+ "@esbuild/win32-ia32": "0.25.12",
+ "@esbuild/win32-x64": "0.25.12"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "license": "MIT"
+ },
+ "node_modules/etag": {
+ "version": "1.8.1",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/express": {
+ "version": "4.22.2",
+ "license": "MIT",
+ "dependencies": {
+ "accepts": "~1.3.8",
+ "array-flatten": "1.1.1",
+ "body-parser": "~1.20.5",
+ "content-disposition": "~0.5.4",
+ "content-type": "~1.0.4",
+ "cookie": "~0.7.1",
+ "cookie-signature": "~1.0.6",
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "finalhandler": "~1.3.1",
+ "fresh": "~0.5.2",
+ "http-errors": "~2.0.0",
+ "merge-descriptors": "1.0.3",
+ "methods": "~1.1.2",
+ "on-finished": "~2.4.1",
+ "parseurl": "~1.3.3",
+ "path-to-regexp": "~0.1.12",
+ "proxy-addr": "~2.0.7",
+ "qs": "~6.15.1",
+ "range-parser": "~1.2.1",
+ "safe-buffer": "5.2.1",
+ "send": "~0.19.0",
+ "serve-static": "~1.16.2",
+ "setprototypeof": "1.2.0",
+ "statuses": "~2.0.1",
+ "type-is": "~1.6.18",
+ "utils-merge": "1.0.1",
+ "vary": "~1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/extend": {
+ "version": "3.0.2",
+ "license": "MIT"
+ },
+ "node_modules/fdir": {
+ "version": "6.5.0",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/fetch-blob": {
+ "version": "3.2.0",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/jimmywarting"
+ },
+ {
+ "type": "paypal",
+ "url": "https://paypal.me/jimmywarting"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "node-domexception": "^1.0.0",
+ "web-streams-polyfill": "^3.0.3"
+ },
+ "engines": {
+ "node": "^12.20 || >= 14.13"
+ }
+ },
+ "node_modules/finalhandler": {
+ "version": "1.3.2",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "2.6.9",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "on-finished": "~2.4.1",
+ "parseurl": "~1.3.3",
+ "statuses": "~2.0.2",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/follow-redirects": {
+ "version": "1.16.0",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.6",
+ "license": "MIT",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "hasown": "^2.0.4",
+ "mime-types": "^2.1.35"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/formdata-polyfill": {
+ "version": "4.0.10",
+ "license": "MIT",
+ "dependencies": {
+ "fetch-blob": "^3.1.2"
+ },
+ "engines": {
+ "node": ">=12.20.0"
+ }
+ },
+ "node_modules/forwarded": {
+ "version": "0.2.0",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fraction.js": {
+ "version": "5.3.4",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/rawify"
+ }
+ },
+ "node_modules/framer-motion": {
+ "version": "12.43.0",
+ "license": "MIT",
+ "dependencies": {
+ "motion-dom": "^12.43.0",
+ "motion-utils": "^12.39.0",
+ "tslib": "^2.4.0"
+ },
+ "peerDependencies": {
+ "@emotion/is-prop-valid": "*",
+ "react": "^18.0.0 || ^19.0.0",
+ "react-dom": "^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@emotion/is-prop-valid": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/fresh": {
+ "version": "0.5.2",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/gaxios": {
+ "version": "7.3.1",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "extend": "^3.0.2",
+ "https-proxy-agent": "^7.0.1",
+ "node-fetch": "^3.3.2"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/gaxios/node_modules/https-proxy-agent": {
+ "version": "7.0.6",
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.2",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/gaxios/node_modules/https-proxy-agent/node_modules/agent-base": {
+ "version": "7.1.4",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/gaxios/node_modules/https-proxy-agent/node_modules/debug": {
+ "version": "4.4.3",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/gaxios/node_modules/https-proxy-agent/node_modules/debug/node_modules/ms": {
+ "version": "2.1.3",
+ "license": "MIT"
+ },
+ "node_modules/gcp-metadata": {
+ "version": "8.1.2",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "gaxios": "^7.0.0",
+ "google-logging-utils": "^1.0.0",
+ "json-bigint": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/google-auth-library": {
+ "version": "10.9.1",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "base64-js": "^1.3.0",
+ "ecdsa-sig-formatter": "^1.0.11",
+ "gaxios": "^7.1.4",
+ "gcp-metadata": "8.1.2",
+ "google-logging-utils": "1.1.3",
+ "jws": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/google-logging-utils": {
+ "version": "1.1.3",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "license": "ISC"
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-tostringtag": {
+ "version": "1.0.2",
+ "license": "MIT",
+ "dependencies": {
+ "has-symbols": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hashids": {
+ "version": "2.3.0",
+ "license": "MIT"
+ },
+ "node_modules/hasown": {
+ "version": "2.0.4",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/http-errors": {
+ "version": "2.0.1",
+ "license": "MIT",
+ "dependencies": {
+ "depd": "~2.0.0",
+ "inherits": "~2.0.4",
+ "setprototypeof": "~1.2.0",
+ "statuses": "~2.0.2",
+ "toidentifier": "~1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/https-proxy-agent": {
+ "version": "5.0.1",
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "6",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/https-proxy-agent/node_modules/debug": {
+ "version": "4.4.3",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/https-proxy-agent/node_modules/debug/node_modules/ms": {
+ "version": "2.1.3",
+ "license": "MIT"
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.4.24",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "license": "ISC"
+ },
+ "node_modules/ipaddr.js": {
+ "version": "1.9.1",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/jiti": {
+ "version": "2.7.0",
+ "license": "MIT",
+ "bin": {
+ "jiti": "lib/jiti-cli.mjs"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "license": "MIT"
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json-bigint": {
+ "version": "1.0.0",
+ "license": "MIT",
+ "dependencies": {
+ "bignumber.js": "^9.0.0"
+ }
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/jwa": {
+ "version": "2.0.1",
+ "license": "MIT",
+ "dependencies": {
+ "buffer-equal-constant-time": "^1.0.1",
+ "ecdsa-sig-formatter": "1.0.11",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/jws": {
+ "version": "4.0.1",
+ "license": "MIT",
+ "dependencies": {
+ "jwa": "^2.0.1",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/jwt-decode": {
+ "version": "4.0.0",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/lightningcss": {
+ "version": "1.32.0",
+ "license": "MPL-2.0",
+ "dependencies": {
+ "detect-libc": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "lightningcss-android-arm64": "1.32.0",
+ "lightningcss-darwin-arm64": "1.32.0",
+ "lightningcss-darwin-x64": "1.32.0",
+ "lightningcss-freebsd-x64": "1.32.0",
+ "lightningcss-linux-arm-gnueabihf": "1.32.0",
+ "lightningcss-linux-arm64-gnu": "1.32.0",
+ "lightningcss-linux-arm64-musl": "1.32.0",
+ "lightningcss-linux-x64-gnu": "1.32.0",
+ "lightningcss-linux-x64-musl": "1.32.0",
+ "lightningcss-win32-arm64-msvc": "1.32.0",
+ "lightningcss-win32-x64-msvc": "1.32.0"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-gnu": {
+ "version": "1.32.0",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-musl": {
+ "version": "1.32.0",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/long": {
+ "version": "5.3.2",
+ "license": "Apache-2.0"
+ },
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/lucide-react": {
+ "version": "0.546.0",
+ "license": "ISC",
+ "peerDependencies": {
+ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.21",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/media-typer": {
+ "version": "0.3.0",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/merge-descriptors": {
+ "version": "1.0.3",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/methods": {
+ "version": "1.1.2",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime": {
+ "version": "1.6.0",
+ "license": "MIT",
+ "bin": {
+ "mime": "cli.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/motion": {
+ "version": "12.43.0",
+ "license": "MIT",
+ "dependencies": {
+ "framer-motion": "^12.43.0",
+ "tslib": "^2.4.0"
+ },
+ "peerDependencies": {
+ "@emotion/is-prop-valid": "*",
+ "react": "^18.0.0 || ^19.0.0",
+ "react-dom": "^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@emotion/is-prop-valid": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/motion-dom": {
+ "version": "12.43.0",
+ "license": "MIT",
+ "dependencies": {
+ "motion-utils": "^12.39.0"
+ }
+ },
+ "node_modules/motion-utils": {
+ "version": "12.39.0",
+ "license": "MIT"
+ },
+ "node_modules/ms": {
+ "version": "2.0.0",
+ "license": "MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.18",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/negotiator": {
+ "version": "0.6.3",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/node-domexception": {
+ "version": "1.0.0",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/jimmywarting"
+ },
+ {
+ "type": "github",
+ "url": "https://paypal.me/jimmywarting"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.5.0"
+ }
+ },
+ "node_modules/node-fetch": {
+ "version": "3.3.2",
+ "license": "MIT",
+ "dependencies": {
+ "data-uri-to-buffer": "^4.0.0",
+ "fetch-blob": "^3.1.4",
+ "formdata-polyfill": "^4.0.10"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/node-fetch"
+ }
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.54",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/object-inspect": {
+ "version": "1.13.4",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/oidc-client-ts": {
+ "version": "3.5.0",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "jwt-decode": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/on-finished": {
+ "version": "2.4.1",
+ "license": "MIT",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/p-retry": {
+ "version": "4.6.2",
+ "license": "MIT",
+ "dependencies": {
+ "@types/retry": "0.12.0",
+ "retry": "^0.13.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/parseurl": {
+ "version": "1.3.3",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/path-to-regexp": {
+ "version": "0.1.13",
+ "license": "MIT"
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.7",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.28",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.18",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/postcss-value-parser": {
+ "version": "4.2.0",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/protobufjs": {
+ "version": "7.6.6",
+ "hasInstallScript": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@protobufjs/aspromise": "^1.1.2",
+ "@protobufjs/base64": "^1.1.2",
+ "@protobufjs/codegen": "^2.0.5",
+ "@protobufjs/eventemitter": "^1.1.1",
+ "@protobufjs/fetch": "^1.1.1",
+ "@protobufjs/float": "^1.0.2",
+ "@protobufjs/path": "^1.1.2",
+ "@protobufjs/pool": "^1.1.0",
+ "@protobufjs/utf8": "^1.1.1",
+ "@types/node": ">=13.7.0",
+ "long": "^5.3.2"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/protobufjs/node_modules/@types/node": {
+ "version": "26.4.1",
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~8.3.0"
+ }
+ },
+ "node_modules/protobufjs/node_modules/@types/node/node_modules/undici-types": {
+ "version": "8.3.0",
+ "license": "MIT"
+ },
+ "node_modules/proxy-addr": {
+ "version": "2.0.7",
+ "license": "MIT",
+ "dependencies": {
+ "forwarded": "0.2.0",
+ "ipaddr.js": "1.9.1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/proxy-from-env": {
+ "version": "2.1.0",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/qs": {
+ "version": "6.15.3",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "es-define-property": "^1.0.1",
+ "side-channel": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/range-parser": {
+ "version": "1.2.1",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/raw-body": {
+ "version": "2.5.3",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "~3.1.2",
+ "http-errors": "~2.0.1",
+ "iconv-lite": "~0.4.24",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/react": {
+ "version": "19.2.8",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "19.2.8",
+ "license": "MIT",
+ "dependencies": {
+ "scheduler": "^0.27.0"
+ },
+ "peerDependencies": {
+ "react": "^19.2.8"
+ }
+ },
+ "node_modules/react-hook-form": {
+ "version": "7.87.0",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/react-hook-form"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17 || ^18 || ^19"
+ }
+ },
+ "node_modules/react-refresh": {
+ "version": "0.18.0",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-router": {
+ "version": "7.18.3",
+ "license": "MIT",
+ "dependencies": {
+ "cookie": "^1.0.1",
+ "set-cookie-parser": "^2.6.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-router-dom": {
+ "version": "7.18.3",
+ "license": "MIT",
+ "dependencies": {
+ "react-router": "7.18.3"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ }
+ },
+ "node_modules/react-router/node_modules/cookie": {
+ "version": "1.1.1",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/retry": {
+ "version": "0.13.1",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.63.1",
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.9"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@napi-rs/lzma-linux-x64-gnu": "1.5.1",
+ "@rollup/rollup-android-arm-eabi": "4.63.1",
+ "@rollup/rollup-android-arm64": "4.63.1",
+ "@rollup/rollup-darwin-arm64": "4.63.1",
+ "@rollup/rollup-darwin-x64": "4.63.1",
+ "@rollup/rollup-freebsd-arm64": "4.63.1",
+ "@rollup/rollup-freebsd-x64": "4.63.1",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.63.1",
+ "@rollup/rollup-linux-arm-musleabihf": "4.63.1",
+ "@rollup/rollup-linux-arm64-gnu": "4.63.1",
+ "@rollup/rollup-linux-arm64-musl": "4.63.1",
+ "@rollup/rollup-linux-loong64-gnu": "4.63.1",
+ "@rollup/rollup-linux-loong64-musl": "4.63.1",
+ "@rollup/rollup-linux-ppc64-gnu": "4.63.1",
+ "@rollup/rollup-linux-ppc64-musl": "4.63.1",
+ "@rollup/rollup-linux-riscv64-gnu": "4.63.1",
+ "@rollup/rollup-linux-riscv64-musl": "4.63.1",
+ "@rollup/rollup-linux-s390x-gnu": "4.63.1",
+ "@rollup/rollup-linux-x64-gnu": "4.63.1",
+ "@rollup/rollup-linux-x64-musl": "4.63.1",
+ "@rollup/rollup-openbsd-x64": "4.63.1",
+ "@rollup/rollup-openharmony-arm64": "4.63.1",
+ "@rollup/rollup-win32-arm64-msvc": "4.63.1",
+ "@rollup/rollup-win32-ia32-msvc": "4.63.1",
+ "@rollup/rollup-win32-x64-gnu": "4.63.1",
+ "@rollup/rollup-win32-x64-msvc": "4.63.1",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "license": "MIT"
+ },
+ "node_modules/scheduler": {
+ "version": "0.27.0",
+ "license": "MIT"
+ },
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/send": {
+ "version": "0.19.2",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "1.2.0",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "fresh": "~0.5.2",
+ "http-errors": "~2.0.1",
+ "mime": "1.6.0",
+ "ms": "2.1.3",
+ "on-finished": "~2.4.1",
+ "range-parser": "~1.2.1",
+ "statuses": "~2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/send/node_modules/ms": {
+ "version": "2.1.3",
+ "license": "MIT"
+ },
+ "node_modules/serve-static": {
+ "version": "1.16.3",
+ "license": "MIT",
+ "dependencies": {
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "parseurl": "~1.3.3",
+ "send": "~0.19.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/set-cookie-parser": {
+ "version": "2.7.2",
+ "license": "MIT"
+ },
+ "node_modules/setprototypeof": {
+ "version": "1.2.0",
+ "license": "ISC"
+ },
+ "node_modules/side-channel": {
+ "version": "1.1.1",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.4",
+ "side-channel-list": "^1.0.1",
+ "side-channel-map": "^1.0.1",
+ "side-channel-weakmap": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-list": {
+ "version": "1.0.1",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-map": {
+ "version": "1.0.1",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-weakmap": {
+ "version": "1.0.2",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3",
+ "side-channel-map": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/statuses": {
+ "version": "2.0.2",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/tailwindcss": {
+ "version": "4.3.3",
+ "license": "MIT"
+ },
+ "node_modules/tapable": {
+ "version": "2.3.3",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ }
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.17",
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.4"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/toidentifier": {
+ "version": "1.0.1",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "license": "0BSD"
+ },
+ "node_modules/tsx": {
+ "version": "4.23.13",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "~0.28.0"
+ },
+ "bin": {
+ "tsx": "dist/cli.mjs"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ }
+ },
+ "node_modules/tsx/node_modules/esbuild": {
+ "version": "0.28.2",
+ "devOptional": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.28.2",
+ "@esbuild/android-arm": "0.28.2",
+ "@esbuild/android-arm64": "0.28.2",
+ "@esbuild/android-x64": "0.28.2",
+ "@esbuild/darwin-arm64": "0.28.2",
+ "@esbuild/darwin-x64": "0.28.2",
+ "@esbuild/freebsd-arm64": "0.28.2",
+ "@esbuild/freebsd-x64": "0.28.2",
+ "@esbuild/linux-arm": "0.28.2",
+ "@esbuild/linux-arm64": "0.28.2",
+ "@esbuild/linux-ia32": "0.28.2",
+ "@esbuild/linux-loong64": "0.28.2",
+ "@esbuild/linux-mips64el": "0.28.2",
+ "@esbuild/linux-ppc64": "0.28.2",
+ "@esbuild/linux-riscv64": "0.28.2",
+ "@esbuild/linux-s390x": "0.28.2",
+ "@esbuild/linux-x64": "0.28.2",
+ "@esbuild/netbsd-arm64": "0.28.2",
+ "@esbuild/netbsd-x64": "0.28.2",
+ "@esbuild/openbsd-arm64": "0.28.2",
+ "@esbuild/openbsd-x64": "0.28.2",
+ "@esbuild/openharmony-arm64": "0.28.2",
+ "@esbuild/sunos-x64": "0.28.2",
+ "@esbuild/win32-arm64": "0.28.2",
+ "@esbuild/win32-ia32": "0.28.2",
+ "@esbuild/win32-x64": "0.28.2"
+ }
+ },
+ "node_modules/tsx/node_modules/esbuild/node_modules/@esbuild/linux-x64": {
+ "version": "0.28.2",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/type-is": {
+ "version": "1.6.18",
+ "license": "MIT",
+ "dependencies": {
+ "media-typer": "0.3.0",
+ "mime-types": "~2.1.24"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.8.3",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "6.21.0",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/unpipe": {
+ "version": "1.0.0",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.3.2",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/utils-merge": {
+ "version": "1.0.1",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/vary": {
+ "version": "1.1.2",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/vite": {
+ "version": "6.4.3",
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.25.0",
+ "fdir": "^6.4.4",
+ "picomatch": "^4.0.2",
+ "postcss": "^8.5.3",
+ "rollup": "^4.34.9",
+ "tinyglobby": "^0.2.13"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
+ "jiti": ">=1.21.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/web-streams-polyfill": {
+ "version": "3.3.3",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/ws": {
+ "version": "8.21.3",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "license": "ISC"
+ },
+ "node_modules/zod": {
+ "version": "3.25.76",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
+ "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ }
+ }
+}
diff --git a/PostFundManagement.ConsoleUI/package.json b/PostFundManagement.ConsoleUI/package.json
index bf986c9..000793a 100644
--- a/PostFundManagement.ConsoleUI/package.json
+++ b/PostFundManagement.ConsoleUI/package.json
@@ -5,6 +5,8 @@
"type": "module",
"scripts": {
"dev": "vite --port=3000 --host=0.0.0.0",
+ "dev:https": "HTTPS=true vite --port=3000 --host=0.0.0.0",
+ "dev:http": "HTTPS=false vite --port=3000 --host=0.0.0.0",
"build": "vite build",
"preview": "vite preview",
"clean": "rm -rf dist server.js",
@@ -15,6 +17,7 @@
"@hookform/resolvers": "^5.9.1",
"@tailwindcss/vite": "^4.1.14",
"@tanstack/react-query": "^5.102.8",
+ "@vitejs/plugin-basic-ssl": "^2.3.0",
"@vitejs/plugin-react": "^5.0.4",
"axios": "^1.20.0",
"dotenv": "^17.2.3",
@@ -29,14 +32,14 @@
"react-router-dom": "^7.18.3",
"tailwindcss": "^4.1.14",
"vite": "^6.2.3",
- "zod": "^4.5.4"
+ "zod": "^3.25.76"
},
"devDependencies": {
+ "@types/express": "^4.17.21",
"@types/node": "^22.14.0",
"autoprefixer": "^10.4.21",
"esbuild": "^0.25.0",
"tsx": "^4.21.0",
- "typescript": "~5.8.2",
- "@types/express": "^4.17.21"
+ "typescript": "~5.8.2"
}
}
diff --git a/PostFundManagement.ConsoleUI/schemas_summary.txt b/PostFundManagement.ConsoleUI/schemas_summary.txt
new file mode 100644
index 0000000..4f4f073
--- /dev/null
+++ b/PostFundManagement.ConsoleUI/schemas_summary.txt
@@ -0,0 +1,469 @@
+
+=== SCHEMA: Activity ===
+Required: []
+ - id: integer,string (format: int64)
+ - projectId: null,integer,string (format: int64)
+ - milestoneId: null,integer,string (format: int64)
+ - createdBy: string (format: uuid)
+ - updatedBy: null,string (format: uuid)
+ - approvedBy: null,string (format: uuid)
+ - createdAt: string (format: date-time)
+ - updatedAt: null,string (format: date-time)
+ - category: null,string (format: none)
+ - status: #/components/schemas/ApprovalStatus (format: none)
+ - amount: number,string (format: double)
+ - title: null,string (format: none)
+ - description: null,string (format: none)
+ - performedAt: null,string (format: date-time)
+
+=== SCHEMA: ActivityStatus ===
+Other: { type: 'integer' }
+
+=== SCHEMA: ApprovalStatus ===
+Other: { type: 'integer' }
+
+=== SCHEMA: Award ===
+Required: []
+ - id: integer,string (format: int64)
+ - organisationId: integer,string (format: int64)
+ - programmeId: integer,string (format: int64)
+ - projectId: null,integer,string (format: int64)
+ - createdBy: string (format: uuid)
+ - updatedBy: null,string (format: uuid)
+ - approvedBy: null,string (format: uuid)
+ - createdAt: string (format: date-time)
+ - updatedAt: null,string (format: date-time)
+ - status: #/components/schemas/ApprovalStatus (format: none)
+ - amount: number,string (format: double)
+
+=== SCHEMA: Budget ===
+Required: []
+ - id: integer,string (format: int64)
+ - projectId: integer,string (format: int64)
+ - createdBy: string (format: uuid)
+ - approvedBy: null,string (format: uuid)
+ - createdAt: string (format: date-time)
+ - updatedAt: null,string (format: date-time)
+ - name: null,string (format: none)
+ - amount: number,string (format: double)
+
+=== SCHEMA: Contract ===
+Required: []
+ - id: integer,string (format: int64)
+ - awardId: integer,string (format: int64)
+ - externalSignatureId: null,string (format: none)
+ - signedDocumentUrl: null,string (format: none)
+ - effectiveAt: null,string (format: date-time)
+ - expiresAt: null,string (format: date-time)
+ - totalValue: number,string (format: double)
+ - status: #/components/schemas/ContractStatus (format: none)
+ - createdBy: string (format: uuid)
+ - createdAt: string (format: date-time)
+
+=== SCHEMA: ContractStatus ===
+Other: { type: 'integer' }
+
+=== SCHEMA: CreateActivityRequest ===
+Required: [
+ 'projectId',
+ 'milestoneId',
+ 'category',
+ 'amount',
+ 'title',
+ 'description',
+ 'performedAt'
+]
+ - projectId: integer,string (format: int64)
+ - milestoneId: null,integer,string (format: int64)
+ - category: string (format: none)
+ - amount: number,string (format: double)
+ - title: string (format: none)
+ - description: string (format: none)
+ - performedAt: null,string (format: date-time)
+
+=== SCHEMA: CreateAwardRequest ===
+Required: [ 'organisationId', 'programmeId', 'amount' ]
+ - organisationId: integer,string (format: int64)
+ - programmeId: integer,string (format: int64)
+ - amount: number,string (format: double)
+
+=== SCHEMA: CreateBudgetRequest ===
+Required: [ 'projectId', 'name', 'amount' ]
+ - projectId: integer,string (format: int64)
+ - name: string (format: none)
+ - amount: number,string (format: double)
+
+=== SCHEMA: CreateContractRequest ===
+Required: [ 'awardId', 'totalValue', 'effectiveAt', 'expiresAt' ]
+ - awardId: integer,string (format: int64)
+ - totalValue: number,string (format: double)
+ - effectiveAt: string (format: date-time)
+ - expiresAt: string (format: date-time)
+
+=== SCHEMA: CreateDisbursementRequest ===
+Required: [ 'projectId', 'milestoneId', 'amount' ]
+ - projectId: integer,string (format: int64)
+ - milestoneId: null,integer,string (format: int64)
+ - amount: number,string (format: double)
+
+=== SCHEMA: CreateIndicatorRequest ===
+Required: [
+ 'projectId',
+ 'name',
+ 'unitOfMeasure',
+ 'baselineAmount',
+ 'targetAmount'
+]
+ - projectId: integer,string (format: int64)
+ - name: string (format: none)
+ - unitOfMeasure: #/components/schemas/UnitOfMeasure (format: none)
+ - baselineAmount: number,string (format: double)
+ - targetAmount: number,string (format: double)
+
+=== SCHEMA: CreateMilestoneRequest ===
+Required: [ 'projectId', 'dueAt', 'name', 'priority' ]
+ - projectId: integer,string (format: int64)
+ - dueAt: string (format: date-time)
+ - name: string (format: none)
+ - priority: #/components/schemas/Priority (format: none)
+
+=== SCHEMA: CreateOrganisationRequest ===
+Required: [ 'name', 'registrationNo', 'email' ]
+ - name: string (format: none)
+ - registrationNo: string (format: none)
+ - email: string (format: none)
+ - type: #/components/schemas/OrganisationType (format: none)
+
+=== SCHEMA: CreatePortfolioRequest ===
+Required: [ 'name', 'description', 'organisationId', 'ownerId' ]
+ - name: string (format: none)
+ - description: string (format: none)
+ - organisationId: integer,string (format: int64)
+ - ownerId: string (format: uuid)
+
+=== SCHEMA: CreateProgrammeRequest ===
+Required: [ 'portfolioId', 'name', 'ownerId' ]
+ - portfolioId: integer,string (format: int64)
+ - name: string (format: none)
+ - ownerId: string (format: uuid)
+
+=== SCHEMA: CreateProjectRequest ===
+Required: [ 'programmeId', 'name', 'description', 'sector', 'projectType' ]
+ - programmeId: integer,string (format: int64)
+ - name: string (format: none)
+ - description: string (format: none)
+ - sector: #/components/schemas/IndustrySector (format: none)
+ - projectType: #/components/schemas/ProjectType (format: none)
+
+=== SCHEMA: CreateRiskRequest ===
+Required: [ 'projectId', 'likelihood', 'impact', 'name', 'description' ]
+ - projectId: integer,string (format: int64)
+ - likelihood: #/components/schemas/RiskLikelihood (format: none)
+ - impact: #/components/schemas/RiskImpact (format: none)
+ - name: string (format: none)
+ - description: string (format: none)
+
+=== SCHEMA: Dashboard ===
+Required: [
+ 'portfolioValue',
+ 'activeAwards',
+ 'disbursed',
+ 'onTrackPercentage',
+ 'atRiskCount',
+ 'beneficiariesReached'
+]
+ - portfolioValue: number,string (format: double)
+ - activeAwards: integer,string (format: int32)
+ - disbursed: number,string (format: double)
+ - onTrackPercentage: number,string (format: double)
+ - atRiskCount: integer,string (format: int32)
+ - beneficiariesReached: integer,string (format: int32)
+
+=== SCHEMA: Disbursement ===
+Required: []
+ - id: integer,string (format: int64)
+ - projectId: integer,string (format: int64)
+ - milestoneId: null,integer,string (format: int64)
+ - createdBy: string (format: uuid)
+ - updatedBy: null,string (format: uuid)
+ - createdAt: string (format: date-time)
+ - updatedAt: null,string (format: date-time)
+ - amount: number,string (format: double)
+ - status: #/components/schemas/ApprovalStatus (format: none)
+
+=== SCHEMA: Evidence ===
+Required: []
+ - id: integer,string (format: int64)
+ - projectId: integer,string (format: int64)
+ - milestoneId: integer,string (format: int64)
+ - indicatorId: integer,string (format: int64)
+ - activityId: integer,string (format: int64)
+ - createdBy: string (format: uuid)
+ - updatedBy: null,string (format: uuid)
+ - createdAt: string (format: date-time)
+ - updatedAt: null,string (format: date-time)
+ - version: integer,string (format: int32)
+ - type: #/components/schemas/EvidenceType (format: none)
+ - documentUrl: null,string (format: none)
+ - status: #/components/schemas/ApprovalStatus (format: none)
+
+=== SCHEMA: EvidenceType ===
+Other: { type: 'integer' }
+
+=== SCHEMA: ExecuteContractRequest ===
+Required: [ 'contractId', 'externalSignatureId', 'signedDocumentUrl' ]
+ - contractId: integer,string (format: int64)
+ - externalSignatureId: string (format: none)
+ - signedDocumentUrl: string (format: none)
+
+=== SCHEMA: Indicator ===
+Required: []
+ - id: integer,string (format: int64)
+ - projectId: integer,string (format: int64)
+ - createdBy: string (format: uuid)
+ - createdAt: string (format: date-time)
+ - name: null,string (format: none)
+ - unitOfMeasure: any (format: none)
+ - baselineAmount: number,string (format: double)
+ - targetAmount: number,string (format: double)
+ - actualAmount: number,string (format: double)
+
+=== SCHEMA: IndustrySector ===
+Other: { type: 'integer' }
+
+=== SCHEMA: Instrument ===
+Required: []
+ - id: integer,string (format: int64)
+ - programmeId: integer,string (format: int64)
+ - code: null,string (format: none)
+ - name: null,string (format: none)
+ - description: null,string (format: none)
+ - status: #/components/schemas/ActivityStatus (format: none)
+ - createdBy: string (format: uuid)
+ - createdAt: string (format: date-time)
+
+=== SCHEMA: Invite ===
+Required: []
+ - id: integer,string (format: int64)
+ - organisationId: integer,string (format: int64)
+ - invitedOrganisationId: integer,string (format: int64)
+ - awardId: integer,string (format: int64)
+ - contractId: integer,string (format: int64)
+ - recipient: string (format: uuid)
+ - createdBy: string (format: uuid)
+ - updatedBy: null,string (format: uuid)
+ - createdAt: string (format: date-time)
+ - updatedAt: null,string (format: date-time)
+
+=== SCHEMA: InviteRequest ===
+Required: [
+ 'organisationId',
+ 'invitedOrganisationId',
+ 'awardId',
+ 'contractId',
+ 'recipient',
+ 'message',
+ 'platform'
+]
+ - organisationId: integer,string (format: int64)
+ - invitedOrganisationId: integer,string (format: int64)
+ - awardId: integer,string (format: int64)
+ - contractId: integer,string (format: int64)
+ - recipient: string (format: uuid)
+ - message: string (format: none)
+ - platform: #/components/schemas/NotificationPlatform (format: none)
+
+=== SCHEMA: Milestone ===
+Required: []
+ - id: integer,string (format: int64)
+ - projectId: integer,string (format: int64)
+ - createdBy: string (format: uuid)
+ - updatedBy: null,string (format: uuid)
+ - createdAt: string (format: date-time)
+ - updatedAt: null,string (format: date-time)
+ - dueAt: string (format: date-time)
+ - name: null,string (format: none)
+ - status: #/components/schemas/ApprovalStatus (format: none)
+ - priority: #/components/schemas/Priority (format: none)
+
+=== SCHEMA: NotificationPlatform ===
+Other: { type: 'integer' }
+
+=== SCHEMA: Organisation ===
+Required: []
+ - id: integer,string (format: int64)
+ - createdBy: string (format: uuid)
+ - createdAt: string (format: date-time)
+ - registrationNo: null,string (format: none)
+ - name: null,string (format: none)
+ - email: null,string (format: none)
+ - type: #/components/schemas/OrganisationType (format: none)
+ - status: #/components/schemas/ActivityStatus (format: none)
+
+=== SCHEMA: OrganisationType ===
+Other: { type: 'integer', default: 5 }
+
+=== SCHEMA: Portfolio ===
+Required: []
+ - id: integer,string (format: int64)
+ - organisationId: integer,string (format: int64)
+ - ownedBy: string (format: uuid)
+ - createdBy: string (format: uuid)
+ - createdAt: string (format: date-time)
+ - name: null,string (format: none)
+ - description: null,string (format: none)
+
+=== SCHEMA: Priority ===
+Other: { type: 'integer' }
+
+=== SCHEMA: ProblemDetails ===
+Required: []
+ - type: null,string (format: none)
+ - title: null,string (format: none)
+ - status: null,integer,string (format: int32)
+ - detail: null,string (format: none)
+ - instance: null,string (format: none)
+
+=== SCHEMA: Programme ===
+Required: []
+ - id: integer,string (format: int64)
+ - portfolioId: integer,string (format: int64)
+ - ownedBy: string (format: uuid)
+ - createdBy: string (format: uuid)
+ - updatedBy: null,string (format: uuid)
+ - createdAt: string (format: date-time)
+ - updatedAt: null,string (format: date-time)
+ - name: null,string (format: none)
+ - status: #/components/schemas/ApprovalStatus (format: none)
+
+=== SCHEMA: Project ===
+Required: []
+ - id: integer,string (format: int64)
+ - programmeId: integer,string (format: int64)
+ - createdBy: string (format: uuid)
+ - updatedBy: null,string (format: uuid)
+ - createdAt: string (format: date-time)
+ - updatedAt: null,string (format: date-time)
+ - startedAt: null,string (format: date-time)
+ - endedAt: null,string (format: date-time)
+ - sector: #/components/schemas/IndustrySector (format: none)
+ - projectType: #/components/schemas/ProjectType (format: none)
+ - name: null,string (format: none)
+ - description: null,string (format: none)
+ - status: #/components/schemas/ApprovalStatus (format: none)
+
+=== SCHEMA: ProjectType ===
+Other: { type: 'integer' }
+
+=== SCHEMA: Risk ===
+Required: []
+ - id: integer,string (format: int64)
+ - projectId: integer,string (format: int64)
+ - createdBy: string (format: uuid)
+ - createdAt: string (format: date-time)
+ - likelihood: #/components/schemas/RiskLikelihood (format: none)
+ - impact: #/components/schemas/RiskImpact (format: none)
+ - name: null,string (format: none)
+ - description: null,string (format: none)
+
+=== SCHEMA: RiskImpact ===
+Other: { type: 'integer' }
+
+=== SCHEMA: RiskLikelihood ===
+Other: { type: 'integer' }
+
+=== SCHEMA: UnitOfMeasure ===
+Other: { type: 'integer' }
+
+=== SCHEMA: UpdateContractRequest ===
+Required: [ 'contractId', 'status' ]
+ - contractId: integer,string (format: int64)
+ - status: #/components/schemas/ContractStatus (format: none)
+
+=== SCHEMA: UpdateDisbursementRequest ===
+Required: [ 'disbursementId', 'milestoneId', 'amount' ]
+ - disbursementId: integer,string (format: int64)
+ - milestoneId: null,integer,string (format: int64)
+ - amount: number,string (format: double)
+
+=== SCHEMA: UpdateEvidenceRequest ===
+Required: [ 'evidenceId', 'version', 'evidenceType', 'status' ]
+ - evidenceId: integer,string (format: int64)
+ - version: integer,string (format: int32)
+ - evidenceType: #/components/schemas/EvidenceType (format: none)
+ - status: #/components/schemas/ApprovalStatus (format: none)
+
+=== SCHEMA: UpdateMilestoneRequest ===
+Required: [ 'milestoneId', 'dueAt', 'name', 'priority', 'status' ]
+ - milestoneId: integer,string (format: int64)
+ - dueAt: string (format: date-time)
+ - name: string (format: none)
+ - priority: #/components/schemas/Priority (format: none)
+ - status: #/components/schemas/ApprovalStatus (format: none)
+
+=== SCHEMA: UpdateOrganisationRequest ===
+Required: [ 'organisationId', 'name', 'registrationNo', 'email', 'type' ]
+ - organisationId: integer,string (format: int64)
+ - name: string (format: none)
+ - registrationNo: string (format: none)
+ - email: string (format: none)
+ - type: #/components/schemas/OrganisationType (format: none)
+
+=== SCHEMA: UpdatePortfolioRequest ===
+Required: [ 'portfolioId', 'name', 'description', 'ownerId' ]
+ - portfolioId: integer,string (format: int64)
+ - name: string (format: none)
+ - description: string (format: none)
+ - ownerId: string (format: uuid)
+
+=== SCHEMA: UpdateProgrammeRequest ===
+Required: [ 'programmeId', 'name', 'ownerId', 'status' ]
+ - programmeId: integer,string (format: int64)
+ - name: string (format: none)
+ - ownerId: string (format: uuid)
+ - status: #/components/schemas/ApprovalStatus (format: none)
+
+=== SCHEMA: UpdateProjectRequest ===
+Required: [
+ 'projectId',
+ 'name',
+ 'description',
+ 'startedAt',
+ 'endedAt',
+ 'sector',
+ 'projectType'
+]
+ - projectId: integer,string (format: int64)
+ - name: string (format: none)
+ - description: string (format: none)
+ - startedAt: null,string (format: date-time)
+ - endedAt: null,string (format: date-time)
+ - sector: #/components/schemas/IndustrySector (format: none)
+ - projectType: #/components/schemas/ProjectType (format: none)
+
+=== SCHEMA: UpdateProjectStatusRequest ===
+Required: [ 'projectId', 'status' ]
+ - projectId: integer,string (format: int64)
+ - status: #/components/schemas/ApprovalStatus (format: none)
+
+=== SCHEMA: UpdateRiskRequest ===
+Required: [ 'riskId', 'likelihood', 'impact', 'name', 'description' ]
+ - riskId: integer,string (format: int64)
+ - likelihood: #/components/schemas/RiskLikelihood (format: none)
+ - impact: #/components/schemas/RiskImpact (format: none)
+ - name: string (format: none)
+ - description: string (format: none)
+
+=== SCHEMA: User ===
+Required: []
+ - id: string (format: uuid)
+ - lastLoginAt: null,string (format: date-time)
+ - email: null,string (format: none)
+ - status: #/components/schemas/ActivityStatus (format: none)
+
+=== SCHEMA: UserFilterRequest ===
+Required: []
+ - status: any (format: none)
+ - email: null,string (format: none)
+ - sortBy: null,string (format: none)
+ - sortDescending: boolean (format: none)
diff --git a/PostFundManagement.ConsoleUI/src/App.tsx b/PostFundManagement.ConsoleUI/src/App.tsx
index 0189855..084a573 100644
--- a/PostFundManagement.ConsoleUI/src/App.tsx
+++ b/PostFundManagement.ConsoleUI/src/App.tsx
@@ -1,17 +1,27 @@
-import React from 'react';
+import React, { Suspense, lazy } from 'react';
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
import { QueryClientProvider } from '@tanstack/react-query';
import { queryClient } from './lib/queryClient';
import { AuthProvider, ProtectedRoute } from './auth/AuthProvider';
import { ToastProvider } from './components/ui/Toast';
import { AppLayout } from './components/layout/AppLayout';
-import { LandingPage } from './pages/LandingPage';
-import { ExecutiveDashboard } from './pages/ExecutiveDashboard';
-import { GovernancePage } from './pages/GovernancePage';
-import { ProjectsPage } from './pages/ProjectsPage';
-import { GrantsPage } from './pages/GrantsPage';
-import { MonitoringPage } from './pages/MonitoringPage';
-import { SettingsPage } from './pages/SettingsPage';
+
+const LandingPage = lazy(() => import('./pages/LandingPage').then(m => ({ default: m.LandingPage })));
+const ExecutiveDashboard = lazy(() => import('./pages/ExecutiveDashboard').then(m => ({ default: m.ExecutiveDashboard })));
+const GovernancePage = lazy(() => import('./pages/GovernancePage').then(m => ({ default: m.GovernancePage })));
+const ProjectsPage = lazy(() => import('./pages/ProjectsPage').then(m => ({ default: m.ProjectsPage })));
+const GrantsPage = lazy(() => import('./pages/GrantsPage').then(m => ({ default: m.GrantsPage })));
+const MonitoringPage = lazy(() => import('./pages/MonitoringPage').then(m => ({ default: m.MonitoringPage })));
+const SettingsPage = lazy(() => import('./pages/SettingsPage').then(m => ({ default: m.SettingsPage })));
+
+const RouteLoading = () => (
+
+);
export default function App() {
return (
@@ -19,30 +29,32 @@ export default function App() {
-
- {/* Public OIDC Login / Landing Page */}
- } />
- } />
+ }>
+
+ {/* Public OIDC Login / Landing Page */}
+ } />
+ } />
- {/* Protected Backoffice Application Shell */}
-
-
-
- }
- >
- } />
- } />
- } />
- } />
- } />
- } />
-
+ {/* Protected Backoffice Application Shell */}
+
+
+
+ }
+ >
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+
- {/* Fallback Redirect */}
- } />
-
+ {/* Fallback Redirect */}
+ } />
+
+
diff --git a/PostFundManagement.ConsoleUI/src/auth/AuthProvider.tsx b/PostFundManagement.ConsoleUI/src/auth/AuthProvider.tsx
index c833437..6be0a35 100644
--- a/PostFundManagement.ConsoleUI/src/auth/AuthProvider.tsx
+++ b/PostFundManagement.ConsoleUI/src/auth/AuthProvider.tsx
@@ -131,7 +131,7 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
// Subscribe to global HTTP 401 session expiration events
useEffect(() => {
const unsubscribe = onSessionExpired((reason) => {
- // Subordination: If account activation lockout is active, session expiration is completely avoided and bypassed
+ // If account activation is pending, suppress session expiration modal
if (isPendingActivation || isActivationPending()) {
return;
}
@@ -148,8 +148,7 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
};
}, [accessToken, isPendingActivation]);
- // Priority Subordination: When activation process is active, dismiss all other modals,
- // cancel session expiration and avoid its processes so that there is no overlapping.
+ // When account activation is pending, ensure secondary dialogs are closed
useEffect(() => {
if (isPendingActivation) {
setIsSessionExpired(false);
@@ -159,7 +158,7 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
}
}, [isPendingActivation]);
- // When session expiration modal is displayed, dismiss all other subordinates
+ // When session expiration modal is displayed, dismiss background modals
useEffect(() => {
if (isSessionExpired) {
dismissSubordinateModals();
@@ -221,10 +220,7 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
// Clean up code and state from URL
window.history.replaceState({}, document.title, window.location.pathname);
- // Execute user sync endpoint upon login.
- // If HTTP 401 -> routes to landing page
- // If non-401 failure -> displays non-dismissable activation modal
- // If success -> saved in browser to prevent endpoint hotspot
+ // Synchronize authenticated user profile with backend directory
try {
const syncResult = await syncUserAccount(userSub, token, profile, true);
if (syncResult.isUnauthorized) {
diff --git a/PostFundManagement.ConsoleUI/src/components/auth/AccountActivationModal.tsx b/PostFundManagement.ConsoleUI/src/components/auth/AccountActivationModal.tsx
index fbdf998..36add5a 100644
--- a/PostFundManagement.ConsoleUI/src/components/auth/AccountActivationModal.tsx
+++ b/PostFundManagement.ConsoleUI/src/components/auth/AccountActivationModal.tsx
@@ -38,8 +38,7 @@ export const AccountActivationModal: React.FC = ({
const [errorMessage, setErrorMessage] = useState(initialError || null);
const [syncAttemptCount, setSyncAttemptCount] = useState(0);
- // Activation process dismisses all other modals so that it is displayed without overlapping.
- // By extension, any active session expiration modal is dismissed and its processes cancelled.
+ // Clear any existing session prompt and close background dialogs
useEffect(() => {
cancelSessionExpiration();
dismissSubordinateModals();
diff --git a/PostFundManagement.ConsoleUI/src/components/auth/SessionExpiredModal.tsx b/PostFundManagement.ConsoleUI/src/components/auth/SessionExpiredModal.tsx
index a2711c4..7523ca8 100644
--- a/PostFundManagement.ConsoleUI/src/components/auth/SessionExpiredModal.tsx
+++ b/PostFundManagement.ConsoleUI/src/components/auth/SessionExpiredModal.tsx
@@ -21,14 +21,14 @@ export const SessionExpiredModal: React.FC = ({
const totalDurationMs = countdownSeconds * 1000;
const isClearingRef = useRef(false);
- // When session expiration modal is displayed, dismiss all subordinate modals
+ // Close background modals when session expires
useEffect(() => {
if (isOpen) {
dismissSubordinateModals();
}
}, [isOpen]);
- // Listen for cancellation event (e.g., when activation lockout takes priority)
+ // Reset state if session expiration is cancelled
useEffect(() => {
const handleCancelled = () => {
setTimeLeftMs(countdownSeconds * 1000);
diff --git a/PostFundManagement.ConsoleUI/src/components/layout/AppLayout.tsx b/PostFundManagement.ConsoleUI/src/components/layout/AppLayout.tsx
index b7a0da8..32afa5a 100644
--- a/PostFundManagement.ConsoleUI/src/components/layout/AppLayout.tsx
+++ b/PostFundManagement.ConsoleUI/src/components/layout/AppLayout.tsx
@@ -18,6 +18,7 @@ import {
} from 'lucide-react';
import { useAuth } from '../../auth/AuthProvider';
import { BrandLogo } from '../ui/BrandLogo';
+import { HelpTooltip } from '../ui/HelpTooltip';
import { useQuery } from '@tanstack/react-query';
import { getUsers } from '../../services/api';
import { getUserEmail, registerUserEmail } from '../../lib/formatters';
@@ -27,31 +28,37 @@ const navItems = [
name: 'Executive Dashboard',
path: '/dashboard',
icon: LayoutDashboard,
+ help: 'Strategic macro metrics, portfolio value, disbursed funding, ISO 31000 risk heatmap, and projects requiring immediate intervention.',
},
{
name: 'Governance & Structure',
path: '/governance',
icon: Building2,
+ help: 'Master records for legal entities, holding structures, investment portfolios, and thematic programmes funded under statutory mandates.',
},
{
name: 'Projects & Milestones',
path: '/projects',
icon: FolderKanban,
+ help: 'Project Work Breakdown Structure (WBS), contractual deliverables, scheduled milestones, evidence verification, and execution tracking.',
},
{
name: 'Grant & Financials',
path: '/grants',
icon: Coins,
+ help: 'Financial management including statutory awards, multi-year budget line allocations, tranches, and scheduled payment disbursements.',
},
{
name: 'M&E, Evidence & Risks',
path: '/monitoring',
icon: ShieldAlert,
+ help: 'Monitoring & Evaluation (M&E) framework, indicator tracking, ISO 31000 risk registers, mitigation plans, and compliance audits.',
},
{
name: 'Settings & Communications',
path: '/settings',
icon: Settings,
+ help: 'User account provisioning, statutory sync, tenant environment parameters, and communication invite templates.',
},
];
@@ -109,7 +116,7 @@ export const AppLayout: React.FC = () => {
sidebarOpen ? 'w-64' : 'w-20'
} shrink-0 bg-[#0b101b]/95 text-slate-200 border-r border-slate-800/80 transition-all duration-300 flex flex-col z-30 shadow-2xl backdrop-blur-md`}
>
- {/* Brand Header — Clickable link navigating to auth-aware Home page */}
+ {/* Brand Header */}
{
{navItems.map((item) => {
const Icon = item.icon;
return (
-
- `flex items-center gap-3 px-3.5 py-2.5 rounded-xl text-xs font-medium transition-all ${
- isActive
- ? 'bg-gradient-to-r from-blue-600 to-indigo-600 text-white shadow-md shadow-blue-600/25 font-semibold'
- : 'text-slate-400 hover:text-slate-100 hover:bg-slate-800/60'
- }`
- }
- >
-
- {sidebarOpen && {item.name} }
-
+
+
+ `flex items-center gap-3 px-3.5 py-2.5 rounded-xl text-xs font-medium transition-all flex-1 min-w-0 ${
+ isActive
+ ? 'bg-gradient-to-r from-blue-600 to-indigo-600 text-white shadow-md shadow-blue-600/25 font-semibold'
+ : 'text-slate-400 hover:text-slate-100 hover:bg-slate-800/60'
+ }`
+ }
+ >
+
+ {sidebarOpen && {item.name} }
+
+ {sidebarOpen && (
+
+
+
+ )}
+
);
})}
- {/* User Card with Token-derived info & Identity Server Logout */}
+ {/* User Card */}
@@ -192,7 +210,7 @@ export const AppLayout: React.FC = () => {
{/* Top Header */}
- {/* Top Bar Brand Icon & Direct Portal Link */}
+ {/* Brand Icon */}
{
- {/* Clickable Home Link */}
+ {/* Home Navigation */}
{
{tenantName}
+
-
-
- Authenticated Session
-
+
+
+
+ Authenticated Session
+
+
+
- {/* User Name & Email derived from token, plus Identity Server signout */}
+ {/* User Profile and Controls */}
@@ -292,7 +324,7 @@ export const AppLayout: React.FC = () => {
- {/* Viewport with Landing Page ambient dot grid and glow */}
+ {/* Main Content Area */}
void;
+ isCreateModalOpenExternal?: boolean;
+ onCloseCreateModalExternal?: () => void;
+}
+
+export const ContractsManagement: React.FC
= ({
+ isCreateModalOpenExternal,
+ onCloseCreateModalExternal,
+}) => {
+ const queryClient = useQueryClient();
+ const { success, error: toastError, info } = useToast();
+
+ // Component state
+ const [internalCreateModalOpen, setInternalCreateModalOpen] = useState(false);
+ const isCreateModalOpen = isCreateModalOpenExternal ?? internalCreateModalOpen;
+ const setCreateModalOpen = (open: boolean) => {
+ if (onCloseCreateModalExternal && !open) {
+ onCloseCreateModalExternal();
+ }
+ setInternalCreateModalOpen(open);
+ };
+
+ const [statusFilter, setStatusFilter] = useState('all');
+ const [searchTerm, setSearchTerm] = useState('');
+
+ // Selected contract for status update
+ const [contractToUpdate, setContractToUpdate] = useState(null);
+ const [newStatus, setNewStatus] = useState(ContractStatus.Active);
+
+ // Selected contract for file upload / replace
+ const [contractForFileUpload, setContractForFileUpload] = useState(null);
+ const [selectedUploadFile, setSelectedUploadFile] = useState(null);
+ const fileInputRef = useRef(null);
+
+ // Create contract form state
+ const [selectedAwardId, setSelectedAwardId] = useState('');
+ const [totalValueInput, setTotalValueInput] = useState(0);
+ const [effectiveAtInput, setEffectiveAtInput] = useState(
+ new Date().toISOString().split('T')[0]
+ );
+ const [expiresAtInput, setExpiresAtInput] = useState(() => {
+ const d = new Date();
+ d.setFullYear(d.getFullYear() + 1);
+ return d.toISOString().split('T')[0];
+ });
+ const [contractPdfFile, setContractPdfFile] = useState(null);
+ const [onlyApprovedAwards, setOnlyApprovedAwards] = useState(true);
+
+ // API Error states for modal banner display
+ const [createApiError, setCreateApiError] = useState(null);
+ const [uploadApiError, setUploadApiError] = useState(null);
+ const [updateApiError, setUpdateApiError] = useState(null);
+
+ // Loading indicator for secure download
+ const [downloadingContractId, setDownloadingContractId] = useState(null);
+
+ // Queries
+ const {
+ data: contracts = [],
+ isLoading: loadingContracts,
+ refetch: refetchContracts,
+ isRefetching: refetchingContracts,
+ } = useQuery({
+ queryKey: ['contracts'],
+ queryFn: () => getContracts(1, 100),
+ });
+
+ const { data: awards = [], isLoading: loadingAwards } = useQuery({
+ queryKey: ['awards'],
+ queryFn: () => getAwards(1, 100),
+ });
+
+ const { data: organisations = [] } = useQuery({
+ queryKey: ['organisations'],
+ queryFn: () => getOrganisations(1, 100),
+ });
+
+ const { data: programmes = [] } = useQuery({
+ queryKey: ['all-programmes'],
+ queryFn: () => getAllProgrammes(1, 100),
+ });
+
+ const { data: projects = [] } = useQuery({
+ queryKey: ['projects'],
+ queryFn: () => getProjects(),
+ });
+
+ // Helper to match award and its related metadata
+ const getAwardForContract = (contract: Contract): Award | undefined => {
+ return awards.find(
+ (a) =>
+ String(a.id) === String(contract.awardId) ||
+ toolsService.encodeIdSync(a.id) === String(contract.awardId)
+ );
+ };
+
+ const getOrganisationForAward = (award?: Award): Organisation | undefined => {
+ if (!award) return undefined;
+ return organisations.find(
+ (o) =>
+ String(o.id) === String(award.organisationId) ||
+ toolsService.encodeIdSync(o.id) === String(award.organisationId)
+ );
+ };
+
+ const getProgrammeForAward = (award?: Award): Programme | undefined => {
+ if (!award) return undefined;
+ return programmes.find(
+ (p) =>
+ String(p.id) === String(award.programmeId) ||
+ toolsService.encodeIdSync(p.id) === String(award.programmeId)
+ );
+ };
+
+ const getProjectForAward = (award?: Award, prog?: Programme): Project | undefined => {
+ if (!award) return undefined;
+ // Match by direct project ID on award or by programme link
+ return projects.find(
+ (pr) =>
+ (award.projectId && (String(pr.id) === String(award.projectId) || toolsService.encodeIdSync(pr.id) === String(award.projectId))) ||
+ (award.programmeId && String(pr.programmeId) === String(award.programmeId)) ||
+ (prog && String(pr.programmeId) === String(prog.id))
+ );
+ };
+
+ // Filter awards for dropdown: Approved status or all
+ const filteredAwardsForDropdown = awards.filter((a) => {
+ if (onlyApprovedAwards) {
+ return Number(a.status) === ApprovalStatus.Approved;
+ }
+ return true;
+ });
+
+ // When selected award changes in modal, auto-populate value and defaults
+ const handleAwardSelect = (awardIdStr: string) => {
+ setSelectedAwardId(awardIdStr);
+ setCreateApiError(null);
+ const award = awards.find(
+ (a) =>
+ String(a.id) === awardIdStr ||
+ toolsService.encodeIdSync(a.id) === awardIdStr
+ );
+ if (award) {
+ if (award.amount && award.amount > 0) {
+ setTotalValueInput(award.amount);
+ }
+ }
+ };
+
+ // Selected award metadata preview
+ const activeSelectedAward = awards.find(
+ (a) =>
+ String(a.id) === selectedAwardId ||
+ toolsService.encodeIdSync(a.id) === selectedAwardId
+ );
+ const activeSelectedOrg = getOrganisationForAward(activeSelectedAward);
+ const activeSelectedProg = getProgrammeForAward(activeSelectedAward);
+ const activeSelectedProj = getProjectForAward(activeSelectedAward, activeSelectedProg);
+
+ // Mutations
+ const createContractMutation = useMutation({
+ mutationFn: async () => {
+ setCreateApiError(null);
+ if (!selectedAwardId) {
+ throw new Error('Please select an approved award from the list.');
+ }
+ if (!totalValueInput || totalValueInput <= 0) {
+ throw new Error('Please enter a valid total contract value greater than 0.');
+ }
+ if (!contractPdfFile) {
+ throw new Error('Please upload a PDF document of the contract.');
+ }
+
+ // Step 1: Create contract via POST /v1/api/grants/contracts (awardId will be hashed by service)
+ const createdContract = await createContract({
+ awardId: selectedAwardId,
+ totalValue: Number(totalValueInput),
+ effectiveAt: new Date(effectiveAtInput).toISOString(),
+ expiresAt: new Date(expiresAtInput).toISOString(),
+ });
+
+ // Target contractId for upload
+ const contractIdToUploadAgainst =
+ createdContract?.id ?? (createdContract as any)?.contractId ?? selectedAwardId;
+
+ // Step 2: Upload PDF document to POST /v1/api/grants/contracts/{contractId}
+ if (contractPdfFile && contractIdToUploadAgainst) {
+ await uploadContractFile(contractIdToUploadAgainst, contractPdfFile);
+ }
+
+ return createdContract;
+ },
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ['contracts'] });
+ queryClient.invalidateQueries({ queryKey: ['awards'] });
+ queryClient.invalidateQueries({ queryKey: ['dashboard'] });
+ success(
+ 'Contract Created & Document Uploaded',
+ 'The contract record has been registered and the PDF document securely stored in S3.'
+ );
+ setCreateModalOpen(false);
+ resetCreateForm();
+ },
+ onError: (err: unknown) => {
+ setCreateApiError(err);
+ toastError('Contract Creation Failed', err);
+ },
+ });
+
+ const uploadFileMutation = useMutation({
+ mutationFn: async () => {
+ setUploadApiError(null);
+ if (!contractForFileUpload) throw new Error('No contract selected for upload.');
+ if (!selectedUploadFile) throw new Error('Please select a PDF document to upload.');
+
+ await uploadContractFile(contractForFileUpload.id, selectedUploadFile);
+ },
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ['contracts'] });
+ success('Contract Document Updated', 'New contract PDF document uploaded successfully.');
+ setContractForFileUpload(null);
+ setSelectedUploadFile(null);
+ },
+ onError: (err: unknown) => {
+ setUploadApiError(err);
+ toastError('Document Upload Failed', err);
+ },
+ });
+
+ const updateStatusMutation = useMutation({
+ mutationFn: async () => {
+ setUpdateApiError(null);
+ if (!contractToUpdate) throw new Error('No contract selected.');
+ await updateContract({
+ contractId: contractToUpdate.id,
+ status: newStatus,
+ });
+ },
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ['contracts'] });
+ queryClient.invalidateQueries({ queryKey: ['dashboard'] });
+ success('Contract Status Updated', 'The contract lifecycle status has been updated.');
+ setContractToUpdate(null);
+ },
+ onError: (err: unknown) => {
+ setUpdateApiError(err);
+ toastError('Status Update Failed', err);
+ },
+ });
+
+ const resetCreateForm = () => {
+ setSelectedAwardId('');
+ setTotalValueInput(0);
+ setContractPdfFile(null);
+ setCreateApiError(null);
+ };
+
+ // Secure download link handler (15-minute on-demand link)
+ const handleDownloadSecureLink = async (contract: Contract) => {
+ setDownloadingContractId(contract.id);
+ try {
+ const url = await getContractSecureLink(contract.id);
+ if (url && typeof url === 'string' && url.trim() !== '') {
+ const link = document.createElement('a');
+ link.href = url.trim();
+ link.target = '_blank';
+ link.rel = 'noopener noreferrer';
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+
+ success(
+ '15-Minute Secure Link Generated',
+ 'A time-limited download URL has been generated on-demand and opened.'
+ );
+ } else {
+ toastError('Download Link Unavailable', 'The server returned an empty secure download URL.');
+ }
+ } catch (err: unknown) {
+ toastError('Failed to Retrieve Secure Link', err);
+ } finally {
+ setDownloadingContractId(null);
+ }
+ };
+
+ // Filter and search contracts
+ const filteredContracts = contracts.filter((c) => {
+ // Status filter
+ if (statusFilter !== 'all') {
+ const statusNum = Number(c.status);
+ if (statusNum !== Number(statusFilter)) return false;
+ }
+
+ // Search filter
+ if (searchTerm.trim()) {
+ const q = searchTerm.toLowerCase();
+ const cId = String(c.id).toLowerCase();
+ const aId = String(c.awardId).toLowerCase();
+
+ const award = getAwardForContract(c);
+ const org = getOrganisationForAward(award);
+ const prog = getProgrammeForAward(award);
+ const proj = getProjectForAward(award, prog);
+
+ const orgName = org?.name?.toLowerCase() || '';
+ const progName = prog?.name?.toLowerCase() || '';
+ const projName = proj?.name?.toLowerCase() || '';
+
+ if (
+ !cId.includes(q) &&
+ !aId.includes(q) &&
+ !orgName.includes(q) &&
+ !progName.includes(q) &&
+ !projName.includes(q)
+ ) {
+ return false;
+ }
+ }
+
+ return true;
+ });
+
+ // Calculate summary metrics
+ const totalContractCount = contracts.length;
+ const activeContractsValue = contracts
+ .filter((c) => Number(c.status) === ContractStatus.Active)
+ .reduce((sum, c) => sum + (Number(c.totalValue) || 0), 0);
+ const pendingContractsCount = contracts.filter(
+ (c) =>
+ Number(c.status) === ContractStatus.Draft ||
+ Number(c.status) === ContractStatus.PendingApproval
+ ).length;
+
+ return (
+
+ {/* Summary Cards */}
+
+
+
+
+ Total Contracts
+
+
+
+
+
+
+ {loadingContracts ? '...' : totalContractCount}
+
+
+ Linked to approved grant funding awards
+
+
+
+
+
+
+ Active Value (ZAR)
+
+
+
+
+
+
+ {loadingContracts ? '...' : formatZAR(activeContractsValue)}
+
+
+ Legally executed active commitments
+
+
+
+
+
+
+ In Review / Draft
+
+
+
+
+
+
+ {loadingContracts ? '...' : pendingContractsCount}
+
+
+ Pending legal review or signature
+
+
+
+
+
+
+ Approved Awards Available
+
+
+
+
+
+
+ {loadingAwards
+ ? '...'
+ : awards.filter((a) => Number(a.status) === ApprovalStatus.Approved).length}
+
+
+ Ready to establish contractual legal binding
+
+
+
+
+ {/* Action and Filter Bar */}
+
+
+ {/* Search */}
+
+
+ setSearchTerm(e.target.value)}
+ className="w-full bg-[#151b2c] border border-slate-700/80 rounded-xl pl-9 pr-3 py-1.5 text-xs text-white placeholder-slate-500 focus:outline-none focus:border-blue-500"
+ />
+
+
+ {/* Status Filter */}
+
+
+ setStatusFilter(e.target.value)}
+ className="bg-[#151b2c] border border-slate-700/80 rounded-xl px-2.5 py-1.5 text-xs text-slate-200 focus:outline-none focus:border-blue-500"
+ >
+ All Statuses
+ {CONTRACT_STATUS_OPTIONS.map((opt) => (
+
+ {opt.label}
+
+ ))}
+
+
+
+
refetchContracts()}
+ disabled={refetchingContracts}
+ className="p-2 rounded-xl bg-[#151b2c] border border-slate-700/80 text-slate-400 hover:text-white hover:bg-slate-800 transition"
+ title="Refresh contracts"
+ >
+
+
+
+
+
{
+ resetCreateForm();
+ setCreateModalOpen(true);
+ }}
+ className="inline-flex items-center justify-center gap-1.5 px-4 py-2 text-xs font-semibold text-white bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-500 hover:to-indigo-500 rounded-xl shadow-lg shadow-blue-500/20 transition cursor-pointer"
+ >
+
+ Create Contract & Upload PDF
+
+
+
+ {/* Contracts Table */}
+
+
+
+
+ Contract Registry & Associated Programme Hierarchy
+
+
+ Showing {filteredContracts.length} of {contracts.length} contracts
+
+
+
+ {loadingContracts ? (
+
+
+ Loading contractual records and resolving Award links...
+
+ ) : filteredContracts.length === 0 ? (
+
+
+
No contracts found
+
+ {searchTerm || statusFilter !== 'all'
+ ? 'No contracts match the current search filters. Try resetting the filters.'
+ : 'No contracts have been created yet. Click "Create Contract & Upload PDF" to link an approved award.'}
+
+ {!(searchTerm || statusFilter !== 'all') && (
+
{
+ resetCreateForm();
+ setCreateModalOpen(true);
+ }}
+ className="mt-3 px-3 py-1.5 text-xs text-blue-400 bg-blue-500/10 hover:bg-blue-500/20 border border-blue-500/30 rounded-xl transition"
+ >
+ Create First Contract
+
+ )}
+
+ ) : (
+
+
+
+
+ Contract / Ref
+ Award & Amount
+ Programme & Project
+ Organisation
+ Validity Period
+ Status
+ Document / S3
+ Actions
+
+
+
+ {filteredContracts.map((contract) => {
+ const award = getAwardForContract(contract);
+ const org = getOrganisationForAward(award);
+ const prog = getProgrammeForAward(award);
+ const proj = getProjectForAward(award, prog);
+ const statusBadge = getContractStatusBadge(contract.status);
+ const isDownloading = downloadingContractId === contract.id;
+
+ return (
+
+ {/* Contract ID */}
+
+
+ #{contract.id}
+
+
+ Hash: {toolsService.encodeIdSync(contract.id)}
+
+
+ {formatZAR(contract.totalValue)}
+
+
+
+ {/* Linked Award */}
+
+ {award ? (
+
+
+ Award #{award.id}
+ {award.referenceNo && (
+
+ ({award.referenceNo})
+
+ )}
+
+
+ Funded: {formatZAR(award.amount)}
+
+ {award.status && (
+
+ {getApprovalStatusBadge(award.status).label}
+
+ )}
+
+ ) : (
+
+ Award #{contract.awardId}
+
+ )}
+
+
+ {/* Programme & Project */}
+
+
+
+
+ {prog?.name || 'Programme Linked'}
+
+
+
+
+
+ {proj?.name || (award?.projectId ? `Project #${award.projectId}` : 'Project Scope')}
+
+
+
+
+ {/* Organisation */}
+
+
+
+
+ {org?.name || 'Recipient Organisation'}
+
+
+ {org?.registrationNo && (
+
+ Reg: {org.registrationNo}
+
+ )}
+
+
+ {/* Validity Period */}
+
+
+
+ {formatDate(contract.effectiveAt)}
+
+
+ to {formatDate(contract.expiresAt)}
+
+
+
+ {/* Status */}
+
+
+ {statusBadge.label}
+
+
+
+ {/* Document / File S3 Download */}
+
+
+ handleDownloadSecureLink(contract)}
+ disabled={isDownloading}
+ className="inline-flex items-center gap-1 px-2.5 py-1 text-[11px] font-medium text-blue-300 hover:text-white bg-blue-500/10 hover:bg-blue-500/25 border border-blue-500/30 rounded-lg transition cursor-pointer"
+ title="Generate 15-minute secure download link on demand"
+ >
+ {isDownloading ? (
+
+ ) : (
+
+ )}
+ Secure PDF
+
+
+ {
+ setContractForFileUpload(contract);
+ setSelectedUploadFile(null);
+ setUploadApiError(null);
+ }}
+ className="inline-flex items-center gap-1 px-2 py-1 text-[11px] text-slate-400 hover:text-slate-200 bg-slate-800 hover:bg-slate-700 rounded-lg border border-slate-700 transition cursor-pointer"
+ title="Upload fresh PDF or replace existing contract document"
+ >
+
+ Replace
+
+
+
+
+ 15-min secure link
+
+
+
+ {/* Actions */}
+
+ {
+ setContractToUpdate(contract);
+ setNewStatus(Number(contract.status));
+ setUpdateApiError(null);
+ }}
+ className="inline-flex items-center gap-1 px-2.5 py-1 text-[11px] font-medium text-slate-300 hover:text-white bg-slate-800/80 hover:bg-slate-700 rounded-lg border border-slate-700 transition cursor-pointer"
+ >
+
+ Status
+
+
+
+ );
+ })}
+
+
+
+ )}
+
+
+ {/* ======================================================== */}
+ {/* MODAL 1: Create Contract & Initial PDF Upload Workflow */}
+ {/* ======================================================== */}
+
setCreateModalOpen(false)}
+ title="Create Legal Contract & Attach PDF"
+ >
+
+
+
+ {/* ======================================================== */}
+ {/* MODAL 2: Replace / Upload New Contract PDF File */}
+ {/* ======================================================== */}
+
setContractForFileUpload(null)}
+ title={`Upload New Contract Document (Contract #${contractForFileUpload?.id})`}
+ >
+
+
+
+ {/* ======================================================== */}
+ {/* MODAL 3: Update Contract Status */}
+ {/* ======================================================== */}
+
setContractToUpdate(null)}
+ title={`Update Contract Status (Contract #${contractToUpdate?.id})`}
+ >
+
+
+
+ );
+};
diff --git a/PostFundManagement.ConsoleUI/src/components/ui/BrandLogo.tsx b/PostFundManagement.ConsoleUI/src/components/ui/BrandLogo.tsx
index 3d53a33..9018f96 100644
--- a/PostFundManagement.ConsoleUI/src/components/ui/BrandLogo.tsx
+++ b/PostFundManagement.ConsoleUI/src/components/ui/BrandLogo.tsx
@@ -17,12 +17,7 @@ const sizeMap = {
};
/**
- * Modern, stylized SVG brand emblem representing Post Fund Management (PFM).
- * Features an isometric kinetic nexus with three interlocking governance facets:
- * 1. Top Canopy: Governance, Strategy & Portfolios (Radiant Indigo / Violet)
- * 2. Left Vault: Capital, Grants & Disbursement Assurance (Electric Sapphire)
- * 3. Right Wing: Milestones, Evidence & Impact Delivery (Cyan to Emerald)
- * 4. Center Core: Luminous Floating Diamond Milestone Star
+ * SVG brand logo component for Post Fund Management (PFM).
*/
export const BrandLogo: React.FC = ({
size = 'md',
diff --git a/PostFundManagement.ConsoleUI/src/components/ui/HelpTooltip.tsx b/PostFundManagement.ConsoleUI/src/components/ui/HelpTooltip.tsx
new file mode 100644
index 0000000..dfcd092
--- /dev/null
+++ b/PostFundManagement.ConsoleUI/src/components/ui/HelpTooltip.tsx
@@ -0,0 +1,125 @@
+import React, { useState, useRef, useEffect, ReactNode } from 'react';
+import { Info, X } from 'lucide-react';
+
+export interface HelpTooltipProps {
+ /** Concise context-sensitive explanation of the section, control, metric, or dialog */
+ content: ReactNode;
+ /** Optional title for the tooltip popover */
+ title?: string;
+ /** Custom alignment: top, bottom, left, right (defaults to top) */
+ placement?: 'top' | 'bottom' | 'left' | 'right';
+ /** Size of the (i) icon: sm (13px), md (15px), lg (18px) */
+ size?: 'sm' | 'md' | 'lg';
+ /** Additional styling classes for the container or trigger button */
+ className?: string;
+ /** Accessible label or ARIA label */
+ ariaLabel?: string;
+}
+
+export const HelpTooltip: React.FC = ({
+ content,
+ title,
+ placement = 'top',
+ size = 'md',
+ className = '',
+ ariaLabel = 'Information and guidance',
+}) => {
+ const [isOpen, setIsOpen] = useState(false);
+ const containerRef = useRef(null);
+
+ // Close on Escape or click outside
+ useEffect(() => {
+ if (!isOpen) return;
+
+ const handleOutsideClick = (e: MouseEvent | TouchEvent) => {
+ if (containerRef.current && !containerRef.current.contains(e.target as Node)) {
+ setIsOpen(false);
+ }
+ };
+
+ const handleKeyDown = (e: KeyboardEvent) => {
+ if (e.key === 'Escape') {
+ setIsOpen(false);
+ }
+ };
+
+ document.addEventListener('mousedown', handleOutsideClick);
+ document.addEventListener('touchstart', handleOutsideClick);
+ document.addEventListener('keydown', handleKeyDown);
+
+ return () => {
+ document.removeEventListener('mousedown', handleOutsideClick);
+ document.removeEventListener('touchstart', handleOutsideClick);
+ document.removeEventListener('keydown', handleKeyDown);
+ };
+ }, [isOpen]);
+
+ const sizeClasses = {
+ sm: 'w-3.5 h-3.5 text-[11px]',
+ md: 'w-4 h-4 text-xs',
+ lg: 'w-5 h-5 text-sm',
+ }[size];
+
+ const iconSizes = {
+ sm: 'w-3 h-3',
+ md: 'w-3.5 h-3.5',
+ lg: 'w-4 h-4',
+ }[size];
+
+ const placementClasses = {
+ top: 'bottom-full left-1/2 -translate-x-1/2 mb-2',
+ bottom: 'top-full left-1/2 -translate-x-1/2 mt-2',
+ left: 'right-full top-1/2 -translate-y-1/2 mr-2',
+ right: 'left-full top-1/2 -translate-y-1/2 ml-2',
+ }[placement];
+
+ return (
+
+
{
+ e.stopPropagation();
+ setIsOpen((prev) => !prev);
+ }}
+ className={`inline-flex items-center justify-center rounded-full text-slate-400 hover:text-blue-400 bg-slate-800/80 hover:bg-slate-700/80 border border-slate-700/60 hover:border-blue-500/50 transition-all cursor-pointer shadow-xs focus:outline-none focus:ring-1 focus:ring-blue-500/60 ${sizeClasses} p-0.5`}
+ title="Click for context guidance"
+ >
+
+
+
+ {isOpen && (
+
e.stopPropagation()}
+ className={`absolute ${placementClasses} z-50 w-72 max-w-[calc(100vw-32px)] p-3 rounded-xl bg-[#0c121e] border border-blue-500/30 text-slate-200 shadow-2xl backdrop-blur-md animate-in fade-in zoom-in-95 duration-150`}
+ >
+
+
+
+
+ {title || 'Guidance & Information'}
+
+
+
{
+ e.stopPropagation();
+ setIsOpen(false);
+ }}
+ className="rounded-md p-0.5 text-slate-400 hover:text-white hover:bg-slate-800 transition cursor-pointer"
+ aria-label="Close tooltip"
+ >
+
+
+
+
+
+ {content}
+
+
+ )}
+
+ );
+};
diff --git a/PostFundManagement.ConsoleUI/src/components/ui/Modal.tsx b/PostFundManagement.ConsoleUI/src/components/ui/Modal.tsx
index a12097b..cfad194 100644
--- a/PostFundManagement.ConsoleUI/src/components/ui/Modal.tsx
+++ b/PostFundManagement.ConsoleUI/src/components/ui/Modal.tsx
@@ -1,11 +1,13 @@
import React, { ReactNode, useEffect } from 'react';
import { X } from 'lucide-react';
+import { HelpTooltip } from './HelpTooltip';
interface ModalProps {
isOpen: boolean;
onClose: () => void;
title: string;
subtitle?: string;
+ helpText?: ReactNode;
children: ReactNode;
maxWidth?: 'sm' | 'md' | 'lg' | 'xl' | '2xl';
}
@@ -15,6 +17,7 @@ export const Modal: React.FC = ({
onClose,
title,
subtitle,
+ helpText,
children,
maxWidth = 'lg',
}) => {
@@ -56,7 +59,17 @@ export const Modal: React.FC = ({
>
-
{title}
+
+
{title}
+ {helpText && (
+
+ )}
+
{subtitle &&
{subtitle}
}
void;
+ title?: string;
+ className?: string;
+}
+
+export const ProblemDetailsAlert: React.FC = ({
+ error,
+ onDismiss,
+ title,
+ className = '',
+}) => {
+ if (!error) return null;
+
+ const parsed = parseFriendlyError(error, title || 'Action Could Not Be Completed');
+
+ return (
+
+
+
+
+
+
+ {parsed.title}
+
+ {parsed.categoryLabel && (
+
+ {parsed.categoryLabel}
+
+ )}
+ {parsed.statusCode && (
+
+ HTTP {parsed.statusCode}
+
+ )}
+
+
+ {/* Single error or fallback message displayed directly */}
+ {(!parsed.errorsList || parsed.errorsList.length <= 1) && parsed.message && (
+
+ {parsed.message}
+
+ )}
+
+ {/* Multiple errors from ProblemDetails displayed directly */}
+ {parsed.errorsList && parsed.errorsList.length > 1 && (
+
+ {parsed.errorsList.map((err, idx) => (
+
+ •
+ {err}
+
+ ))}
+
+ )}
+
+ {parsed.technicalDetails && (
+
+ Ref: {parsed.technicalDetails}
+
+ )}
+
+
+ {onDismiss && (
+
+
+
+ )}
+
+
+ );
+};
diff --git a/PostFundManagement.ConsoleUI/src/components/ui/RiskHeatmapMatrix.tsx b/PostFundManagement.ConsoleUI/src/components/ui/RiskHeatmapMatrix.tsx
index 57b9857..c276fd5 100644
--- a/PostFundManagement.ConsoleUI/src/components/ui/RiskHeatmapMatrix.tsx
+++ b/PostFundManagement.ConsoleUI/src/components/ui/RiskHeatmapMatrix.tsx
@@ -2,6 +2,7 @@ import React, { useState, useMemo } from 'react';
import { Risk, Project } from '../../types';
import { getUserEmail } from '../../lib/formatters';
import { AlertTriangle, ShieldAlert, CheckCircle2, ChevronRight, X, Info } from 'lucide-react';
+import { HelpTooltip } from './HelpTooltip';
interface RiskHeatmapMatrixProps {
risks: Risk[];
@@ -168,7 +169,15 @@ export const RiskHeatmapMatrix: React.FC = ({
{/* Header & Controls */}
- {title &&
{title} }
+
+ {title &&
{title} }
+
+
{subtitle &&
{subtitle}
}
@@ -200,6 +209,12 @@ export const RiskHeatmapMatrix: React.FC
= ({
3×3 Summary
+
diff --git a/PostFundManagement.ConsoleUI/src/components/ui/Toast.tsx b/PostFundManagement.ConsoleUI/src/components/ui/Toast.tsx
index a07afbd..e4a741c 100644
--- a/PostFundManagement.ConsoleUI/src/components/ui/Toast.tsx
+++ b/PostFundManagement.ConsoleUI/src/components/ui/Toast.tsx
@@ -25,6 +25,7 @@ export interface ToastItem {
categoryLabel?: string;
technicalDetails?: string;
fieldErrors?: Array<{ field: string; message: string }>;
+ errorsList?: string[];
}
export interface ToastOptions {
@@ -34,6 +35,7 @@ export interface ToastOptions {
categoryLabel?: string;
technicalDetails?: string;
fieldErrors?: Array<{ field: string; message: string }>;
+ errorsList?: string[];
}
interface ToastContextType {
@@ -153,11 +155,21 @@ const ToastCard: React.FC<{
)}
- {toast.message && (
+ {/* Display errors directly if multiple, or single message directly */}
+ {toast.errorsList && toast.errorsList.length > 1 ? (
+
+ {toast.errorsList.map((err, idx) => (
+
+ •
+ {err}
+
+ ))}
+
+ ) : toast.message ? (
{toast.message}
- )}
+ ) : null}
{/* Optional collapsible technical reference */}
{toast.technicalDetails && (
@@ -255,6 +267,7 @@ export const ToastProvider: React.FC<{ children: ReactNode }> = ({ children }) =
categoryLabel: options?.categoryLabel,
technicalDetails: options?.technicalDetails,
fieldErrors: options?.fieldErrors,
+ errorsList: options?.errorsList,
},
]);
};
@@ -302,6 +315,7 @@ export const ToastProvider: React.FC<{ children: ReactNode }> = ({ children }) =
categoryLabel: options?.categoryLabel || parsed.categoryLabel,
technicalDetails: parsed.technicalDetails || options?.technicalDetails,
fieldErrors: parsed.fieldErrors || options?.fieldErrors,
+ errorsList: parsed.errorsList || options?.errorsList,
});
};
diff --git a/PostFundManagement.ConsoleUI/src/config.ts b/PostFundManagement.ConsoleUI/src/config.ts
index b7ad2ca..c3f2ab1 100644
--- a/PostFundManagement.ConsoleUI/src/config.ts
+++ b/PostFundManagement.ConsoleUI/src/config.ts
@@ -65,13 +65,33 @@ function pickFirstNonEmpty(...candidates: any[]): string | undefined {
}
/**
- * Normalizes an API base URL ensuring proper protocol and single trailing slash.
+ * Normalizes an API base URL ensuring HTTPS protocol by default and a single trailing slash.
*/
function normalizeBaseUrl(url: string): string {
- const trimmed = url.trim();
+ let trimmed = url.trim();
+ // Ensure HTTPS is default protocol if protocol is omitted
+ if (!/^https?:\/\//i.test(trimmed)) {
+ trimmed = `https://${trimmed}`;
+ } else if (trimmed.startsWith('http://') && !trimmed.includes('localhost') && !trimmed.includes('127.0.0.1')) {
+ // Upgrade remote HTTP URLs to HTTPS by default
+ trimmed = trimmed.replace(/^http:\/\//i, 'https://');
+ }
return trimmed.endsWith('/') ? trimmed : `${trimmed}/`;
}
+/**
+ * Normalizes an OIDC/STS Authority URL ensuring HTTPS protocol by default.
+ */
+function normalizeAuthorityUrl(url: string): string {
+ let trimmed = url.trim();
+ if (!/^https?:\/\//i.test(trimmed)) {
+ trimmed = `https://${trimmed}`;
+ } else if (trimmed.startsWith('http://') && !trimmed.includes('localhost') && !trimmed.includes('127.0.0.1')) {
+ trimmed = trimmed.replace(/^http:\/\//i, 'https://');
+ }
+ return trimmed.replace(/\/+$/, '');
+}
+
/**
* Reads any runtime config object injected into window.
* Supports: window.__RUNTIME_CONFIG__, window.APP_CONFIG, window._env_
@@ -268,7 +288,7 @@ function resolveConfig(fetchedJson?: Record
): AppConfig {
return {
SecuritySettings: {
- Authority: authority,
+ Authority: normalizeAuthorityUrl(authority),
Audience: audience,
ClientId: clientId,
ClientSecret: clientSecret,
diff --git a/PostFundManagement.ConsoleUI/src/pages/ExecutiveDashboard.tsx b/PostFundManagement.ConsoleUI/src/pages/ExecutiveDashboard.tsx
index 20b32e5..18b6db2 100644
--- a/PostFundManagement.ConsoleUI/src/pages/ExecutiveDashboard.tsx
+++ b/PostFundManagement.ConsoleUI/src/pages/ExecutiveDashboard.tsx
@@ -28,6 +28,7 @@ import {
import { formatCompactZAR, formatZAR, getIndustrySectorLabel } from '../lib/formatters';
import { ApprovalStatus } from '../types';
import { RiskHeatmapMatrix } from '../components/ui/RiskHeatmapMatrix';
+import { HelpTooltip } from '../components/ui/HelpTooltip';
export const ExecutiveDashboard: React.FC = () => {
const queryClient = useQueryClient();
@@ -35,7 +36,7 @@ export const ExecutiveDashboard: React.FC = () => {
const [selectedSector, setSelectedSector] = useState('All');
const [selectedPeriod, setSelectedPeriod] = useState('FY 2026/27');
- // Real Queries from OpenAPI Endpoints
+ // Data queries
const { data: dashboard, isLoading: loadingDashboard, refetch: refetchDashboard, isFetching } = useQuery({
queryKey: ['dashboard'],
queryFn: getDashboard,
@@ -109,7 +110,7 @@ export const ExecutiveDashboard: React.FC = () => {
return risks.filter((r) => projectIds.has(String(r.projectId)));
}, [risks, filteredProjects, selectedProgramme, selectedSector]);
- // Headline metrics sourced directly from live API models with real filtering
+ // Headline portfolio metrics calculation
const portfolioValue = useMemo(() => {
if (selectedProgramme !== 'All' || selectedSector !== 'All') {
const projectIds = new Set(filteredProjects.map((p) => String(p.id)));
@@ -176,7 +177,7 @@ export const ExecutiveDashboard: React.FC = () => {
return Math.round((onTrack / targetProjects.length) * 100);
}, [dashboard, filteredProjects, projects]);
- // Dynamically calculate At Risk count directly from live registered risks
+ // Calculate at-risk projects count based on risk scores
const atRiskCount = useMemo(() => {
const targetRisks = filteredRisks;
const highRisks = targetRisks.filter((r) => {
@@ -198,7 +199,7 @@ export const ExecutiveDashboard: React.FC = () => {
return sumActual > 0 ? sumActual : 0;
}, [dashboard, indicators]);
- // Real sector breakdown calculated dynamically from actual project records
+ // Sector allocation breakdown
const sectorData = useMemo(() => {
const targetProjects = filteredProjects.length > 0 ? filteredProjects : projects;
if (targetProjects.length === 0) return [];
@@ -224,7 +225,7 @@ export const ExecutiveDashboard: React.FC = () => {
});
}, [projects, filteredProjects, portfolioValue]);
- // Quarterly disbursements distribution computed from real disbursement items
+ // Quarterly disbursements distribution
const quarterlyTrend = useMemo(() => {
if (!disbursements || disbursements.length === 0) {
return [
@@ -260,7 +261,7 @@ export const ExecutiveDashboard: React.FC = () => {
}));
}, [disbursements]);
- // Attention Projects derived directly from live project data & risk scores
+ // Projects requiring management attention based on risk profile
const attentionProjects = useMemo(() => {
const targetProjects = filteredProjects.length > 0 ? filteredProjects : projects;
if (targetProjects.length === 0) return [];
@@ -323,6 +324,12 @@ export const ExecutiveDashboard: React.FC = () => {
Executive Portfolio Dashboard
+
Live Portfolio Feed
@@ -339,41 +346,65 @@ export const ExecutiveDashboard: React.FC = () => {
Filter:
-
setSelectedProgramme(e.target.value)}
- className="text-xs font-medium bg-[#090d16] border border-slate-700/80 rounded-xl px-3 py-1.5 text-slate-200 focus:outline-none focus:ring-1 focus:ring-blue-500 transition cursor-pointer"
- >
- Programme: All
- {programmes.map((pr) => (
-
- {pr.name}
-
- ))}
-
+
+ setSelectedProgramme(e.target.value)}
+ className="text-xs font-medium bg-[#090d16] border border-slate-700/80 rounded-xl px-3 py-1.5 text-slate-200 focus:outline-none focus:ring-1 focus:ring-blue-500 transition cursor-pointer"
+ >
+ Programme: All
+ {programmes.map((pr) => (
+
+ {pr.name}
+
+ ))}
+
+
+
-
setSelectedSector(e.target.value)}
- className="text-xs font-medium bg-[#090d16] border border-slate-700/80 rounded-xl px-3 py-1.5 text-slate-200 focus:outline-none focus:ring-1 focus:ring-blue-500 transition cursor-pointer"
- >
- Sector: All
- {sectorData.map((s) => (
-
- {s.name}
-
- ))}
-
+
+ setSelectedSector(e.target.value)}
+ className="text-xs font-medium bg-[#090d16] border border-slate-700/80 rounded-xl px-3 py-1.5 text-slate-200 focus:outline-none focus:ring-1 focus:ring-blue-500 transition cursor-pointer"
+ >
+ Sector: All
+ {sectorData.map((s) => (
+
+ {s.name}
+
+ ))}
+
+
+
-
setSelectedPeriod(e.target.value)}
- className="text-xs font-medium bg-[#090d16] border border-slate-700/80 rounded-xl px-3 py-1.5 text-slate-200 focus:outline-none focus:ring-1 focus:ring-blue-500 transition cursor-pointer"
- >
- Period: FY 2026/27
- Period: FY 2025/26
- All-Time Cumulative
-
+
+ setSelectedPeriod(e.target.value)}
+ className="text-xs font-medium bg-[#090d16] border border-slate-700/80 rounded-xl px-3 py-1.5 text-slate-200 focus:outline-none focus:ring-1 focus:ring-blue-500 transition cursor-pointer"
+ >
+ Period: FY 2026/27
+ Period: FY 2025/26
+ All-Time Cumulative
+
+
+
{
{/* Card 1: Portfolio Value */}
-
Portfolio Value
+
+ Portfolio Value
+
+
@@ -405,7 +444,15 @@ export const ExecutiveDashboard: React.FC = () => {
{/* Card 2: Active Awards */}
-
Active Awards
+
+ Active Awards
+
+
@@ -421,7 +468,15 @@ export const ExecutiveDashboard: React.FC = () => {
{/* Card 3: Disbursed */}
-
Disbursed
+
+ Disbursed
+
+
@@ -437,7 +492,15 @@ export const ExecutiveDashboard: React.FC = () => {
{/* Card 4: On Track */}
-
On Track
+
+ On Track
+
+
@@ -451,7 +514,15 @@ export const ExecutiveDashboard: React.FC = () => {
{/* Card 5: At Risk */}
-
At Risk
+
+ At Risk
+
+
@@ -465,7 +536,15 @@ export const ExecutiveDashboard: React.FC = () => {
{/* Card 6: Beneficiaries Reached */}
-
Beneficiaries
+
+ Beneficiaries
+
+
@@ -485,13 +564,21 @@ export const ExecutiveDashboard: React.FC = () => {
-
Funding by Sector
+
+
Funding by Sector
+
+
Live Portfolio
- {/* Circular Graphic Simulation */}
+ {/* Sector Donut Chart */}
@@ -535,7 +622,15 @@ export const ExecutiveDashboard: React.FC = () => {
-
Disbursement Distribution
+
+
Disbursement Distribution
+
+
{disbursements.length} Tranche{disbursements.length === 1 ? '' : 's'}
@@ -545,7 +640,7 @@ export const ExecutiveDashboard: React.FC = () => {
Quarterly release distribution across active portfolios and milestone verifications.
- {/* Graphic Trend Visualization from Live Disbursements */}
+ {/* Quarterly Disbursement Trend Chart */}
{quarterlyTrend.map((q) => (
@@ -597,7 +692,15 @@ export const ExecutiveDashboard: React.FC = () => {
-
Projects Requiring Attention
+
+
Projects Requiring Attention
+
+
Prioritized by risk severity, delivery delay, or financial variance threshold breach.
diff --git a/PostFundManagement.ConsoleUI/src/pages/GovernancePage.tsx b/PostFundManagement.ConsoleUI/src/pages/GovernancePage.tsx
index 58b0209..796a4e7 100644
--- a/PostFundManagement.ConsoleUI/src/pages/GovernancePage.tsx
+++ b/PostFundManagement.ConsoleUI/src/pages/GovernancePage.tsx
@@ -19,13 +19,16 @@ import {
} from 'lucide-react';
import {
getOrganisations,
+ getOrganisation,
createOrganisation,
updateOrganisation,
getAllPortfolios,
+ getPortfolios,
getPortfolio,
createPortfolio,
updatePortfolio,
getAllProgrammes,
+ getProgrammes,
getProgramme,
createProgramme,
updateProgramme,
@@ -33,6 +36,7 @@ import {
getPortfolioProgrammes,
} from '../services/api';
import { Modal } from '../components/ui/Modal';
+import { HelpTooltip } from '../components/ui/HelpTooltip';
import { useToast } from '../components/ui/Toast';
import { useAuth } from '../auth/AuthProvider';
import {
@@ -111,6 +115,15 @@ export const GovernancePage: React.FC = () => {
const [selectedPortfolioForProgrammes, setSelectedPortfolioForProgrammes] = useState
(null);
const [isPortfolioProgrammesModalOpen, setIsPortfolioProgrammesModalOpen] = useState(false);
+ const [selectedOrgForDetail, setSelectedOrgForDetail] = useState(null);
+ const [isOrgDetailModalOpen, setIsOrgDetailModalOpen] = useState(false);
+
+ const [selectedPortfolioForDetail, setSelectedPortfolioForDetail] = useState(null);
+ const [isPortfolioDetailModalOpen, setIsPortfolioDetailModalOpen] = useState(false);
+
+ const [selectedProgrammeForDetail, setSelectedProgrammeForDetail] = useState(null);
+ const [isProgrammeDetailModalOpen, setIsProgrammeDetailModalOpen] = useState(false);
+
// Queries
const { data: organisations = [], isLoading: loadingOrgs } = useQuery({
queryKey: ['organisations'],
@@ -139,6 +152,36 @@ export const GovernancePage: React.FC = () => {
enabled: !!selectedPortfolioForProgrammes && isPortfolioProgrammesModalOpen,
});
+ const { data: portfolioDirectProgrammes = [] } = useQuery({
+ queryKey: ['portfolio-direct-programmes', selectedPortfolioForProgrammes?.id],
+ queryFn: () => (selectedPortfolioForProgrammes ? getProgrammes(selectedPortfolioForProgrammes.id) : Promise.resolve([])),
+ enabled: !!selectedPortfolioForProgrammes && isPortfolioProgrammesModalOpen,
+ });
+
+ const { data: orgFreshDetail, isLoading: loadingOrgFreshDetail } = useQuery({
+ queryKey: ['organisation', selectedOrgForDetail?.id],
+ queryFn: () => (selectedOrgForDetail ? getOrganisation(selectedOrgForDetail.id) : null),
+ enabled: !!selectedOrgForDetail && isOrgDetailModalOpen,
+ });
+
+ const { data: orgPortfolios = [], isLoading: loadingOrgPortfolios } = useQuery({
+ queryKey: ['organisation-portfolios', selectedOrgForDetail?.id],
+ queryFn: () => (selectedOrgForDetail ? getPortfolios(selectedOrgForDetail.id) : Promise.resolve([])),
+ enabled: !!selectedOrgForDetail && isOrgDetailModalOpen,
+ });
+
+ const { data: portfolioFreshDetail, isLoading: loadingPortfolioFreshDetail } = useQuery({
+ queryKey: ['portfolio', selectedPortfolioForDetail?.id],
+ queryFn: () => (selectedPortfolioForDetail ? getPortfolio(selectedPortfolioForDetail.id) : null),
+ enabled: !!selectedPortfolioForDetail && isPortfolioDetailModalOpen,
+ });
+
+ const { data: programmeFreshDetail, isLoading: loadingProgrammeFreshDetail } = useQuery({
+ queryKey: ['programme', selectedProgrammeForDetail?.id],
+ queryFn: () => (selectedProgrammeForDetail ? getProgramme(selectedProgrammeForDetail.id) : null),
+ enabled: !!selectedProgrammeForDetail && isProgrammeDetailModalOpen,
+ });
+
// Forms
const orgForm = useForm({
resolver: zodResolver(organisationSchema) as any,
@@ -336,6 +379,11 @@ export const GovernancePage: React.FC = () => {
Directory
+
Maintain Master Organisation Registry, Funder Portfolios, and Strategic Programmes hierarchy.
@@ -385,7 +433,13 @@ export const GovernancePage: React.FC = () => {
}`}
>
- Organisations Directory ({organisations.length})
+ Organisations Directory ({organisations.length})
+
setActiveTab('portfolios')}
@@ -396,7 +450,13 @@ export const GovernancePage: React.FC = () => {
}`}
>
- Funder Portfolios ({portfolios.length})
+ Funder Portfolios ({portfolios.length})
+
setActiveTab('programmes')}
@@ -407,7 +467,13 @@ export const GovernancePage: React.FC = () => {
}`}
>
- Programmes Hierarchy ({programmes.length})
+ Programmes Hierarchy ({programmes.length})
+
@@ -494,13 +560,27 @@ export const GovernancePage: React.FC = () => {
- openEditOrg(org)}
- className="inline-flex items-center gap-1 text-xs font-semibold text-slate-200 hover:text-white bg-slate-900/80 hover:bg-slate-800 border border-slate-700/80 px-3 py-1.5 rounded-lg transition shadow-xs cursor-pointer"
- >
-
- Edit
-
+
+ {
+ setSelectedOrgForDetail(org);
+ setIsOrgDetailModalOpen(true);
+ }}
+ className="inline-flex items-center gap-1 text-xs font-semibold text-blue-400 hover:text-white bg-blue-500/10 hover:bg-blue-500/20 border border-blue-500/30 px-2.5 py-1.5 rounded-lg transition shadow-xs cursor-pointer"
+ title="Inspect Organisation (GET /v1/api/governance/organisations/{id})"
+ >
+
+ Inspect
+
+ openEditOrg(org)}
+ className="inline-flex items-center gap-1 text-xs font-semibold text-slate-200 hover:text-white bg-slate-900/80 hover:bg-slate-800 border border-slate-700/80 px-3 py-1.5 rounded-lg transition shadow-xs cursor-pointer"
+ >
+
+ Edit
+
+
);
@@ -544,6 +624,18 @@ export const GovernancePage: React.FC = () => {
Owner:
{getUserEmail(p.ownedBy)}
+
{
+ setSelectedPortfolioForDetail(p);
+ setIsPortfolioDetailModalOpen(true);
+ }}
+ className="inline-flex items-center gap-1 text-xs font-semibold text-cyan-400 hover:text-white bg-cyan-500/10 hover:bg-cyan-500/20 border border-cyan-500/30 px-2.5 py-1 rounded-lg transition shadow-xs cursor-pointer"
+ title="Inspect Portfolio (GET /v1/api/governance/portfolios/{id})"
+ >
+
+ Inspect
+
{
@@ -619,6 +711,18 @@ export const GovernancePage: React.FC = () => {
+
{
+ setSelectedProgrammeForDetail(pr);
+ setIsProgrammeDetailModalOpen(true);
+ }}
+ className="inline-flex items-center gap-1 text-xs font-semibold text-cyan-400 hover:text-white bg-cyan-500/10 hover:bg-cyan-500/20 border border-cyan-500/30 px-2.5 py-1 rounded-lg transition shadow-xs cursor-pointer"
+ title="Inspect Programme (GET /v1/api/projects/programme/{id})"
+ >
+
+ Inspect
+
{
@@ -657,12 +761,21 @@ export const GovernancePage: React.FC = () => {
onClose={() => setIsOrgModalOpen(false)}
title={editingOrg ? 'Edit Organisation Profile' : 'Register New Organisation'}
subtitle="Mandatory master data profile for Recipient or Funder entities (POPIA compliant)"
+ helpText="Maintains formal company or institutional credentials for funding allocation, audited legal standing, and regulatory reporting."
>