{
  "openapi": "3.0.0",
  "paths": {
    "/api/health": {
      "get": {
        "operationId": "HealthController_check",
        "parameters": [],
        "responses": {
          "200": {
            "description": "The Health Check is successful",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "example": "ok"
                    },
                    "info": {
                      "type": "object",
                      "example": {
                        "database": {
                          "status": "up"
                        }
                      },
                      "additionalProperties": {
                        "type": "object",
                        "required": [
                          "status"
                        ],
                        "properties": {
                          "status": {
                            "type": "string"
                          }
                        },
                        "additionalProperties": true
                      },
                      "nullable": true
                    },
                    "error": {
                      "type": "object",
                      "example": {},
                      "additionalProperties": {
                        "type": "object",
                        "required": [
                          "status"
                        ],
                        "properties": {
                          "status": {
                            "type": "string"
                          }
                        },
                        "additionalProperties": true
                      },
                      "nullable": true
                    },
                    "details": {
                      "type": "object",
                      "example": {
                        "database": {
                          "status": "up"
                        }
                      },
                      "additionalProperties": {
                        "type": "object",
                        "required": [
                          "status"
                        ],
                        "properties": {
                          "status": {
                            "type": "string"
                          }
                        },
                        "additionalProperties": true
                      }
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "The Health Check is not successful",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "example": "error"
                    },
                    "info": {
                      "type": "object",
                      "example": {
                        "database": {
                          "status": "up"
                        }
                      },
                      "additionalProperties": {
                        "type": "object",
                        "required": [
                          "status"
                        ],
                        "properties": {
                          "status": {
                            "type": "string"
                          }
                        },
                        "additionalProperties": true
                      },
                      "nullable": true
                    },
                    "error": {
                      "type": "object",
                      "example": {
                        "redis": {
                          "status": "down",
                          "message": "Could not connect"
                        }
                      },
                      "additionalProperties": {
                        "type": "object",
                        "required": [
                          "status"
                        ],
                        "properties": {
                          "status": {
                            "type": "string"
                          }
                        },
                        "additionalProperties": true
                      },
                      "nullable": true
                    },
                    "details": {
                      "type": "object",
                      "example": {
                        "database": {
                          "status": "up"
                        },
                        "redis": {
                          "status": "down",
                          "message": "Could not connect"
                        }
                      },
                      "additionalProperties": {
                        "type": "object",
                        "required": [
                          "status"
                        ],
                        "properties": {
                          "status": {
                            "type": "string"
                          }
                        },
                        "additionalProperties": true
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "summary": "Platform health check — database and memory",
        "tags": [
          "health"
        ],
        "x-public": true
      }
    },
    "/api/admin/api-keys": {
      "get": {
        "operationId": "ApiKeyController_list",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "List API keys with status and last use (secrets are never returned)",
        "tags": [
          "API Keys"
        ],
        "x-permissions": [
          "api_key:manage"
        ],
        "x-permissions-mode": "all"
      },
      "post": {
        "operationId": "ApiKeyController_create",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateApiKeyDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Create an API key; the response holds the secret, shown only this once",
        "tags": [
          "API Keys"
        ],
        "x-permissions": [
          "api_key:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/admin/api-keys/{id}/revoke": {
      "post": {
        "operationId": "ApiKeyController_revoke",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RevokeApiKeyDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Revoke an API key; it stops working immediately",
        "tags": [
          "API Keys"
        ],
        "x-permissions": [
          "api_key:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/me": {
      "get": {
        "operationId": "MeController_me",
        "parameters": [],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MeResponseDto"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer token"
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Get the current authenticated user profile",
        "tags": [
          "me"
        ]
      }
    },
    "/api/integrations/bipo/interfaces": {
      "get": {
        "description": "The full registry of InterfaceCodes evidenced by the authoritative BIPO Postman collection, with the operations each supports per API generation and whether the running provider offers it as a pullable sync resource. Generated data — nothing here is inferred or fabricated.",
        "operationId": "IntegrationsController_bipoInterfaces",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "BIPO InterfaceCode capability catalog",
        "tags": [
          "integrations"
        ],
        "x-permissions": [
          "integration:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/integrations/bipo/refresh-employee": {
      "post": {
        "description": "Delegates to BIPO's RefreshEmployee endpoint (v1: /api2/BIPOPersonnel/RefreshEmployee, v2: /perapi/Process/RefreshEmployee). Optionally scoped by refreshAsAt and an EmployeeRange filter. Every invocation is written to the audit log.",
        "operationId": "IntegrationsController_bipoRefreshEmployee",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BipoRefreshEmployeeDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Ask BIPO to recompute employee data server-side (integration:manage)",
        "tags": [
          "integrations"
        ],
        "x-permissions": [
          "integration:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/integrations/bipo/attachments/{fileId}": {
      "get": {
        "description": "v1-only (GetAttachment). BIPO returns a bare binary body — the file's name/type is not part of the contract, so it is served as octet-stream.",
        "operationId": "IntegrationsController_bipoAttachment",
        "parameters": [
          {
            "name": "fileId",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Download one BIPO attachment as raw bytes (integration:manage)",
        "tags": [
          "integrations"
        ],
        "x-permissions": [
          "integration:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/integrations/health": {
      "get": {
        "description": "Iterates every registered integration provider and pings its health endpoint. Returns per-provider status (up/degraded/down), round-trip latency and advertised capabilities. Fully dynamic — no provider is hardcoded, so newly registered providers appear automatically. Read-only; does not affect any data.",
        "operationId": "IntegrationsController_integrationHealth",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Check integration adapter connectivity",
        "tags": [
          "integrations"
        ]
      }
    },
    "/api/integrations/providers": {
      "get": {
        "operationId": "IntegrationsController_listProviders",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Describe every registered provider (identity + capabilities)",
        "tags": [
          "integrations"
        ]
      }
    },
    "/api/integrations/sync": {
      "post": {
        "description": "Drives the Sync Engine for one (provider, resource, direction). Auth is checked first — if credentials are missing/invalid the run is recorded as FAILED with a clear message and NO mock data is returned.",
        "operationId": "IntegrationsController_runSync",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TriggerSyncDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Trigger a sync run for a provider/resource (requires integration:manage)",
        "tags": [
          "integrations"
        ],
        "x-permissions": [
          "integration:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/integrations/sync-status": {
      "get": {
        "operationId": "IntegrationsController_syncStatus",
        "parameters": [
          {
            "name": "provider",
            "required": false,
            "in": "query",
            "description": "Filter by provider key",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "take",
            "required": false,
            "in": "query",
            "schema": {
              "minimum": 1,
              "maximum": 200,
              "default": 50,
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Recent sync events with failure count (requires integration:read)",
        "tags": [
          "integrations"
        ],
        "x-permissions": [
          "integration:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/integrations/queue/health": {
      "get": {
        "operationId": "IntegrationsController_queueHealth",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Queue health for the Sync Monitor (counts, backlog, dead-letter)",
        "tags": [
          "integrations"
        ],
        "x-permissions": [
          "integration:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/integrations/queue/search": {
      "get": {
        "description": "Search spans queue id, local record, idempotency key, external id, correlation id and error text.",
        "operationId": "IntegrationsController_searchQueue",
        "parameters": [
          {
            "name": "status",
            "required": true,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "provider",
            "required": true,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "resource",
            "required": true,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "direction",
            "required": true,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "q",
            "required": true,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "required": true,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "required": true,
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Paginated, searchable queue listing (Integration Monitor)",
        "tags": [
          "integrations"
        ],
        "x-permissions": [
          "integration:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/integrations/netsuite/postability/{resource}": {
      "get": {
        "description": "Reports the decision-seam verdict the outbound push consults before anything is transmitted: whether every NetSuite field id required for the resource has been verified, which ones have not, and which decision blocks them. Read-only — this is the same answer the push itself gets, so a refused push can be explained without reading the code.",
        "operationId": "IntegrationsController_netsuitePostability",
        "parameters": [
          {
            "name": "resource",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "May this resource be posted to NetSuite yet? (CR-13 field-id gate)",
        "tags": [
          "integrations"
        ],
        "x-permissions": [
          "integration:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/integrations/worker/status": {
      "get": {
        "operationId": "IntegrationsController_workerStatus",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Background sync worker runtime status",
        "tags": [
          "integrations"
        ],
        "x-permissions": [
          "integration:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/integrations/worker/tick": {
      "post": {
        "operationId": "IntegrationsController_workerTick",
        "parameters": [],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Run one drain pass on demand (does not disturb the interval)",
        "tags": [
          "integrations"
        ],
        "x-permissions": [
          "integration:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/integrations/queue/{id}": {
      "get": {
        "operationId": "IntegrationsController_queueItem",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Inspect one queue item — payload, result and error",
        "tags": [
          "integrations"
        ],
        "x-permissions": [
          "integration:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/integrations/queue/{id}/retry": {
      "post": {
        "operationId": "IntegrationsController_retryItem",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Retry a failed/dead job (re-attempts the same item)",
        "tags": [
          "integrations"
        ],
        "x-permissions": [
          "integration:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/integrations/queue/{id}/replay": {
      "post": {
        "description": "Carries the idempotency key over, so replaying a push updates the same remote record rather than duplicating it.",
        "operationId": "IntegrationsController_replayItem",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Replay a finished job as a new queue item",
        "tags": [
          "integrations"
        ],
        "x-permissions": [
          "integration:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/integrations/queue/{id}/cancel": {
      "post": {
        "description": "Refused for a PROCESSING job — cancelling locally would not stop the in-flight remote call — and for one already DONE.",
        "operationId": "IntegrationsController_cancelItem",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Cancel a job where it is safe to do so",
        "tags": [
          "integrations"
        ],
        "x-permissions": [
          "integration:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/integrations/metadata": {
      "get": {
        "operationId": "MetadataController_list",
        "parameters": [
          {
            "name": "provider",
            "required": true,
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "List synced record-type metadata (field/custom counts)",
        "tags": [
          "integrations"
        ],
        "x-permissions": [
          "integration:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/integrations/metadata/{provider}/{recordType}": {
      "get": {
        "operationId": "MetadataController_get",
        "parameters": [
          {
            "name": "provider",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "recordType",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Full detected schema (all fields incl. custom) for a record type",
        "tags": [
          "integrations"
        ],
        "x-permissions": [
          "integration:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/integrations/metadata/sync": {
      "post": {
        "operationId": "MetadataController_sync",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SyncMetadataDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Detect & cache live metadata for one or more record types",
        "tags": [
          "integrations"
        ],
        "x-permissions": [
          "integration:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/integrations/metadata/mappings/{provider}": {
      "get": {
        "operationId": "MetadataController_listMappings",
        "parameters": [
          {
            "name": "provider",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "resource",
            "required": true,
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "List field mappings for a provider (optionally by resource)",
        "tags": [
          "integrations"
        ],
        "x-permissions": [
          "integration:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/integrations/metadata/mappings/seed": {
      "post": {
        "description": "Upserts every mapping named in FINAL Blueprint v2.4 Integration Architecture A.2-A.9 / B.2-B.9 that is NOT Division-carrying. Re-runnable: rows upsert on (provider, resource, localField, direction). Division mappings are deliberately withheld while CR-05 (Custom Segment vs custentity_apergu_division vs native class) is OPEN, and are returned in `cr05Blocked` so the gap stays visible.",
        "operationId": "MetadataController_seedMappings",
        "parameters": [],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Seed the blueprint-declared field mappings (idempotent)",
        "tags": [
          "integrations"
        ],
        "x-permissions": [
          "integration:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/integrations/metadata/mappings": {
      "post": {
        "operationId": "MetadataController_upsertMapping",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpsertFieldMappingDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Create/update a dynamic field mapping",
        "tags": [
          "integrations"
        ],
        "x-permissions": [
          "integration:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/integrations/metadata/mappings/{id}": {
      "delete": {
        "operationId": "MetadataController_deleteMapping",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Delete a field mapping",
        "tags": [
          "integrations"
        ],
        "x-permissions": [
          "integration:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/integrations/metadata/mappings/{provider}/{resource}/validate/{recordType}": {
      "get": {
        "operationId": "MetadataController_validate",
        "parameters": [
          {
            "name": "provider",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "resource",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "recordType",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Validate mappings against detected metadata (flags unknown fields)",
        "tags": [
          "integrations"
        ],
        "x-permissions": [
          "integration:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/integrations/crosswalk/employees/pull": {
      "post": {
        "description": "Runs the BIPO employee pull through the SyncEngine (auth gate + sync event) and upserts EmployeeProfile on bipoEmployeeCode, linking to an RSMID user by email where one matches. Idempotent. Rows BIPO returns without an EmployeeCode are counted in `skippedWithoutCode`, never written under a fabricated key.",
        "operationId": "EmployeeCrosswalkController_pull",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PullEmployeesDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Mirror the BIPO employee master into EmployeeProfile (integration:manage)",
        "tags": [
          "integrations"
        ],
        "x-permissions": [
          "integration:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/integrations/crosswalk/employees/unmatched": {
      "get": {
        "description": "Two lists: BIPO employees with no RSMID user, and active RSMID users with no resolved NetSuite employee id. Pattern A has no automatic fallback, so these are the rows a human must reconcile — they are surfaced rather than skipped.",
        "operationId": "EmployeeCrosswalkController_unmatched",
        "parameters": [
          {
            "name": "take",
            "required": true,
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Everyone the crosswalk could not complete (integration:read)",
        "tags": [
          "integrations"
        ],
        "x-permissions": [
          "integration:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/integrations/crosswalk/employees/{userId}/netsuite-employee-id": {
      "get": {
        "description": "Returns the cached id when one exists. Otherwise performs a single SuiteQL lookup on the NetSuite field that IntegrationFieldMapping says carries the BIPO key, caches the answer on RsmidUser.netsuiteEmployeeId, and returns it. Zero matches, several matches, a missing mapping and a missing BIPO link are all explicit failures — never a default.",
        "operationId": "EmployeeCrosswalkController_resolve",
        "parameters": [
          {
            "name": "userId",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "force",
            "required": true,
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Resolve (once) and cache a user’s NetSuite employee internalId",
        "tags": [
          "integrations"
        ],
        "x-permissions": [
          "integration:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/integrations/crosswalk/employees/link": {
      "post": {
        "description": "The manual reconciliation path for unmatched employees. Supply either side or both. Refuses a link that would give one BIPO employee or one NetSuite employee two RSMID users.",
        "operationId": "EmployeeCrosswalkController_link",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LinkEmployeeDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Link an RSMID user to a BIPO employee and/or a NetSuite employee (integration:manage)",
        "tags": [
          "integrations"
        ],
        "x-permissions": [
          "integration:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/integrations/crosswalk/employees/unlink": {
      "post": {
        "operationId": "EmployeeCrosswalkController_unlink",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UnlinkEmployeeDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Drop a crosswalk link so it can be re-pointed (integration:manage)",
        "tags": [
          "integrations"
        ],
        "x-permissions": [
          "integration:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/dashboard/me": {
      "get": {
        "operationId": "DashboardController_me",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Current user context — profile, division, org, unread notification count",
        "tags": [
          "Dashboard"
        ]
      }
    },
    "/api/dashboard/kpis": {
      "get": {
        "operationId": "DashboardController_kpis",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Dashboard KPI summary (mock in M4, real sync in M5)",
        "tags": [
          "Dashboard"
        ]
      }
    },
    "/api/dashboard/integrations": {
      "get": {
        "operationId": "DashboardController_integrations",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Integration adapter health and last sync stats from DB",
        "tags": [
          "Dashboard"
        ]
      }
    },
    "/api/dashboard/notifications": {
      "get": {
        "operationId": "DashboardController_notifications",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "In-app notifications for the authenticated user (latest 20)",
        "tags": [
          "Dashboard"
        ]
      }
    },
    "/api/timesheets/projects": {
      "get": {
        "operationId": "TimesheetsController_getProjects",
        "parameters": [
          {
            "name": "divisionCode",
            "required": true,
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "List available projects for timesheet entry (from AperguSuite mock)",
        "tags": [
          "Timesheets"
        ]
      }
    },
    "/api/timesheets/projects/{projectId}/tasks": {
      "get": {
        "operationId": "TimesheetsController_getProjectTasks",
        "parameters": [
          {
            "name": "projectId",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "List tasks for a given project",
        "tags": [
          "Timesheets"
        ]
      }
    },
    "/api/timesheets/approvals/pending": {
      "get": {
        "operationId": "TimesheetsController_findPendingApprovals",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Pending timesheets awaiting manager review",
        "tags": [
          "Timesheets"
        ],
        "x-roles": [
          "admin",
          "partner",
          "manager"
        ]
      }
    },
    "/api/timesheets/options/employees": {
      "get": {
        "operationId": "TimesheetsController_getEmployeeOptions",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Employees that have timesheets — reviewer list filter options",
        "tags": [
          "Timesheets"
        ],
        "x-roles": [
          "admin",
          "partner",
          "manager"
        ]
      }
    },
    "/api/timesheets/entries": {
      "get": {
        "operationId": "TimesheetsController_listEntries",
        "parameters": [
          {
            "name": "status",
            "required": false,
            "in": "query",
            "schema": {
              "enum": [
                "DRAFT",
                "SUBMITTED",
                "APPROVED",
                "REJECTED"
              ],
              "type": "string"
            }
          },
          {
            "name": "userId",
            "required": false,
            "in": "query",
            "description": "Filter by userId (managers only)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "scope",
            "required": false,
            "in": "query",
            "description": "Reviewers only: 'all' lists every employee's timesheets instead of the caller's own",
            "schema": {
              "enum": [
                "all"
              ],
              "type": "string"
            }
          },
          {
            "name": "from",
            "required": false,
            "in": "query",
            "description": "Only weeks starting on/after this ISO date",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "required": false,
            "in": "query",
            "description": "Only weeks starting on/before this ISO date",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "projectId",
            "required": false,
            "in": "query",
            "description": "Only timesheets containing entries for this project",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "required": false,
            "in": "query",
            "schema": {
              "default": 1,
              "type": "number"
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "schema": {
              "default": 20,
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Daily time entries (one row per day/project) — own for staff, ?scope=all for reviewers",
        "tags": [
          "Timesheets"
        ]
      },
      "post": {
        "description": "The weekly container is found-or-created automatically for the entry date; refused when that week is already submitted/approved.",
        "operationId": "TimesheetsController_createEntry",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateDailyEntryDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Record one daily time entry",
        "tags": [
          "Timesheets"
        ]
      }
    },
    "/api/timesheets/entries/{entryId}": {
      "patch": {
        "operationId": "TimesheetsController_updateEntry",
        "parameters": [
          {
            "name": "entryId",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateDailyEntryDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Edit a daily entry (its week must be DRAFT/REJECTED)",
        "tags": [
          "Timesheets"
        ]
      },
      "delete": {
        "operationId": "TimesheetsController_deleteEntry",
        "parameters": [
          {
            "name": "entryId",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Delete a daily entry (its week must be DRAFT/REJECTED)",
        "tags": [
          "Timesheets"
        ]
      }
    },
    "/api/timesheets": {
      "get": {
        "operationId": "TimesheetsController_findAll",
        "parameters": [
          {
            "name": "status",
            "required": false,
            "in": "query",
            "schema": {
              "enum": [
                "DRAFT",
                "SUBMITTED",
                "APPROVED",
                "REJECTED"
              ],
              "type": "string"
            }
          },
          {
            "name": "userId",
            "required": false,
            "in": "query",
            "description": "Filter by userId (managers only)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "scope",
            "required": false,
            "in": "query",
            "description": "Reviewers only: 'all' lists every employee's timesheets instead of the caller's own",
            "schema": {
              "enum": [
                "all"
              ],
              "type": "string"
            }
          },
          {
            "name": "from",
            "required": false,
            "in": "query",
            "description": "Only weeks starting on/after this ISO date",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "required": false,
            "in": "query",
            "description": "Only weeks starting on/before this ISO date",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "projectId",
            "required": false,
            "in": "query",
            "description": "Only timesheets containing entries for this project",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "required": false,
            "in": "query",
            "schema": {
              "default": 1,
              "type": "number"
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "schema": {
              "default": 20,
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "List timesheets (own for staff; all for managers with ?userId=)",
        "tags": [
          "Timesheets"
        ]
      },
      "post": {
        "operationId": "TimesheetsController_create",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateTimesheetDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Create a new DRAFT timesheet for a given week",
        "tags": [
          "Timesheets"
        ]
      }
    },
    "/api/timesheets/{id}": {
      "get": {
        "operationId": "TimesheetsController_findOne",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Get a single timesheet with all entries",
        "tags": [
          "Timesheets"
        ]
      },
      "patch": {
        "operationId": "TimesheetsController_update",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateTimesheetDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Update timesheet notes and/or entries (DRAFT or REJECTED only)",
        "tags": [
          "Timesheets"
        ]
      }
    },
    "/api/timesheets/{id}/submit": {
      "post": {
        "operationId": "TimesheetsController_submit",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Submit timesheet for approval (DRAFT/REJECTED → SUBMITTED)",
        "tags": [
          "Timesheets"
        ]
      }
    },
    "/api/timesheets/{id}/approve": {
      "post": {
        "operationId": "TimesheetsController_approve",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReviewTimesheetDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Approve a submitted timesheet (manager/partner/admin only)",
        "tags": [
          "Timesheets"
        ],
        "x-roles": [
          "admin",
          "partner",
          "manager"
        ]
      }
    },
    "/api/timesheets/{id}/reject": {
      "post": {
        "operationId": "TimesheetsController_reject",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReviewTimesheetDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Reject a submitted timesheet with optional notes",
        "tags": [
          "Timesheets"
        ],
        "x-roles": [
          "admin",
          "partner",
          "manager"
        ]
      }
    },
    "/api/timesheets/{id}/approval-requirement": {
      "get": {
        "description": "CR-08 is unresolved: :973 names the Upliner, :1013 names the MIC — a line manager and a per-project role, usually different people. The approval policy reports the required role and marks it unresolvable; it does NOT narrow who may approve. The existing admin/partner/manager guard remains the enforcement point until the decision is taken.",
        "operationId": "TimesheetsController_approvalRequirement",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Who the blueprint requires to approve this timesheet, and whether it is resolvable",
        "tags": [
          "Timesheets"
        ]
      }
    },
    "/api/timesheets/{id}/approval-history": {
      "get": {
        "operationId": "TimesheetsController_approvalHistory",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Approval request history for this timesheet (owner or reviewer)",
        "tags": [
          "Timesheets"
        ]
      }
    },
    "/api/timesheets/{id}/audit": {
      "get": {
        "operationId": "TimesheetsController_audit",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Audit trail for this timesheet (owner or reviewer)",
        "tags": [
          "Timesheets"
        ]
      }
    },
    "/api/timesheets/{id}/push": {
      "post": {
        "description": "Each entry posts as its own NetSuite timeBill — NetSuite exposes no writable weekly container. Entries post independently, so one bad line does not strand the week.",
        "operationId": "TimesheetsController_push",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Post an APPROVED timesheet to NetSuite",
        "tags": [
          "Timesheets"
        ],
        "x-permissions": [
          "timesheet:approve"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/timesheets/{id}/retry": {
      "post": {
        "operationId": "TimesheetsController_retry",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Retry only the entries that failed to post",
        "tags": [
          "Timesheets"
        ],
        "x-permissions": [
          "timesheet:approve"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/timesheets/{id}/sync-status": {
      "get": {
        "operationId": "TimesheetsController_syncStatus",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Per-entry NetSuite posting status for this week",
        "tags": [
          "Timesheets"
        ]
      }
    },
    "/api/timesheets/{id}/entries/{entryId}/sync-history": {
      "get": {
        "operationId": "TimesheetsController_entryHistory",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "entryId",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Queue/sync history for one timesheet entry",
        "tags": [
          "Timesheets"
        ]
      }
    },
    "/api/crm/leads": {
      "get": {
        "operationId": "LeadController_findAll",
        "parameters": [
          {
            "name": "status",
            "required": false,
            "in": "query",
            "description": "Comma-separated statuses.",
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "NEW",
                  "ASSIGNED",
                  "QUALIFIED",
                  "IN_PIPELINE",
                  "CONVERTED",
                  "DISQUALIFIED"
                ]
              }
            }
          },
          {
            "name": "leadType",
            "required": false,
            "in": "query",
            "schema": {
              "enum": [
                "NEW",
                "RECURRING"
              ],
              "type": "string"
            }
          },
          {
            "name": "search",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "ownerId",
            "required": false,
            "in": "query",
            "description": "Filter by owner (CRM managers only).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "List leads (own + own-LOB for staff; firm-wide for CRM managers)",
        "tags": [
          "CRM — Leads"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      },
      "post": {
        "operationId": "LeadController_create",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateLeadDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "BP1.1 — Register a lead (MBD/Partner; recurring may originate from LOB/Division)",
        "tags": [
          "CRM — Leads"
        ],
        "x-permissions": [
          "crm:lead:create"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/leads/{id}": {
      "get": {
        "operationId": "LeadController_findOne",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Get one lead",
        "tags": [
          "CRM — Leads"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      },
      "patch": {
        "operationId": "LeadController_update",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateLeadDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Update lead content. Once IN_PIPELINE / CONVERTED / DISQUALIFIED only the owner (CRM managers) and marketing consent may change.",
        "tags": [
          "CRM — Leads"
        ],
        "x-permissions": [
          "crm:lead:update"
        ],
        "x-permissions-mode": "all"
      },
      "delete": {
        "operationId": "LeadController_remove",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Soft-delete a lead (refused once IN_PIPELINE or CONVERTED)",
        "tags": [
          "CRM — Leads"
        ],
        "x-permissions": [
          "crm:lead:delete"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/leads/{id}/events": {
      "get": {
        "operationId": "LeadController_history",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Immutable domain-event history of a lead",
        "tags": [
          "CRM — Leads"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/leads/{id}/client-matches": {
      "get": {
        "operationId": "LeadController_clientMatchesFor",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "ADVISORY — candidate existing clients for this lead by normalized company name (read-only; changes nothing)",
        "tags": [
          "CRM — Leads"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/leads/{id}/conversion": {
      "get": {
        "operationId": "LeadController_conversion",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "The lead's Customer conversion record (null until it became a Customer)",
        "tags": [
          "CRM — Leads"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/leads/{id}/notify-lob": {
      "post": {
        "operationId": "LeadController_notifyLob",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/NotifyLobDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "BP1.2 — Notify the relevant LOB (MBD). Must precede LOB assignment.",
        "tags": [
          "CRM — Leads"
        ],
        "x-permissions": [
          "crm:lead:notify"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/leads/{id}/assign-lob": {
      "post": {
        "operationId": "LeadController_assignLob",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AssignLobDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "BP1.3 — MANUAL LOB assignment (LOB). Auto-routing is an open item and stays off.",
        "tags": [
          "CRM — Leads"
        ],
        "x-permissions": [
          "crm:lead:assign"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/leads/{id}/define-lob-info": {
      "post": {
        "operationId": "LeadController_defineLobInfo",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DefineLobInfoDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "BP1.4 — Define LOB information (service need / initial scope)",
        "tags": [
          "CRM — Leads"
        ],
        "x-permissions": [
          "crm:lead:define"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/leads/{id}/qualify": {
      "post": {
        "operationId": "LeadController_qualify",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/QualifyLeadDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Qualify the lead (requires LOB assignment + LOB information)",
        "tags": [
          "CRM — Leads"
        ],
        "x-permissions": [
          "crm:lead:qualify"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/leads/{id}/opportunity": {
      "post": {
        "operationId": "LeadController_createOpportunity",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ConvertLeadDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "BP1.5 — Create the opportunity from the QUALIFIED lead (MBD + Partner); the lead moves to IN_PIPELINE",
        "tags": [
          "CRM — Leads"
        ],
        "x-permissions": [
          "crm:lead:convert"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/leads/{id}/convert": {
      "post": {
        "description": "Despite the name this does NOT make the lead a Customer: that happens at engagement-letter approval (ADR-058).",
        "operationId": "LeadController_convert",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ConvertLeadDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "DEPRECATED name of POST :id/opportunity (kept permanently) — creates the opportunity; the lead moves to IN_PIPELINE",
        "tags": [
          "CRM — Leads"
        ],
        "x-permissions": [
          "crm:lead:convert"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/leads/{id}/disqualify": {
      "post": {
        "operationId": "LeadController_disqualify",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DisqualifyLeadDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Disqualify a NEW / ASSIGNED / QUALIFIED lead (reason required; terminal)",
        "tags": [
          "CRM — Leads"
        ],
        "x-permissions": [
          "crm:lead:qualify"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/leads/{id}/convert-to-customer": {
      "post": {
        "operationId": "LeadController_convertToCustomer",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ConvertLeadToCustomerDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Explicit Customer conversion — retry, legacy repair, or link to an existing Customer (idempotent; feature-flagged)",
        "tags": [
          "CRM — Leads"
        ],
        "x-permissions": [
          "crm_admin:customer-govern"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/opportunities": {
      "get": {
        "operationId": "OpportunityController_findAll",
        "parameters": [
          {
            "name": "status",
            "required": false,
            "in": "query",
            "description": "Comma-separated statuses.",
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "OPEN",
                  "ACCEPTANCE",
                  "PROPOSAL",
                  "WON",
                  "LOST"
                ]
              }
            }
          },
          {
            "name": "closingStatus",
            "required": false,
            "in": "query",
            "description": "Comma-separated closing statuses; NONE = not recorded.",
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "WIN",
                  "LOSS",
                  "DECLINE",
                  "CANCEL",
                  "NONE"
                ]
              }
            }
          },
          {
            "name": "search",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "ownerId",
            "required": false,
            "in": "query",
            "description": "Filter by owner (CRM managers only).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "List opportunities (own for staff; firm-wide for CRM managers)",
        "tags": [
          "CRM — Opportunities"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      },
      "post": {
        "description": "A normal new client must come through the qualified Lead flow (BP1); the API refuses the bypass.",
        "operationId": "OpportunityController_create",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateOpportunityDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Direct opportunity creation — RECURRING clients only",
        "tags": [
          "CRM — Opportunities"
        ],
        "x-permissions": [
          "crm:opportunity:create"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/opportunities/{id}": {
      "get": {
        "operationId": "OpportunityController_findOne",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Get one opportunity",
        "tags": [
          "CRM — Opportunities"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      },
      "patch": {
        "operationId": "OpportunityController_update",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateOpportunityDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Update opportunity content (refused once WON/LOST)",
        "tags": [
          "CRM — Opportunities"
        ],
        "x-permissions": [
          "crm:opportunity:update"
        ],
        "x-permissions-mode": "all"
      },
      "delete": {
        "operationId": "OpportunityController_remove",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Soft-delete an opportunity (refused when WON)",
        "tags": [
          "CRM — Opportunities"
        ],
        "x-permissions": [
          "crm:opportunity:delete"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/opportunities/{id}/events": {
      "get": {
        "operationId": "OpportunityController_history",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Immutable domain-event history of an opportunity",
        "tags": [
          "CRM — Opportunities"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/opportunities/{id}/start-acceptance": {
      "post": {
        "operationId": "OpportunityController_startAcceptance",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StartAcceptanceDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "OPEN → ACCEPTANCE; creates the acceptance checklist with default items",
        "tags": [
          "CRM — Opportunities"
        ],
        "x-permissions": [
          "crm:opportunity:stage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/opportunities/{id}/move-to-proposal": {
      "post": {
        "operationId": "OpportunityController_moveToProposal",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "ACCEPTANCE → PROPOSAL; requires a COMPLETED checklist",
        "tags": [
          "CRM — Opportunities"
        ],
        "x-permissions": [
          "crm:opportunity:stage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/opportunities/{id}/baseline": {
      "get": {
        "operationId": "OpportunityController_baseline",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Project Baseline carried over from the accepted proposal (for Project Setup)",
        "tags": [
          "CRM — Opportunities"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/opportunities/{id}/won": {
      "post": {
        "operationId": "OpportunityController_markWon",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "PROPOSAL → WON; requires a client-accepted proposal; carries structure into the baseline",
        "tags": [
          "CRM — Opportunities"
        ],
        "x-permissions": [
          "crm:opportunity:stage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/opportunities/{id}/lost": {
      "post": {
        "operationId": "OpportunityController_markLost",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LoseOpportunityDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Mark LOST with a mandatory reason",
        "tags": [
          "CRM — Opportunities"
        ],
        "x-permissions": [
          "crm:opportunity:stage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/checklist-templates": {
      "get": {
        "operationId": "ChecklistController_listTemplates",
        "parameters": [
          {
            "name": "templateType",
            "required": false,
            "in": "query",
            "schema": {
              "enum": [
                "ACCEPTANCE",
                "ENGAGEMENT_LETTER"
              ],
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "List checklist template versions (global + per-LOB)",
        "tags": [
          "CRM — Checklists"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      },
      "post": {
        "operationId": "ChecklistController_createTemplate",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateTemplateVersionDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Publish a NEW template version (existing versions are immutable)",
        "tags": [
          "CRM — Checklists"
        ],
        "x-permissions": [
          "crm:template:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/checklist-templates/{id}": {
      "delete": {
        "operationId": "ChecklistController_deactivateTemplate",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Deactivate a template version (history retained)",
        "tags": [
          "CRM — Checklists"
        ],
        "x-permissions": [
          "crm:template:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/checklists/by-opportunity/{opportunityId}": {
      "get": {
        "operationId": "ChecklistController_byOpportunity",
        "parameters": [
          {
            "name": "opportunityId",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Get the acceptance checklist of an opportunity",
        "tags": [
          "CRM — Checklists"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/checklists/by-engagement-letter/{elId}": {
      "get": {
        "operationId": "ChecklistController_byEngagementLetter",
        "parameters": [
          {
            "name": "elId",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Get the EL checklist of an engagement letter (the send gate)",
        "tags": [
          "CRM — Checklists"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/checklists/{id}": {
      "get": {
        "operationId": "ChecklistController_findOne",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Get one checklist with items",
        "tags": [
          "CRM — Checklists"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/checklists/{id}/items/{itemId}": {
      "patch": {
        "description": "The substantive check is performed manually OUTSIDE RSMID; this records the outcome.",
        "operationId": "ChecklistController_recordCheck",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "itemId",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RecordCheckDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Record a check result (Pass/Fail/N/A) — evidence MANDATORY for required checks",
        "tags": [
          "CRM — Checklists"
        ],
        "x-permissions": [
          "crm:checklist:update"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/checklists/{id}/items": {
      "post": {
        "operationId": "ChecklistController_addItem",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AddChecklistItemDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Add an engagement-specific item to an open checklist",
        "tags": [
          "CRM — Checklists"
        ],
        "x-permissions": [
          "crm:checklist:update"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/checklists/{id}/submit": {
      "post": {
        "operationId": "ChecklistController_submit",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SubmitChecklistDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Submit for approval (PENDING → IN_REVIEW; all required checks evidenced)",
        "tags": [
          "CRM — Checklists"
        ],
        "x-permissions": [
          "crm:checklist:submit"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/checklists/{id}/approve": {
      "post": {
        "operationId": "ChecklistController_approve",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Approve (IN_REVIEW → APPROVED) — Partner is the documented approver",
        "tags": [
          "CRM — Checklists"
        ],
        "x-permissions": [
          "crm:checklist:approve"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/checklists/{id}/reject": {
      "post": {
        "operationId": "ChecklistController_reject",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RejectChecklistDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Reject (IN_REVIEW → REJECTED) with reason",
        "tags": [
          "CRM — Checklists"
        ],
        "x-permissions": [
          "crm:checklist:approve"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/checklists/{id}/revise": {
      "post": {
        "operationId": "ChecklistController_revise",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Revise a REJECTED checklist (back to PENDING)",
        "tags": [
          "CRM — Checklists"
        ],
        "x-permissions": [
          "crm:checklist:submit"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/proposals": {
      "get": {
        "operationId": "ProposalController_findAll",
        "parameters": [
          {
            "name": "status",
            "required": false,
            "in": "query",
            "schema": {
              "enum": [
                "DRAFT",
                "SUBMITTED",
                "APPROVED",
                "REJECTED",
                "SENT",
                "ACCEPTED"
              ],
              "type": "string"
            }
          },
          {
            "name": "clientDecision",
            "required": false,
            "in": "query",
            "schema": {
              "enum": [
                "PENDING",
                "ACCEPTED",
                "REJECTED"
              ],
              "type": "string"
            }
          },
          {
            "name": "opportunityId",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "search",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "List proposals (own + own-LOB for staff; firm-wide for CRM managers)",
        "tags": [
          "CRM — Proposals"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      },
      "post": {
        "description": "Numbering is centralized — drafts carry no final number until central Admin assigns it.",
        "operationId": "ProposalController_create",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateProposalDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Create a proposal (Admin/LOB PIC; opportunity in PROPOSAL stage with APPROVED checklist)",
        "tags": [
          "CRM — Proposals"
        ],
        "x-permissions": [
          "crm:proposal:create"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/proposals/{id}": {
      "get": {
        "operationId": "ProposalController_findOne",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Get one proposal (incl. structure; rate_internal redacted without permission)",
        "tags": [
          "CRM — Proposals"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      },
      "patch": {
        "operationId": "ProposalController_update",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateProposalDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Update proposal content (DRAFT only)",
        "tags": [
          "CRM — Proposals"
        ],
        "x-permissions": [
          "crm:proposal:update"
        ],
        "x-permissions-mode": "all"
      },
      "delete": {
        "operationId": "ProposalController_remove",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Soft-delete a proposal (DRAFT / REJECTED only; never one linked to a Customer)",
        "tags": [
          "CRM — Proposals"
        ],
        "x-permissions": [
          "crm:proposal:delete"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/proposals/{id}/approval-chain": {
      "get": {
        "description": "Blueprint :550-552 — <1M DCOO/EP, 1M-5M MP LOB, >5M or Strategic Client CEP. INFORMATIONAL AND NOT ENFORCED: every step is reported with `resolvable`, and while OQ-17 is unanswered no blueprint role maps to a system identity, so the enforcement point remains the route permission plus the PIC business-role check. `gaps` lists what cannot be evaluated (including OQ-14, which leaves Strategic Client undefined).",
        "operationId": "ProposalController_approvalChain",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "The approval chain the blueprint fee ladder requires for this proposal",
        "tags": [
          "CRM — Proposals"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/proposals/{id}/events": {
      "get": {
        "operationId": "ProposalController_history",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Immutable domain-event history of a proposal",
        "tags": [
          "CRM — Proposals"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/proposals/{id}/structure": {
      "put": {
        "operationId": "ProposalController_replaceStructure",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReplaceProjectStructureDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "BP3.7 — Replace the flat role/grade Project Structure (DRAFT only, no named resources)",
        "tags": [
          "CRM — Proposals"
        ],
        "x-permissions": [
          "crm:structure:update"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/proposals/{id}/assignments": {
      "get": {
        "operationId": "ProposalController_listAssignments",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Assignment lines of a proposal (SWA fields only for crm:assignment:read-swa holders)",
        "tags": [
          "CRM — Proposals"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      },
      "post": {
        "operationId": "ProposalController_createAssignment",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpsertProposalAssignmentDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Add an assignment line (DRAFT only); proposal amount := Σ proposedFee",
        "tags": [
          "CRM — Proposals"
        ],
        "x-permissions": [
          "crm:proposal:update"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/proposals/{id}/assignments/{aid}": {
      "patch": {
        "operationId": "ProposalController_updateAssignment",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "aid",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpsertProposalAssignmentDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Update an assignment line (DRAFT only, expectedVersion lock); proposal amount := Σ proposedFee",
        "tags": [
          "CRM — Proposals"
        ],
        "x-permissions": [
          "crm:proposal:update"
        ],
        "x-permissions-mode": "all"
      },
      "delete": {
        "operationId": "ProposalController_removeAssignment",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "aid",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Remove an assignment line (DRAFT only); proposal amount := Σ proposedFee of the rest",
        "tags": [
          "CRM — Proposals"
        ],
        "x-permissions": [
          "crm:proposal:update"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/proposals/{id}/submit": {
      "post": {
        "operationId": "ProposalController_submit",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "DRAFT → SUBMITTED (gates: type, service, billing type; TOR mandatory for Tender; Fixed-Fee budget hours)",
        "tags": [
          "CRM — Proposals"
        ],
        "x-permissions": [
          "crm:proposal:submit"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/proposals/{id}/approve": {
      "post": {
        "description": "Matrix thresholds (amount / service / risk) are an OPEN item — the provisional configuration is a single Approval Authority / Partner level, flagged pendingBusinessConfirmation.",
        "operationId": "ProposalController_approve",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Approve via the configured approval matrix",
        "tags": [
          "CRM — Proposals"
        ],
        "x-permissions": [
          "crm:proposal:approve"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/proposals/{id}/reject": {
      "post": {
        "operationId": "ProposalController_reject",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RejectProposalDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Reject / require revision (approval matrix decision)",
        "tags": [
          "CRM — Proposals"
        ],
        "x-permissions": [
          "crm:proposal:approve"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/proposals/{id}/revise": {
      "post": {
        "operationId": "ProposalController_revise",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "REJECTED → DRAFT (version bump)",
        "tags": [
          "CRM — Proposals"
        ],
        "x-permissions": [
          "crm:proposal:update"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/proposals/{id}/assign-number": {
      "post": {
        "operationId": "ProposalController_assignNumber",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Assign the FINAL proposal number — central Admin only (numbering is centralized)",
        "tags": [
          "CRM — Proposals"
        ],
        "x-permissions": [
          "crm_admin:proposal-number"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/proposals/{id}/send": {
      "post": {
        "operationId": "ProposalController_send",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "APPROVED (+ numbered) → SENT; starts the aging monitor",
        "tags": [
          "CRM — Proposals"
        ],
        "x-permissions": [
          "crm:proposal:send"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/proposals/{id}/accept": {
      "post": {
        "operationId": "ProposalController_accept",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "SENT → ACCEPTED (client decision)",
        "tags": [
          "CRM — Proposals"
        ],
        "x-permissions": [
          "crm:proposal:send"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/proposals/{id}/client-reject": {
      "post": {
        "operationId": "ProposalController_clientReject",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RejectProposalDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Record a client rejection (status stays SENT for follow-up/aging)",
        "tags": [
          "CRM — Proposals"
        ],
        "x-permissions": [
          "crm:proposal:send"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/engagement-letters": {
      "get": {
        "operationId": "EngagementLetterController_findAll",
        "parameters": [
          {
            "name": "status",
            "required": false,
            "in": "query",
            "schema": {
              "enum": [
                "DRAFT",
                "REVIEWED",
                "SENT",
                "APPROVED",
                "CANCELLED",
                "QA_REVIEW",
                "DECLINED"
              ],
              "type": "string"
            }
          },
          {
            "name": "opportunityId",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "search",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "List engagement letters (own for staff; firm-wide for CRM managers)",
        "tags": [
          "CRM — Engagement Letters"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      },
      "post": {
        "operationId": "EngagementLetterController_create",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateEngagementLetterDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Create an EL from an ACCEPTED proposal",
        "tags": [
          "CRM — Engagement Letters"
        ],
        "x-permissions": [
          "crm:el:create"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/engagement-letters/{id}": {
      "get": {
        "operationId": "EngagementLetterController_findOne",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Get one engagement letter",
        "tags": [
          "CRM — Engagement Letters"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      },
      "patch": {
        "operationId": "EngagementLetterController_update",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateEngagementLetterDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Update EL content incl. CF (DRAFT only — CF freezes at approval)",
        "tags": [
          "CRM — Engagement Letters"
        ],
        "x-permissions": [
          "crm:el:update"
        ],
        "x-permissions-mode": "all"
      },
      "delete": {
        "operationId": "EngagementLetterController_remove",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Soft-delete an EL (DRAFT only; never one linked to a Customer — cancel it instead)",
        "tags": [
          "CRM — Engagement Letters"
        ],
        "x-permissions": [
          "crm:el:delete"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/engagement-letters/{id}/events": {
      "get": {
        "operationId": "EngagementLetterController_history",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Immutable domain-event history of an EL",
        "tags": [
          "CRM — Engagement Letters"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/engagement-letters/{id}/review": {
      "post": {
        "operationId": "EngagementLetterController_review",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "DRAFT → REVIEWED",
        "tags": [
          "CRM — Engagement Letters"
        ],
        "x-permissions": [
          "crm:el:review"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/engagement-letters/{id}/qa-review": {
      "post": {
        "description": "Refused with EL_QA_REVIEW_NOT_AUDIT unless at least one assignment is of type Audit.",
        "operationId": "EngagementLetterController_qaReview",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/QaReviewEngagementLetterDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "REVIEWED → QA_REVIEW (\"QA Review (Audit only)\")",
        "tags": [
          "CRM — Engagement Letters"
        ],
        "x-permissions": [
          "crm:el:review"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/engagement-letters/{id}/decline": {
      "post": {
        "operationId": "EngagementLetterController_decline",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DeclineEngagementLetterDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "DRAFT / REVIEWED / QA_REVIEW / SENT → DECLINED (client declined; reason required; never after approval)",
        "tags": [
          "CRM — Engagement Letters"
        ],
        "x-permissions": [
          "crm:el:approve"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/engagement-letters/{id}/send": {
      "post": {
        "operationId": "EngagementLetterController_send",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "REVIEWED / QA_REVIEW → SENT",
        "tags": [
          "CRM — Engagement Letters"
        ],
        "x-permissions": [
          "crm:el:send"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/engagement-letters/{id}/approve": {
      "post": {
        "description": "Gate: assignments must reconcile (SUM=cf, SUM%=100). Creates the LOCAL customer record and runs the LOCAL duplicate check. Nothing is pushed to the ERP here — no Customer, no Project, no Estimate, no Sales Order. With crm.customer-lifecycle ON the duplicate check runs BEFORE any Customer is created, and the lead is converted into its Customer (linked or new) in the same transaction; a likely duplicate waits for the dedupe decision. The optional body records the signature (signedDate → EL Signed journey event; attachmentReference = final signed file).",
        "operationId": "EngagementLetterController_approve",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ApproveEngagementLetterDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "SENT → APPROVED — freezes CF and the assignment set (all local)",
        "tags": [
          "CRM — Engagement Letters"
        ],
        "x-permissions": [
          "crm:el:approve"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/engagement-letters/{id}/cancel": {
      "post": {
        "description": "Decided by the approval tier (Partner / Approval Authority / Admin). An APPROVED EL changes only through an amendment.",
        "operationId": "EngagementLetterController_cancel",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CancelEngagementLetterDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "DRAFT / REVIEWED / QA_REVIEW / SENT → CANCELLED (reason required; never after approval)",
        "tags": [
          "CRM — Engagement Letters"
        ],
        "x-permissions": [
          "crm:el:approve"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/engagement-letters/{id}/amendments": {
      "get": {
        "operationId": "EngagementLetterController_amendments",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Amendment versions of this EL",
        "tags": [
          "CRM — Engagement Letters"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      },
      "post": {
        "description": "PROSPECTIVE ONLY: existing assignments must remain; actuals and Projects never move; new assignments start empty and receive Projects later via Project Setup.",
        "operationId": "EngagementLetterController_createAmendment",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateAmendmentDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Create an EL Amendment (APPROVED ELs only; the full post-amendment assignment set)",
        "tags": [
          "CRM — Engagement Letters"
        ],
        "x-permissions": [
          "crm:el:amend"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/engagement-letters/{id}/amendments/{amendmentId}/submit": {
      "post": {
        "operationId": "EngagementLetterController_submitAmendment",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "amendmentId",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Submit the amendment for approval",
        "tags": [
          "CRM — Engagement Letters"
        ],
        "x-permissions": [
          "crm:el:amend"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/engagement-letters/{id}/amendments/{amendmentId}/approve": {
      "post": {
        "operationId": "EngagementLetterController_approveAmendment",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "amendmentId",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Approve the amendment via the configured matrix (open item) — applies prospectively",
        "tags": [
          "CRM — Engagement Letters"
        ],
        "x-permissions": [
          "crm:el:approve"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/engagement-letters/{id}/amendments/{amendmentId}/reject": {
      "post": {
        "operationId": "EngagementLetterController_rejectAmendment",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "amendmentId",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DecideAmendmentDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Reject the amendment",
        "tags": [
          "CRM — Engagement Letters"
        ],
        "x-permissions": [
          "crm:el:approve"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/engagement-letters/{id}/customer": {
      "get": {
        "operationId": "EngagementLetterController_customer",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Customer record + dedupe verdict + sync state",
        "tags": [
          "CRM — Engagement Letters"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/engagement-letters/{id}/customer/check-duplicates": {
      "post": {
        "operationId": "EngagementLetterController_recheck",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Re-run the fuzzy duplicate check",
        "tags": [
          "CRM — Engagement Letters"
        ],
        "x-permissions": [
          "crm:customer:sync"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/engagement-letters/{id}/customer/dedupe-decision": {
      "post": {
        "description": "With crm.customer-lifecycle ON the decision also settles the Customer and converts the lead in the same transaction.",
        "operationId": "EngagementLetterController_decide",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DedupeDecisionDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Decide a SUSPECT duplicate: override-create or link the existing customer",
        "tags": [
          "CRM — Engagement Letters"
        ],
        "x-permissions": [
          "crm:customer:dedupe-decide"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/engagement-letters/{id}/customer/push": {
      "post": {
        "description": "The ONLY action that requests an ERP sync. Requires the EL APPROVED and a CLEAR or OVERRIDE_APPROVED duplicate verdict. Returns immediately; an unreachable ERP shows up as sync status FAILED with retry, never as a blocked business operation.",
        "operationId": "EngagementLetterController_pushToErp",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Explicitly push the customer to NetSuite (async — the worker transmits)",
        "tags": [
          "CRM — Engagement Letters"
        ],
        "x-permissions": [
          "crm:customer:sync"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/engagement-letters/{id}/customer/retry-sync": {
      "post": {
        "operationId": "EngagementLetterController_retrySync",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Re-enqueue a failed customer push (async — never waits on the ERP)",
        "tags": [
          "CRM — Engagement Letters"
        ],
        "x-permissions": [
          "crm:customer:sync"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/engagement-letters/{id}/customer/sync-history": {
      "get": {
        "operationId": "EngagementLetterController_syncHistory",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Queue history of the customer push",
        "tags": [
          "CRM — Engagement Letters"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/assignments": {
      "get": {
        "operationId": "AssignmentController_findAll",
        "parameters": [
          {
            "name": "engagementLetterId",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lobId",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "List assignments (LOB-isolated; SWA redacted without read-swa)",
        "tags": [
          "CRM — Assignments"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/engagement-letters/{elId}/assignments": {
      "get": {
        "operationId": "AssignmentController_byEl",
        "parameters": [
          {
            "name": "elId",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Assignments of one EL",
        "tags": [
          "CRM — Assignments"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      },
      "post": {
        "operationId": "AssignmentController_create",
        "parameters": [
          {
            "name": "elId",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpsertAssignmentDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Add an assignment (one-of prorate amount/percentage; frozen once the EL is APPROVED)",
        "tags": [
          "CRM — Assignments"
        ],
        "x-permissions": [
          "crm:assignment:create"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/engagement-letters/{elId}/assignments/validate": {
      "get": {
        "operationId": "AssignmentController_validate",
        "parameters": [
          {
            "name": "elId",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Validate the set: SUM(prorate)=cf and SUM(%)=100 (Decimal-exact)",
        "tags": [
          "CRM — Assignments"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/assignments/{id}": {
      "patch": {
        "operationId": "AssignmentController_update",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateAssignmentDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Update an assignment (frozen once the EL is APPROVED)",
        "tags": [
          "CRM — Assignments"
        ],
        "x-permissions": [
          "crm:assignment:update"
        ],
        "x-permissions-mode": "all"
      },
      "delete": {
        "operationId": "AssignmentController_remove",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Remove an assignment (frozen once the EL is APPROVED)",
        "tags": [
          "CRM — Assignments"
        ],
        "x-permissions": [
          "crm:assignment:update"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/assignments/{id}/cf-penyesuaian": {
      "patch": {
        "description": "The cf_penyesuaian approval workflow is an OPEN item; the input is audited with a mandatory reason.",
        "operationId": "AssignmentController_adjustCf",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AdjustAssignmentCfDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "cf_penyesuaian — manual central-Admin input; cf_selisih computed server-side",
        "tags": [
          "CRM — Assignments"
        ],
        "x-permissions": [
          "crm_admin:assignment-cf"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/kam": {
      "get": {
        "operationId": "KamController_findAll",
        "parameters": [
          {
            "name": "search",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "required": false,
            "in": "query",
            "description": "Plan status; one value or several comma-separated.",
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "DRAFT",
                  "SUBMITTED",
                  "APPROVED",
                  "REJECTED"
                ]
              }
            }
          },
          {
            "name": "page",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "List key accounts (own for staff; firm-wide for CRM managers)",
        "tags": [
          "CRM — Key Account Management"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      },
      "post": {
        "operationId": "KamController_create",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateKamAccountDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Create a key account and its Draft strategic account plan",
        "tags": [
          "CRM — Key Account Management"
        ],
        "x-permissions": [
          "crm:kam:create"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/kam/options": {
      "get": {
        "operationId": "KamController_options",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Key account picker (Lead form KAM Reference) — id / code / name / status",
        "tags": [
          "CRM — Key Account Management"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/kam/{id}": {
      "get": {
        "operationId": "KamController_findOne",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Get one key account with its plan, logs, history, related records and notes",
        "tags": [
          "CRM — Key Account Management"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      },
      "patch": {
        "operationId": "KamController_update",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateKamAccountDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Update a key account (plan fields lock while Pending Approval / Approved; logs stay editable)",
        "tags": [
          "CRM — Key Account Management"
        ],
        "x-permissions": [
          "crm:kam:update"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/kam/{id}/submit": {
      "post": {
        "operationId": "KamController_submit",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/KamTransitionDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Draft / Rejected → Pending Approval (requires a complete plan)",
        "tags": [
          "CRM — Key Account Management"
        ],
        "x-permissions": [
          "crm:kam:submit"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/kam/{id}/approve": {
      "post": {
        "operationId": "KamController_approve",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/KamTransitionDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Pending Approval → Approved (Industry Leader)",
        "tags": [
          "CRM — Key Account Management"
        ],
        "x-permissions": [
          "crm:kam:approve"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/kam/{id}/reject": {
      "post": {
        "operationId": "KamController_reject",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RejectKamDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Pending Approval → Rejected with a mandatory reason",
        "tags": [
          "CRM — Key Account Management"
        ],
        "x-permissions": [
          "crm:kam:approve"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/dashboard/operational": {
      "get": {
        "operationId": "CrmWorkspaceController_operationalDashboard",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Operational dashboard — real work queues (approvals inbox, my work, pipeline, deadlines, customer sync), LOB-scoped",
        "tags": [
          "CRM — Workspace"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/leads/{id}/workspace": {
      "get": {
        "operationId": "CrmWorkspaceController_leadWorkspace",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Operational workspace projection for a lead",
        "tags": [
          "CRM — Workspace"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/opportunities/{id}/workspace": {
      "get": {
        "operationId": "CrmWorkspaceController_opportunityWorkspace",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Operational workspace projection for an opportunity",
        "tags": [
          "CRM — Workspace"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/proposals/{id}/workspace": {
      "get": {
        "operationId": "CrmWorkspaceController_proposalWorkspace",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Operational workspace projection for a proposal",
        "tags": [
          "CRM — Workspace"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/engagement-letters/{id}/workspace": {
      "get": {
        "operationId": "CrmWorkspaceController_engagementLetterWorkspace",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Operational workspace projection for an engagement letter",
        "tags": [
          "CRM — Workspace"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/my-context": {
      "get": {
        "operationId": "CrmWorkspaceController_myContext",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "The caller's CRM business context (PIC roles, LOB, division)",
        "tags": [
          "CRM — Workspace"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/business-contexts": {
      "get": {
        "operationId": "CrmWorkspaceController_contexts",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "All explicit business-context assignments (central Admin)",
        "tags": [
          "CRM — Workspace"
        ],
        "x-permissions": [
          "crm_admin:context-manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/business-contexts/{userId}": {
      "put": {
        "operationId": "CrmWorkspaceController_setContext",
        "parameters": [
          {
            "name": "userId",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetBusinessContextDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Assign a user's business roles + LOB/Division (PIC model, central Admin)",
        "tags": [
          "CRM — Workspace"
        ],
        "x-permissions": [
          "crm_admin:context-manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/customers": {
      "get": {
        "operationId": "CrmWorkspaceController_customers",
        "parameters": [
          {
            "name": "dedupeStatus",
            "required": false,
            "in": "query",
            "schema": {
              "enum": [
                "UNCHECKED",
                "CLEAR",
                "SUSPECT",
                "OVERRIDE_APPROVED",
                "LINKED_EXISTING"
              ],
              "type": "string"
            }
          },
          {
            "name": "search",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Customer register (scoped by engagement-letter ownership; firm-wide for CRM managers)",
        "tags": [
          "CRM — Workspace"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/activities": {
      "get": {
        "operationId": "CrmWorkspaceController_activities",
        "parameters": [
          {
            "name": "eventType",
            "required": false,
            "in": "query",
            "description": "Filter by event type, e.g. LeadCreated.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "aggregateType",
            "required": false,
            "in": "query",
            "description": "Filter by aggregate, e.g. CrmProposal.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Activity feed — immutable CRM domain events, newest first",
        "tags": [
          "CRM — Workspace"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/settings": {
      "get": {
        "operationId": "CrmWorkspaceController_settings",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "CRM application configuration snapshot (read-only, live values)",
        "tags": [
          "CRM — Workspace"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/masters": {
      "get": {
        "operationId": "CrmMasterController_reference",
        "parameters": [
          {
            "name": "type",
            "required": false,
            "in": "query",
            "schema": {
              "enum": [
                "INDUSTRY",
                "SECTOR",
                "SERVICE",
                "OFFICE",
                "PARTNER",
                "MANAGER",
                "STATUS",
                "PROBABILITY",
                "CURRENCY",
                "ASSIGNMENT_ROLE",
                "BUSINESS_UNIT",
                "LEAD_SOURCE",
                "ROLE_GRADE",
                "ADDRESS_TYPE",
                "COMM_CHANNEL",
                "QUALIFICATION_SCORE",
                "CLIENT_TYPE",
                "CLASS",
                "LOCATION",
                "PAYMENT_TERM",
                "QA_REVIEW_STATUS",
                "SWA_TYPE",
                "PROJECT_TEMPLATE",
                "ASSIGNMENT_TYPE"
              ],
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Reference data (industries, services, currencies, …) — local",
        "tags": [
          "CRM — Masters"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/masters/lines-of-business": {
      "get": {
        "operationId": "CrmMasterController_linesOfBusiness",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Lines of business (for lead notify / assign) — real LineOfBusiness rows",
        "tags": [
          "CRM — Masters"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/masters/entities": {
      "get": {
        "operationId": "CrmMasterController_entities",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Subsidiaries (legal entities) for lead / opportunity classification — real Entity rows",
        "tags": [
          "CRM — Masters"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/masters/divisions": {
      "get": {
        "operationId": "CrmMasterController_divisions",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Divisions for lead / opportunity classification — real Division rows",
        "tags": [
          "CRM — Masters"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/masters/partners": {
      "get": {
        "operationId": "CrmMasterController_partners",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Partners (Lead form lookup) — employee master ∪ PARTNER reference rows",
        "tags": [
          "CRM — Masters"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/masters/employees": {
      "get": {
        "operationId": "CrmMasterController_employees",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Employees (Resources picker) — active employee master; level = jobGrade ?? position",
        "tags": [
          "CRM — Masters"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/masters/customers": {
      "get": {
        "operationId": "CrmMasterController_customers",
        "parameters": [
          {
            "name": "search",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Customer register (dedupe source) — local",
        "tags": [
          "CRM — Masters"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/masters/customers/data-quality": {
      "get": {
        "description": "Read-only. Reports gaps only; nothing is derived or gated (ADR-057).",
        "operationId": "CrmMasterController_customerDataQuality",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Classification data-quality check — active clients missing classification fields (counts + ids)",
        "tags": [
          "CRM — Masters"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/masters/customers/{id}": {
      "get": {
        "operationId": "CrmMasterController_customer",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "One client record — classification, finance fields and parent group",
        "tags": [
          "CRM — Masters"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/masters/customers/{id}/profile": {
      "patch": {
        "description": "Locally owned fields: never pushed to NetSuite (OQ-34) and never overwritten by the ERP refresh. Captured manually pending ratification of the KAM Policy 2027 — no derivation or approval routing.",
        "operationId": "CrmMasterController_updateCustomerProfile",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateCustomerProfileDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Edit the client profile (classification, parent group, A.3 finance fields) — manual capture",
        "tags": [
          "CRM — Masters"
        ],
        "x-permissions": [
          "crm:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/masters/refresh": {
      "post": {
        "description": "Refuses cleanly when NETSUITE_ENABLED=false or the ERP is unreachable — local masters remain authoritative either way. LOCAL rows are never overwritten.",
        "operationId": "CrmMasterController_refresh",
        "parameters": [],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Merge ERP master data into the local registers (explicit, async-tolerant)",
        "tags": [
          "CRM — Masters"
        ],
        "x-permissions": [
          "crm:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/clients": {
      "get": {
        "description": "Default source APP: Customers of the App lifecycle. includePreCustomers=true appends IN_PIPELINE leads that are not a Customer yet. Scoped to crm:manage, owned documents or the caller's LOB.",
        "operationId": "ClientController_list",
        "parameters": [
          {
            "name": "search",
            "required": false,
            "in": "query",
            "description": "Company name, normalized name or NetSuite Customer ID.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "isActive",
            "required": false,
            "in": "query",
            "description": "Artifact Status: true = Active, false = Inactive. Pre-customer rows have no status.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "lobId",
            "required": false,
            "in": "query",
            "description": "LineOfBusiness id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "entityId",
            "required": false,
            "in": "query",
            "description": "Subsidiary (Entity id).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "source",
            "required": false,
            "in": "query",
            "description": "APP (default): Customers of the App lifecycle. NETSUITE: rows mirrored from the ERP. ALL: the whole register.",
            "schema": {
              "default": "APP",
              "enum": [
                "APP",
                "NETSUITE",
                "ALL"
              ],
              "type": "string"
            }
          },
          {
            "name": "includePreCustomers",
            "required": false,
            "in": "query",
            "description": "Also list IN_PIPELINE leads that are not a Customer yet (\"Not yet a Customer\"), after the Customers.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "page",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Client list — one row per company (NetSuite Customer ID / Company / LOB / Subsidiary / Status)",
        "tags": [
          "CRM — Clients"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/clients/{id}": {
      "get": {
        "operationId": "ClientController_get",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "One Customer — identity, profile, origin / converted leads, conversions and provisioning records",
        "tags": [
          "CRM — Clients"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/clients/{id}/identity": {
      "patch": {
        "description": "Optimistically locked (expectedVersion). 400 NETSUITE_OWNED on NetSuite-sourced rows. Audited and recorded as a CUSTOMER_UPDATED journey event. Existing documents keep their snapshot; new documents use the new values.",
        "operationId": "ClientController_updateIdentity",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateClientIdentityDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Edit the Customer identity (name, PIC, email, phone, address, website) — LOCAL rows only",
        "tags": [
          "CRM — Clients"
        ],
        "x-permissions": [
          "crm:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/clients/{id}/status": {
      "patch": {
        "operationId": "ClientController_setStatus",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetClientStatusDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Set the Customer status (Active / Inactive) — LOCAL rows only, optimistically locked",
        "tags": [
          "CRM — Clients"
        ],
        "x-permissions": [
          "crm:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/clients/{id}/journey": {
      "get": {
        "description": "Events outside the caller's access are dropped and counted in hiddenCount.",
        "operationId": "ClientController_journey",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "cursor",
            "required": false,
            "in": "query",
            "description": "Opaque keyset cursor from the previous page.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "schema": {
              "minimum": 1,
              "maximum": 100,
              "default": 50,
              "type": "number"
            }
          },
          {
            "name": "order",
            "required": false,
            "in": "query",
            "description": "asc = oldest first.",
            "schema": {
              "default": "asc",
              "enum": [
                "asc",
                "desc"
              ],
              "type": "string"
            }
          },
          {
            "name": "types",
            "required": false,
            "in": "query",
            "description": "Comma-separated event types.",
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "LEAD_REGISTERED",
                  "LEAD_UPDATED",
                  "LEAD_DISQUALIFIED",
                  "LEAD_CONVERTED",
                  "CUSTOMER_CREATED",
                  "CUSTOMER_UPDATED",
                  "OPPORTUNITY_CREATED",
                  "OPPORTUNITY_WON",
                  "OPPORTUNITY_LOST",
                  "PROPOSAL_DRAFTED",
                  "PROPOSAL_SUBMITTED",
                  "PROPOSAL_APPROVED",
                  "PROPOSAL_REJECTED",
                  "PROPOSAL_SENT",
                  "EL_CREATED",
                  "EL_APPROVED",
                  "EL_SIGNED",
                  "EL_CANCELLED",
                  "PROJECT_CREATED",
                  "PROJECT_BASELINE_RELEASED",
                  "PROJECT_COMPLETED",
                  "PROJECT_CLOSED",
                  "TIMESHEET_STARTED",
                  "IIR_SUBMITTED",
                  "INVOICE_GENERATED",
                  "ARCHIVE_COMPLETED",
                  "KAM_ACCOUNT_ACTIVATED",
                  "KAM_ACTIVITY_RECORDED"
                ]
              }
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Customer Journey — chronological, keyset-paginated, filtered per event by access to its source",
        "tags": [
          "CRM — Clients"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/clients/{id}/system-notes": {
      "get": {
        "operationId": "ClientController_systemNotes",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "cursor",
            "required": false,
            "in": "query",
            "description": "Opaque keyset cursor from the previous page.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "description": "Audit entries per page.",
            "schema": {
              "minimum": 1,
              "maximum": 100,
              "default": 50,
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "System Notes — the Customer's audit trail, one line per changed field, newest first",
        "tags": [
          "CRM — Clients"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/sectors": {
      "get": {
        "operationId": "SectorController_findAll",
        "parameters": [
          {
            "name": "search",
            "required": false,
            "in": "query",
            "description": "Case-insensitive match on code or sector.",
            "schema": {
              "maxLength": 100,
              "type": "string"
            }
          },
          {
            "name": "status",
            "required": false,
            "in": "query",
            "schema": {
              "default": "all",
              "enum": [
                "active",
                "inactive",
                "all"
              ],
              "type": "string"
            }
          },
          {
            "name": "page",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "List sectors (master list used by the Lead Sector field), with lead counts",
        "tags": [
          "CRM — Sector"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      },
      "post": {
        "operationId": "SectorController_create",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateSectorDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Create a sector (Active, LOCAL); the code is immutable afterwards",
        "tags": [
          "CRM — Sector"
        ],
        "x-permissions": [
          "crm:sector:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/sectors/{id}": {
      "get": {
        "operationId": "SectorController_findOne",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Get one sector with its related leads and system notes",
        "tags": [
          "CRM — Sector"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      },
      "patch": {
        "operationId": "SectorController_update",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateSectorDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Update a sector (Sector, Industry Type, Sort Order); leads keep the label they store",
        "tags": [
          "CRM — Sector"
        ],
        "x-permissions": [
          "crm:sector:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/sectors/{id}/deactivate": {
      "post": {
        "operationId": "SectorController_deactivate",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SectorTransitionDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Active → Inactive (removed from the Lead Sector select; existing leads keep it)",
        "tags": [
          "CRM — Sector"
        ],
        "x-permissions": [
          "crm:sector:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/sectors/{id}/activate": {
      "post": {
        "operationId": "SectorController_activate",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SectorTransitionDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Inactive → Active",
        "tags": [
          "CRM — Sector"
        ],
        "x-permissions": [
          "crm:sector:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/vendors": {
      "get": {
        "operationId": "VendorController_findAll",
        "parameters": [
          {
            "name": "search",
            "required": false,
            "in": "query",
            "description": "Case-insensitive match on company name, Vendor ID, legal name, category, email, tax ID.",
            "schema": {
              "maxLength": 100,
              "type": "string"
            }
          },
          {
            "name": "status",
            "required": false,
            "in": "query",
            "description": "Active / Inactive (NetSuite isinactive); one value or several comma-separated.",
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "ACTIVE",
                  "INACTIVE"
                ]
              }
            }
          },
          {
            "name": "page",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "List NetSuite vendors (live pull; search, Active / Inactive filter, paged)",
        "tags": [
          "CRM — Vendor (NetSuite, read-only)"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/vendors/options": {
      "get": {
        "operationId": "VendorController_options",
        "parameters": [
          {
            "name": "search",
            "required": false,
            "in": "query",
            "schema": {
              "maxLength": 100,
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Vendor picker (SPB Vendor) — active NetSuite vendors, max 500",
        "tags": [
          "CRM — Vendor (NetSuite, read-only)"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/vendors/{id}": {
      "get": {
        "operationId": "VendorController_findOne",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "One NetSuite vendor (by internal id) with system notes and related SPB Vendor submissions",
        "tags": [
          "CRM — Vendor (NetSuite, read-only)"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/projects": {
      "get": {
        "operationId": "CrmProjectsController_findAll",
        "parameters": [
          {
            "name": "search",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "required": false,
            "in": "query",
            "description": "Stage code(s), comma-separated (DRAFT = Planning not yet submitted, SUBMITTED = Planning submitted).",
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "DRAFT",
                  "SUBMITTED",
                  "ON_PROGRESS",
                  "ON_HOLD",
                  "CLOSED",
                  "CANCELLED",
                  "COMPLETED"
                ]
              }
            }
          },
          {
            "name": "page",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "List projects (firm-wide for the project tier; else managed / created / member)",
        "tags": [
          "CRM — Projects"
        ],
        "x-permissions": [
          "project:read"
        ],
        "x-permissions-mode": "all"
      },
      "post": {
        "operationId": "CrmProjectsController_create",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateCrmProjectDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Create Project from an EL assignment (seeds the Schedule from its Budget Hours)",
        "tags": [
          "CRM — Projects"
        ],
        "x-permissions": [
          "project:create"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/projects/options": {
      "get": {
        "operationId": "CrmProjectsController_options",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "People picker (active employees) for manager, resources and assignees",
        "tags": [
          "CRM — Projects"
        ],
        "x-permissions": [
          "project:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/projects/eligible-assignments": {
      "get": {
        "operationId": "CrmProjectsController_eligibleAssignments",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "EL assignments a project can be created from (EL In Progress / Signed, no project yet)",
        "tags": [
          "CRM — Projects"
        ],
        "x-permissions": [
          "project:create"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/projects/{id}": {
      "get": {
        "operationId": "CrmProjectsController_findOne",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Get one project with schedule, resources, financial, history, related records and notes",
        "tags": [
          "CRM — Projects"
        ],
        "x-permissions": [
          "project:read"
        ],
        "x-permissions-mode": "all"
      },
      "patch": {
        "operationId": "CrmProjectsController_update",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateCrmProjectDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Update header fields, Billing Information and the Resources row tabs",
        "tags": [
          "CRM — Projects"
        ],
        "x-permissions": [
          "project:update"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/projects/{id}/tasks/bulk": {
      "put": {
        "operationId": "CrmProjectsController_bulkTasks",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BulkProjectTasksDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Add/Edit Tasks in Bulk (rows absent from the array are untouched)",
        "tags": [
          "CRM — Projects"
        ],
        "x-permissions": [
          "project:update"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/projects/{id}/tasks": {
      "post": {
        "operationId": "CrmProjectsController_createTask",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateProjectTaskDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "New Project Task / New Milestone",
        "tags": [
          "CRM — Projects"
        ],
        "x-permissions": [
          "project:update"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/projects/{id}/tasks/{taskId}": {
      "patch": {
        "operationId": "CrmProjectsController_updateTask",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "taskId",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateProjectTaskDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Edit a task (EL-seeded name / planned work and rolled-up parent fields are locked)",
        "tags": [
          "CRM — Projects"
        ],
        "x-permissions": [
          "project:update"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/projects/{id}/tasks/{taskId}/copy": {
      "post": {
        "operationId": "CrmProjectsController_copyTask",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "taskId",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Copy Task (same parent, \"(copy)\", children not copied)",
        "tags": [
          "CRM — Projects"
        ],
        "x-permissions": [
          "project:update"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/projects/{id}/submit": {
      "post": {
        "operationId": "CrmProjectsController_submit",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProjectTransitionDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Draft → Submitted (requires name, dates, manager and a Schedule task)",
        "tags": [
          "CRM — Projects"
        ],
        "x-permissions": [
          "project:update"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/projects/{id}/approve": {
      "post": {
        "operationId": "CrmProjectsController_approve",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProjectTransitionDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Submitted → On Progress",
        "tags": [
          "CRM — Projects"
        ],
        "x-permissions": [
          "project:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/projects/{id}/reject": {
      "post": {
        "operationId": "CrmProjectsController_reject",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProjectReasonDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Submitted → Draft with a mandatory reason",
        "tags": [
          "CRM — Projects"
        ],
        "x-permissions": [
          "project:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/projects/{id}/hold": {
      "post": {
        "operationId": "CrmProjectsController_hold",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProjectTransitionDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "On Progress → On Hold",
        "tags": [
          "CRM — Projects"
        ],
        "x-permissions": [
          "project:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/projects/{id}/resume": {
      "post": {
        "operationId": "CrmProjectsController_resume",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProjectTransitionDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "On Hold → On Progress",
        "tags": [
          "CRM — Projects"
        ],
        "x-permissions": [
          "project:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/projects/{id}/close": {
      "post": {
        "operationId": "CrmProjectsController_close",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProjectTransitionDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "On Progress / On Hold → Closed (WP Submission must be complete and received by GA/IT)",
        "tags": [
          "CRM — Projects"
        ],
        "x-permissions": [
          "project:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/projects/{id}/cancel": {
      "post": {
        "operationId": "CrmProjectsController_cancel",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProjectReasonDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Draft / On Progress / On Hold → Cancelled with a mandatory reason",
        "tags": [
          "CRM — Projects"
        ],
        "x-permissions": [
          "project:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/projects/{id}/iirs": {
      "post": {
        "operationId": "CrmProjectsController_createIir",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateProjectIirDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Create IIR for one termin of the billing schedule (one IIR per installment)",
        "tags": [
          "CRM — Projects"
        ],
        "x-permissions": [
          "project:update"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/timesheets": {
      "get": {
        "operationId": "CrmTimesheetsController_findAll",
        "parameters": [
          {
            "name": "month",
            "required": false,
            "in": "query",
            "description": "YYYY-MM (default: the current month in Asia/Jakarta).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "required": false,
            "in": "query",
            "description": "Effective approval status; one value or several comma-separated.",
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "DRAFT",
                  "SUBMITTED",
                  "APPROVED",
                  "APPROVED_BY_SYSTEM",
                  "REJECTED"
                ]
              }
            }
          },
          {
            "name": "scope",
            "required": false,
            "in": "query",
            "description": "Reviewers only; ignored otherwise.",
            "schema": {
              "enum": [
                "mine",
                "all"
              ],
              "type": "string"
            }
          },
          {
            "name": "employeeId",
            "required": false,
            "in": "query",
            "description": "Reviewers only; ignored otherwise.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "projectId",
            "required": false,
            "in": "query",
            "description": "RsmidProject id (Project › Resources › Timesheet).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "search",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "List timesheet entries of a month (own; reviewers may pick an employee or all)",
        "tags": [
          "CRM — Timesheet"
        ],
        "x-permissions": [
          "timesheet:read"
        ],
        "x-permissions-mode": "all"
      },
      "post": {
        "operationId": "CrmTimesheetsController_create",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateCrmTimesheetDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Create a Draft timesheet entry (1 date, 1 project, 1 time range)",
        "tags": [
          "CRM — Timesheet"
        ],
        "x-permissions": [
          "timesheet:create"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/timesheets/options": {
      "get": {
        "operationId": "CrmTimesheetsController_options",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Form options: the caller, bookable projects, the office pseudo-project, reviewer flag",
        "tags": [
          "CRM — Timesheet"
        ],
        "x-permissions": [
          "timesheet:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/timesheets/options/projects/{projectId}/tasks": {
      "get": {
        "operationId": "CrmTimesheetsController_projectTasks",
        "parameters": [
          {
            "name": "projectId",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Tasks of a bookable project (Task select)",
        "tags": [
          "CRM — Timesheet"
        ],
        "x-permissions": [
          "timesheet:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/timesheets/{id}": {
      "get": {
        "operationId": "CrmTimesheetsController_findOne",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Get one Timesheet ID with its approval history, related records and system notes",
        "tags": [
          "CRM — Timesheet"
        ],
        "x-permissions": [
          "timesheet:read"
        ],
        "x-permissions-mode": "all"
      },
      "patch": {
        "operationId": "CrmTimesheetsController_update",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateCrmTimesheetDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Update a Draft / Rejected timesheet entry (locked while Submitted / Approved)",
        "tags": [
          "CRM — Timesheet"
        ],
        "x-permissions": [
          "timesheet:create"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/timesheets/{id}/submit": {
      "post": {
        "operationId": "CrmTimesheetsController_submit",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CrmTimesheetTransitionDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Draft / Rejected → Submitted (the employee only)",
        "tags": [
          "CRM — Timesheet"
        ],
        "x-permissions": [
          "timesheet:submit"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/timesheets/{id}/approve": {
      "post": {
        "operationId": "CrmTimesheetsController_approve",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CrmTimesheetTransitionDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Submitted → Approved",
        "tags": [
          "CRM — Timesheet"
        ],
        "x-permissions": [
          "timesheet:approve"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/timesheets/{id}/reject": {
      "post": {
        "operationId": "CrmTimesheetsController_reject",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RejectCrmTimesheetDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Submitted → Rejected with a mandatory reason",
        "tags": [
          "CRM — Timesheet"
        ],
        "x-permissions": [
          "timesheet:approve"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/invoice-issuance-requests": {
      "get": {
        "operationId": "IirController_findAll",
        "parameters": [
          {
            "name": "search",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "required": false,
            "in": "query",
            "description": "IIR status; one value or several comma-separated.",
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "DRAFT",
                  "PENDING_APPROVAL",
                  "APPROVED",
                  "REJECTED",
                  "SYNCED"
                ]
              }
            }
          },
          {
            "name": "projectId",
            "required": false,
            "in": "query",
            "description": "Exact project id (Project › Related Records / IIR Plan).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "List IIRs (own / project-team for staff; firm-wide for CRM managers)",
        "tags": [
          "CRM — Invoice Issuance Request"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      },
      "post": {
        "operationId": "IirController_create",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateIirFromTermDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Create IIR — one installment of the project IIR Plan (materialises the plan on first use)",
        "tags": [
          "CRM — Invoice Issuance Request"
        ],
        "x-permissions": [
          "crm:iir:create"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/invoice-issuance-requests/plan": {
      "get": {
        "operationId": "IirController_plan",
        "parameters": [
          {
            "name": "projectId",
            "required": true,
            "in": "query",
            "description": "Project (RsmidProject id).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "A project's IIR Plan (Project › Financial › IIR Plan) — never writes",
        "tags": [
          "CRM — Invoice Issuance Request"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/invoice-issuance-requests/{id}": {
      "get": {
        "operationId": "IirController_findOne",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Get one IIR with its approval history, related records and system notes",
        "tags": [
          "CRM — Invoice Issuance Request"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      },
      "patch": {
        "operationId": "IirController_update",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateIirDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Update invoice alias / grouping memo / integration note (locked once the NetSuite invoice exists)",
        "tags": [
          "CRM — Invoice Issuance Request"
        ],
        "x-permissions": [
          "crm:iir:update"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/invoice-issuance-requests/{id}/submit": {
      "post": {
        "operationId": "IirController_submit",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IirTransitionDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Draft / Rejected → Pending Approval",
        "tags": [
          "CRM — Invoice Issuance Request"
        ],
        "x-permissions": [
          "crm:iir:submit"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/invoice-issuance-requests/{id}/approve": {
      "post": {
        "operationId": "IirController_approve",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IirTransitionDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Pending Approval → Approved (MIC/MPLOB) — no NetSuite call in this iteration",
        "tags": [
          "CRM — Invoice Issuance Request"
        ],
        "x-permissions": [
          "crm:iir:approve"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/invoice-issuance-requests/{id}/reject": {
      "post": {
        "operationId": "IirController_reject",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RejectIirDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Pending Approval → Rejected with a mandatory reason",
        "tags": [
          "CRM — Invoice Issuance Request"
        ],
        "x-permissions": [
          "crm:iir:approve"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/release-checklists": {
      "get": {
        "operationId": "ReleaseChecklistController_findAll",
        "parameters": [
          {
            "name": "search",
            "required": false,
            "in": "query",
            "schema": {
              "maxLength": 100,
              "type": "string"
            }
          },
          {
            "name": "status",
            "required": false,
            "in": "query",
            "description": "One value or several comma-separated.",
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "DRAFT",
                  "IN_PROGRESS",
                  "COMPLETED"
                ]
              }
            }
          },
          {
            "name": "releaseType",
            "required": false,
            "in": "query",
            "schema": {
              "enum": [
                "AUDIT",
                "TAX",
                "CONSULTING"
              ],
              "type": "string"
            }
          },
          {
            "name": "stage",
            "required": false,
            "in": "query",
            "description": "One value or several comma-separated.",
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "MANAGER_REVIEW",
                  "EP_REVIEW",
                  "QA_REVIEW",
                  "PAYMENT_CHECK",
                  "OPINION",
                  "DELIVERY",
                  "COMPLETED"
                ]
              }
            }
          },
          {
            "name": "projectId",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "List release checklists (own / managed project for staff; firm-wide for CRM managers)",
        "tags": [
          "CRM — Release Checklist"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      },
      "post": {
        "operationId": "ReleaseChecklistController_create",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateReleaseChecklistDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Open the release checklist of a project (Draft, four Review Chain rows)",
        "tags": [
          "CRM — Release Checklist"
        ],
        "x-permissions": [
          "crm:release:create"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/release-checklists/project-options": {
      "get": {
        "operationId": "ReleaseChecklistController_projectOptions",
        "parameters": [
          {
            "name": "search",
            "required": false,
            "in": "query",
            "schema": {
              "maxLength": 100,
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Projects that can still open a release checklist (create page picker)",
        "tags": [
          "CRM — Release Checklist"
        ],
        "x-permissions": [
          "crm:release:create"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/release-checklists/by-project/{projectId}": {
      "get": {
        "operationId": "ReleaseChecklistController_byProject",
        "parameters": [
          {
            "name": "projectId",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "The checklist summary of one project (Project › Release Checklist tab), or null",
        "tags": [
          "CRM — Release Checklist"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/release-checklists/{id}": {
      "get": {
        "operationId": "ReleaseChecklistController_findOne",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Get one release checklist with its review chain, history, related records and notes",
        "tags": [
          "CRM — Release Checklist"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      },
      "patch": {
        "operationId": "ReleaseChecklistController_update",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateReleaseChecklistDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Update date / type / tab blocks / reviewer names (stage-driven locks)",
        "tags": [
          "CRM — Release Checklist"
        ],
        "x-permissions": [
          "crm:release:update"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/release-checklists/{id}/reviews/{stage}/submit": {
      "post": {
        "operationId": "ReleaseChecklistController_submitReview",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "stage",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReleaseCommentDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Submit one Review Chain row (manager | ep | qa | concurring)",
        "tags": [
          "CRM — Release Checklist"
        ],
        "x-permissions": [
          "crm:release:review"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/release-checklists/{id}/payment-check": {
      "post": {
        "operationId": "ReleaseChecklistController_paymentCheck",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReleasePaymentCheckDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Payment Status Check (by Finance): Lunas / Outstanding",
        "tags": [
          "CRM — Release Checklist"
        ],
        "x-permissions": [
          "crm:release:finance"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/release-checklists/{id}/ep-agreement": {
      "post": {
        "operationId": "ReleaseChecklistController_epAgreement",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReleaseAgreementDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Engagement Partner agreement while Outstanding",
        "tags": [
          "CRM — Release Checklist"
        ],
        "x-permissions": [
          "crm:release:partner-review"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/release-checklists/{id}/mplob-agreement": {
      "post": {
        "operationId": "ReleaseChecklistController_mpLobAgreement",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReleaseAgreementDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "MPLOB agreement while Outstanding",
        "tags": [
          "CRM — Release Checklist"
        ],
        "x-permissions": [
          "crm:release:mplob"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/release-checklists/{id}/opinion/request": {
      "post": {
        "operationId": "ReleaseChecklistController_requestOpinion",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReleaseOpinionRequestDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Request (record) the opinion number",
        "tags": [
          "CRM — Release Checklist"
        ],
        "x-permissions": [
          "crm:release:review"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/release-checklists/{id}/opinion/coordinator-review": {
      "post": {
        "operationId": "ReleaseChecklistController_coordinatorReview",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReleaseCommentDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Operator Coordinator Review",
        "tags": [
          "CRM — Release Checklist"
        ],
        "x-permissions": [
          "crm:release:coordinate"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/release-checklists/{id}/opinion/ep-signoff": {
      "post": {
        "operationId": "ReleaseChecklistController_epSignoff",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReleaseCommentDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Engagement Partner Sign-off (Scan QR — recorded by this call)",
        "tags": [
          "CRM — Release Checklist"
        ],
        "x-permissions": [
          "crm:release:partner-review"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/release-checklists/{id}/delivery": {
      "post": {
        "operationId": "ReleaseChecklistController_delivery",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReleaseDeliveryDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Delivery: date sent to client and/or archive; completing it triggers Project Closure",
        "tags": [
          "CRM — Release Checklist"
        ],
        "x-permissions": [
          "crm:release:deliver"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/client-surveys": {
      "get": {
        "operationId": "ClientSurveyController_findAll",
        "parameters": [
          {
            "name": "search",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "required": false,
            "in": "query",
            "description": "Status; one value or several comma-separated.",
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "DRAFT",
                  "SENT",
                  "SUBMITTED",
                  "CLOSED"
                ]
              }
            }
          },
          {
            "name": "projectId",
            "required": false,
            "in": "query",
            "description": "Surveys of one project (Project › Client Survey tab).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "List client surveys (own / project team for staff; firm-wide for CRM managers)",
        "tags": [
          "CRM — Client Survey"
        ],
        "x-permissions": [
          "survey:read"
        ],
        "x-permissions-mode": "all"
      },
      "post": {
        "operationId": "ClientSurveyController_create",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateClientSurveyDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Create a Draft client survey for a project",
        "tags": [
          "CRM — Client Survey"
        ],
        "x-permissions": [
          "survey:create"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/client-surveys/project-options": {
      "get": {
        "operationId": "ClientSurveyController_projectOptions",
        "parameters": [
          {
            "name": "search",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Project picker of the create form (open survey flagged)",
        "tags": [
          "CRM — Client Survey"
        ],
        "x-permissions": [
          "crm:client-survey:create"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/client-surveys/{id}": {
      "get": {
        "operationId": "ClientSurveyController_findOne",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Get one client survey with history, related records and notes",
        "tags": [
          "CRM — Client Survey"
        ],
        "x-permissions": [
          "survey:read"
        ],
        "x-permissions-mode": "all"
      },
      "patch": {
        "operationId": "ClientSurveyController_update",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateClientSurveyDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Update a client survey (fields unlock per status; Completed is locked)",
        "tags": [
          "CRM — Client Survey"
        ],
        "x-permissions": [
          "survey:create"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/client-surveys/{id}/send": {
      "post": {
        "operationId": "ClientSurveyController_send",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SendClientSurveyDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Draft → Sent (sets Date sent)",
        "tags": [
          "CRM — Client Survey"
        ],
        "x-permissions": [
          "crm:client-survey:send"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/client-surveys/{id}/complete": {
      "post": {
        "operationId": "ClientSurveyController_complete",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CompleteClientSurveyDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Sent → Completed (requires Respondent 1 and all 8 answers)",
        "tags": [
          "CRM — Client Survey"
        ],
        "x-permissions": [
          "crm:client-survey:complete"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/wp-submissions": {
      "get": {
        "operationId": "WpSubmissionController_findAll",
        "parameters": [
          {
            "name": "search",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "required": false,
            "in": "query",
            "description": "Draft / In Progress / Completed; one value or several comma-separated.",
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "DRAFT",
                  "IN_PROGRESS",
                  "COMPLETED"
                ]
              }
            }
          },
          {
            "name": "projectId",
            "required": false,
            "in": "query",
            "description": "Exact project (Projects module \"Open WP Submission\" link).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "List WP Submissions (own projects for staff; firm-wide for CRM managers and GA/IT)",
        "tags": [
          "CRM — WP Submission"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      },
      "post": {
        "operationId": "WpSubmissionController_create",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateWpSubmissionDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Create the Draft WP Submission of a project (three Document Type items)",
        "tags": [
          "CRM — WP Submission"
        ],
        "x-permissions": [
          "crm:wps:prepare"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/wp-submissions/eligible-projects": {
      "get": {
        "operationId": "WpSubmissionController_eligibleProjects",
        "parameters": [
          {
            "name": "search",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Projects (On Progress / On Hold) in scope that have no WP Submission yet",
        "tags": [
          "CRM — WP Submission"
        ],
        "x-permissions": [
          "crm:wps:prepare"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/wp-submissions/{id}": {
      "get": {
        "operationId": "WpSubmissionController_findOne",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Get one WP Submission with its document types, receipts, storage, history and notes",
        "tags": [
          "CRM — WP Submission"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      },
      "patch": {
        "operationId": "WpSubmissionController_update",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateWpSubmissionDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Update field groups: Document Type (Draft, ECS) · GA/IT receipts (after Partner approval) · Storage Location (GA/IT)",
        "tags": [
          "CRM — WP Submission"
        ],
        "x-permissions": [
          "crm:wps:prepare",
          "crm:wps:receive"
        ],
        "x-permissions-mode": "any"
      }
    },
    "/api/crm/wp-submissions/{id}/submit": {
      "post": {
        "operationId": "WpSubmissionController_submit",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WpTransitionDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Draft → In Progress (Pending Manager approval); requires complete Document Types",
        "tags": [
          "CRM — WP Submission"
        ],
        "x-permissions": [
          "crm:wps:prepare"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/wp-submissions/{id}/approve": {
      "post": {
        "operationId": "WpSubmissionController_approve",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WpTransitionDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Approve the current step: Manager, then Partner (\"submit ke GA/IT\")",
        "tags": [
          "CRM — WP Submission"
        ],
        "x-permissions": [
          "crm:wps:approve",
          "crm:wps:approve-partner"
        ],
        "x-permissions-mode": "any"
      }
    },
    "/api/crm/wp-submissions/{id}/reject": {
      "post": {
        "operationId": "WpSubmissionController_reject",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RejectWpDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Reject the current step back to Draft with a mandatory reason",
        "tags": [
          "CRM — WP Submission"
        ],
        "x-permissions": [
          "crm:wps:approve",
          "crm:wps:approve-partner"
        ],
        "x-permissions-mode": "any"
      }
    },
    "/api/crm/document-borrowings": {
      "get": {
        "operationId": "DocumentBorrowingController_findAll",
        "parameters": [
          {
            "name": "search",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "required": false,
            "in": "query",
            "description": "One value or several comma-separated (web \"Draft\" = REQUESTED,APPROVED).",
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "REQUESTED",
                  "APPROVED",
                  "RELEASED",
                  "RETURNED",
                  "REJECTED"
                ]
              }
            }
          },
          {
            "name": "borrowerId",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "assignmentId",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "overdue",
            "required": false,
            "in": "query",
            "description": "true = Dipinjam past the due date; false = everything else.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "page",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "List borrowings (own for staff; all for CRM managers and GA)",
        "tags": [
          "CRM — Document Borrowing"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      },
      "post": {
        "operationId": "DocumentBorrowingController_create",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateBorrowingDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Create a borrowing request (Draft, BRW-YYYY-NNNN)",
        "tags": [
          "CRM — Document Borrowing"
        ],
        "x-permissions": [
          "crm:borrowing:request"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/document-borrowings/defaults": {
      "get": {
        "operationId": "DocumentBorrowingController_defaults",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "New Borrowing defaults — borrower block, Date transaction, GA contacts",
        "tags": [
          "CRM — Document Borrowing"
        ],
        "x-permissions": [
          "crm:borrowing:request"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/document-borrowings/assignment-options": {
      "get": {
        "operationId": "DocumentBorrowingController_assignmentOptions",
        "parameters": [
          {
            "name": "search",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Assignment picker (firm-wide, identifiers only — cross-division borrowing)",
        "tags": [
          "CRM — Document Borrowing"
        ],
        "x-permissions": [
          "crm:borrowing:request"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/document-borrowings/options": {
      "get": {
        "operationId": "DocumentBorrowingController_options",
        "parameters": [
          {
            "name": "borrowerId",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Borrowing picker (Clearance) — id / number / status / client / assignment",
        "tags": [
          "CRM — Document Borrowing"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/document-borrowings/{id}": {
      "get": {
        "operationId": "DocumentBorrowingController_findOne",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Get one borrowing with approvals, GA fields, history, related records and notes",
        "tags": [
          "CRM — Document Borrowing"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      },
      "patch": {
        "operationId": "DocumentBorrowingController_update",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateBorrowingDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Update a borrowing — request fields while Draft/Rejected; GA: Borrowing date (→ Dipinjam) and Return date (→ Selesai)",
        "tags": [
          "CRM — Document Borrowing"
        ],
        "x-permissions": [
          "crm:borrowing:request",
          "crm:borrowing:ga"
        ],
        "x-permissions-mode": "any"
      }
    },
    "/api/crm/document-borrowings/{id}/submit": {
      "post": {
        "operationId": "DocumentBorrowingController_submit",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BorrowingSubmitDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Draft / Rejected → submitted for approval (restarts the approval chain)",
        "tags": [
          "CRM — Document Borrowing"
        ],
        "x-permissions": [
          "crm:borrowing:request"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/document-borrowings/{id}/approve": {
      "post": {
        "operationId": "DocumentBorrowingController_approve",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BorrowingApproveDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Approve the next required step (Manager → Partner → MPLOB → Project Owner → CAO)",
        "tags": [
          "CRM — Document Borrowing"
        ],
        "x-permissions": [
          "crm:borrowing:approve",
          "crm:borrowing:approve-senior"
        ],
        "x-permissions-mode": "any"
      }
    },
    "/api/crm/document-borrowings/{id}/reject": {
      "post": {
        "operationId": "DocumentBorrowingController_reject",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BorrowingRejectDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Reject at the next required step with a mandatory reason",
        "tags": [
          "CRM — Document Borrowing"
        ],
        "x-permissions": [
          "crm:borrowing:approve",
          "crm:borrowing:approve-senior"
        ],
        "x-permissions-mode": "any"
      }
    },
    "/api/crm/spb-employee": {
      "get": {
        "operationId": "SpbEmployeeController_findAll",
        "parameters": [
          {
            "name": "search",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "required": false,
            "in": "query",
            "description": "One value or several comma-separated.",
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "DRAFT",
                  "SUBMITTED",
                  "APPROVED",
                  "REJECTED"
                ]
              }
            }
          },
          {
            "name": "scope",
            "required": false,
            "in": "query",
            "description": "Reviewers only; ignored for everyone else.",
            "schema": {
              "enum": [
                "mine",
                "all"
              ],
              "type": "string"
            }
          },
          {
            "name": "employeeId",
            "required": false,
            "in": "query",
            "description": "Reviewers only; ignored for everyone else.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "projectId",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "List SPB Employee submissions (own; firm-wide for expense approvers with scope=all)",
        "tags": [
          "CRM — SPB Employee"
        ],
        "x-permissions": [
          "expense:read"
        ],
        "x-permissions-mode": "all"
      },
      "post": {
        "operationId": "SpbEmployeeController_create",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateSpbEmployeeDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Create a Draft SPB for the caller (number assigned by the server)",
        "tags": [
          "CRM — SPB Employee"
        ],
        "x-permissions": [
          "expense:create"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/spb-employee/options": {
      "get": {
        "operationId": "SpbEmployeeController_options",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Form options: caller, classification, projects, categories, currencies, evidence rule",
        "tags": [
          "CRM — SPB Employee"
        ],
        "x-permissions": [
          "expense:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/spb-employee/options/projects/{projectId}/tasks": {
      "get": {
        "operationId": "SpbEmployeeController_projectTasks",
        "parameters": [
          {
            "name": "projectId",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Project Task options of one bookable project",
        "tags": [
          "CRM — SPB Employee"
        ],
        "x-permissions": [
          "expense:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/spb-employee/{id}": {
      "get": {
        "operationId": "SpbEmployeeController_findOne",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Get one SPB with its items, evidence, history, related records and notes",
        "tags": [
          "CRM — SPB Employee"
        ],
        "x-permissions": [
          "expense:read"
        ],
        "x-permissions-mode": "all"
      },
      "patch": {
        "operationId": "SpbEmployeeController_update",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateSpbEmployeeDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Update an own Draft / Rejected SPB (items replace by id-sync)",
        "tags": [
          "CRM — SPB Employee"
        ],
        "x-permissions": [
          "expense:create"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/spb-employee/{id}/submit": {
      "post": {
        "operationId": "SpbEmployeeController_submit",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SpbTransitionDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Draft / Rejected → Submitted (complete lines + receipts required)",
        "tags": [
          "CRM — SPB Employee"
        ],
        "x-permissions": [
          "expense:submit"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/spb-employee/{id}/approve": {
      "post": {
        "operationId": "SpbEmployeeController_approve",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SpbTransitionDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Submitted → Approved (Manager LOB); queues the nightly NetSuite push",
        "tags": [
          "CRM — SPB Employee"
        ],
        "x-permissions": [
          "expense:approve"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/spb-employee/{id}/reject": {
      "post": {
        "operationId": "SpbEmployeeController_reject",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RejectSpbDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Submitted → Rejected with a mandatory reason",
        "tags": [
          "CRM — SPB Employee"
        ],
        "x-permissions": [
          "expense:approve"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/spb-employee/{id}/evidence": {
      "post": {
        "operationId": "SpbEmployeeController_uploadEvidence",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/UploadSpbEvidenceDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Upload a receipt (image or PDF, max 10 MB), optionally linked to a line",
        "tags": [
          "CRM — SPB Employee"
        ],
        "x-permissions": [
          "expense:create"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/spb-employee/{id}/evidence/{evidenceId}": {
      "delete": {
        "operationId": "SpbEmployeeController_deleteEvidence",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "evidenceId",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Remove one evidence file (soft delete; the stored bytes stay for the audit trail)",
        "tags": [
          "CRM — SPB Employee"
        ],
        "x-permissions": [
          "expense:create"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/spb-vendor": {
      "get": {
        "operationId": "SpbVendorController_findAll",
        "parameters": [
          {
            "name": "search",
            "required": false,
            "in": "query",
            "schema": {
              "maxLength": 100,
              "type": "string"
            }
          },
          {
            "name": "status",
            "required": false,
            "in": "query",
            "description": "One value or several comma-separated.",
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "DRAFT",
                  "SUBMITTED",
                  "APPROVED",
                  "REJECTED"
                ]
              }
            }
          },
          {
            "name": "projectId",
            "required": false,
            "in": "query",
            "description": "Exact project (Projects module Related Records).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "netsuiteVendorId",
            "required": false,
            "in": "query",
            "description": "Exact NetSuite vendor internal id (Vendor module Related Records).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "List SPB Vendor submissions (own / project scope; firm-wide for CRM managers)",
        "tags": [
          "CRM — SPB Vendor"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      },
      "post": {
        "operationId": "SpbVendorController_create",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateSpbVendorDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Create a Draft SPB Vendor (vendor validated against NetSuite)",
        "tags": [
          "CRM — SPB Vendor"
        ],
        "x-permissions": [
          "crm:spb-vendor:create"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/spb-vendor/project-options": {
      "get": {
        "operationId": "SpbVendorController_projectOptions",
        "parameters": [
          {
            "name": "search",
            "required": false,
            "in": "query",
            "schema": {
              "maxLength": 100,
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Project picker — open projects in the caller’s project scope",
        "tags": [
          "CRM — SPB Vendor"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/spb-vendor/{id}": {
      "get": {
        "operationId": "SpbVendorController_findOne",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Get one SPB Vendor with items, history, related records and notes",
        "tags": [
          "CRM — SPB Vendor"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      },
      "patch": {
        "operationId": "SpbVendorController_update",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateSpbVendorDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Update an SPB Vendor (content locks while Submitted / Approved)",
        "tags": [
          "CRM — SPB Vendor"
        ],
        "x-permissions": [
          "crm:spb-vendor:update"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/spb-vendor/{id}/submit": {
      "post": {
        "operationId": "SpbVendorController_submit",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SpbVendorTransitionDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Draft / Rejected → Submitted (requires vendor, date, project and items)",
        "tags": [
          "CRM — SPB Vendor"
        ],
        "x-permissions": [
          "crm:spb-vendor:submit"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/spb-vendor/{id}/approve": {
      "post": {
        "operationId": "SpbVendorController_approve",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SpbVendorTransitionDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Submitted → Approved (Manager LOB) — recorded in the Custom App only",
        "tags": [
          "CRM — SPB Vendor"
        ],
        "x-permissions": [
          "crm:spb-vendor:approve"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/spb-vendor/{id}/reject": {
      "post": {
        "operationId": "SpbVendorController_reject",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RejectSpbVendorDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Submitted → Rejected with a mandatory reason",
        "tags": [
          "CRM — SPB Vendor"
        ],
        "x-permissions": [
          "crm:spb-vendor:approve"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/ta-advances": {
      "get": {
        "operationId": "TaAdvanceController_findAll",
        "parameters": [
          {
            "name": "search",
            "required": false,
            "in": "query",
            "description": "Number, destination, employee, project code / name.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "required": false,
            "in": "query",
            "description": "One value or several comma-separated.",
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "DRAFT",
                  "SUBMITTED",
                  "APPROVED",
                  "REJECTED",
                  "PAID"
                ]
              }
            }
          },
          {
            "name": "scope",
            "required": false,
            "in": "query",
            "description": "Reviewers only; others always see their own.",
            "schema": {
              "enum": [
                "mine",
                "all"
              ],
              "type": "string"
            }
          },
          {
            "name": "requestorId",
            "required": false,
            "in": "query",
            "description": "Reviewers only.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "projectId",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "List TA advances (own for employees; firm-wide for approvers / Finance)",
        "tags": [
          "CRM — TA Advance"
        ],
        "x-permissions": [
          "expense:read"
        ],
        "x-permissions-mode": "all"
      },
      "post": {
        "operationId": "TaAdvanceController_create",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateTaAdvanceDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Create a Draft TA advance (number TAA-YYYY-NNNN assigned by the server)",
        "tags": [
          "CRM — TA Advance"
        ],
        "x-permissions": [
          "expense:create"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/ta-advances/options": {
      "get": {
        "operationId": "TaAdvanceController_options",
        "parameters": [
          {
            "name": "settleable",
            "required": false,
            "in": "query",
            "description": "true → include Approved / Paid advances for the Settlement TA picker.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Form options: requestor, project picker, affordances, settleable advances",
        "tags": [
          "CRM — TA Advance"
        ],
        "x-permissions": [
          "expense:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/ta-advances/{id}": {
      "get": {
        "operationId": "TaAdvanceController_findOne",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Get one TA advance with trip, items, checks, payment, history and related records",
        "tags": [
          "CRM — TA Advance"
        ],
        "x-permissions": [
          "expense:read"
        ],
        "x-permissions-mode": "all"
      },
      "patch": {
        "operationId": "TaAdvanceController_update",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateTaAdvanceDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Update a TA advance (content: requestor in Draft / Rejected; Payment section: Finance while Approved)",
        "tags": [
          "CRM — TA Advance"
        ],
        "x-permissions": [
          "expense:create"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/ta-advances/{id}/submit": {
      "post": {
        "operationId": "TaAdvanceController_submit",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TaTransitionDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Draft / Rejected → Submitted (requestor only; freezes the calculated TA amount)",
        "tags": [
          "CRM — TA Advance"
        ],
        "x-permissions": [
          "expense:submit"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/ta-advances/{id}/approve": {
      "post": {
        "operationId": "TaAdvanceController_approve",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TaTransitionDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Submitted → Approved",
        "tags": [
          "CRM — TA Advance"
        ],
        "x-permissions": [
          "expense:approve"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/ta-advances/{id}/reject": {
      "post": {
        "operationId": "TaAdvanceController_reject",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RejectTaAdvanceDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Submitted → Rejected with a mandatory reason",
        "tags": [
          "CRM — TA Advance"
        ],
        "x-permissions": [
          "expense:approve"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/ta-advances/{id}/pay": {
      "post": {
        "operationId": "TaAdvanceController_pay",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PayTaAdvanceDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Approved → Paid (Finance records the manual payment; request status Selesai)",
        "tags": [
          "CRM — TA Advance"
        ],
        "x-permissions": [
          "expense:pay"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/ta-settlements": {
      "get": {
        "operationId": "TaSettlementController_findAll",
        "parameters": [
          {
            "name": "search",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "required": false,
            "in": "query",
            "description": "Status; one value or several comma-separated.",
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "DRAFT",
                  "SUBMITTED",
                  "APPROVED",
                  "REJECTED",
                  "CLOSED"
                ]
              }
            }
          },
          {
            "name": "result",
            "required": false,
            "in": "query",
            "schema": {
              "enum": [
                "OVER_PAYMENT",
                "UNDER_PAYMENT",
                "MATCHED"
              ],
              "type": "string"
            }
          },
          {
            "name": "taAdvanceId",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "List settlements (own for staff; firm-wide for CRM managers)",
        "tags": [
          "CRM — Settlement TA"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      },
      "post": {
        "operationId": "TaSettlementController_create",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateTaSettlementDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Create a Draft settlement for an Approved TA Advance",
        "tags": [
          "CRM — Settlement TA"
        ],
        "x-permissions": [
          "crm:ta-settlement:create"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/ta-settlements/advance-options": {
      "get": {
        "operationId": "TaSettlementController_advanceOptions",
        "parameters": [
          {
            "name": "search",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Approved / Paid TA Advances that have no settlement yet (create picker)",
        "tags": [
          "CRM — Settlement TA"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/ta-settlements/{id}": {
      "get": {
        "operationId": "TaSettlementController_findOne",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Get one settlement with reconciliation, history, related records and notes",
        "tags": [
          "CRM — Settlement TA"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      },
      "patch": {
        "operationId": "TaSettlementController_update",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateTaSettlementDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Update lines / date (Draft, Rejected) or the transfer date (Over Payment)",
        "tags": [
          "CRM — Settlement TA"
        ],
        "x-permissions": [
          "crm:ta-settlement:update"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/ta-settlements/{id}/submit": {
      "post": {
        "operationId": "TaSettlementController_submit",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TaSettlementTransitionDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Draft / Rejected → Submitted (reconciliation frozen)",
        "tags": [
          "CRM — Settlement TA"
        ],
        "x-permissions": [
          "crm:ta-settlement:submit"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/ta-settlements/{id}/approve": {
      "post": {
        "operationId": "TaSettlementController_approve",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TaSettlementTransitionDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Submitted → Approved (Over Payment needs transfer date + proof)",
        "tags": [
          "CRM — Settlement TA"
        ],
        "x-permissions": [
          "crm:ta-settlement:approve"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/ta-settlements/{id}/reject": {
      "post": {
        "operationId": "TaSettlementController_reject",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RejectTaSettlementDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Submitted → Rejected with a mandatory reason",
        "tags": [
          "CRM — Settlement TA"
        ],
        "x-permissions": [
          "crm:ta-settlement:approve"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/ta-settlements/{id}/payment": {
      "post": {
        "operationId": "TaSettlementController_payment",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TaSettlementPaymentDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Finance: record the Under Payment additional payment (stays Approved)",
        "tags": [
          "CRM — Settlement TA"
        ],
        "x-permissions": [
          "crm:ta-settlement:finance"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/ta-settlements/{id}/close": {
      "post": {
        "operationId": "TaSettlementController_close",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TaSettlementTransitionDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Approved → Closed (Under Payment needs the additional payment)",
        "tags": [
          "CRM — Settlement TA"
        ],
        "x-permissions": [
          "crm:ta-settlement:finance"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/ta-settlements/{id}/lines/{lineId}/evidence": {
      "post": {
        "operationId": "TaSettlementController_uploadEvidence",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lineId",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Upload the evidence of one Actual Expense line (image or PDF, max 10 MB)",
        "tags": [
          "CRM — Settlement TA"
        ],
        "x-permissions": [
          "crm:ta-settlement:update"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/ta-settlements/{id}/transfer-proof": {
      "post": {
        "operationId": "TaSettlementController_uploadTransferProof",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Upload the transfer-to-company proof (Over Payment; image or PDF, max 10 MB)",
        "tags": [
          "CRM — Settlement TA"
        ],
        "x-permissions": [
          "crm:ta-settlement:update"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/ta-settlements/{id}/files/lines/{lineId}": {
      "get": {
        "operationId": "TaSettlementController_lineFile",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lineId",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Download the evidence of one Actual Expense line",
        "tags": [
          "CRM — Settlement TA"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/ta-settlements/{id}/files/transfer-proof": {
      "get": {
        "operationId": "TaSettlementController_transferProofFile",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Download the transfer-to-company proof",
        "tags": [
          "CRM — Settlement TA"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/mccer": {
      "get": {
        "operationId": "MccerController_findAll",
        "parameters": [
          {
            "name": "search",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "required": false,
            "in": "query",
            "description": "One value or several comma-separated.",
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "DRAFT",
                  "AWAITING_SETTLEMENT",
                  "VERIFIED",
                  "APPROVED"
                ]
              }
            }
          },
          {
            "name": "usageYear",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "cardholderUserId",
            "required": false,
            "in": "query",
            "description": "Ignored for callers without crm:mccer:read-all.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "List MCCERs (own as cardholder / preparer; every report with crm:mccer:read-all)",
        "tags": [
          "CRM — MCCER"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      },
      "post": {
        "operationId": "MccerController_create",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateMccerDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Create an MCCER (Draft) from the bank statement",
        "tags": [
          "CRM — MCCER"
        ],
        "x-permissions": [
          "crm:mccer:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/mccer/options": {
      "get": {
        "operationId": "MccerController_options",
        "parameters": [
          {
            "name": "status",
            "required": false,
            "in": "query",
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "DRAFT",
                  "AWAITING_SETTLEMENT",
                  "VERIFIED",
                  "APPROVED"
                ]
              }
            }
          },
          {
            "name": "cardholderUserId",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "MCCER picker (CC Settlement \"MCCER reference\") — scoped like the list",
        "tags": [
          "CRM — MCCER"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/mccer/options/cardholders": {
      "get": {
        "operationId": "MccerController_cardholders",
        "parameters": [
          {
            "name": "search",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Cardholder picker — active users with employee code and division",
        "tags": [
          "CRM — MCCER"
        ],
        "x-permissions": [
          "crm:mccer:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/mccer/{id}": {
      "get": {
        "operationId": "MccerController_findOne",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Get one MCCER with its rows, approval block, history, related records and notes",
        "tags": [
          "CRM — MCCER"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      },
      "patch": {
        "operationId": "MccerController_update",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateMccerDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Update an MCCER (header + rows while Draft; Finance verified while Awaiting Settlement)",
        "tags": [
          "CRM — MCCER"
        ],
        "x-permissions": [
          "crm:mccer:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/mccer/{id}/send": {
      "post": {
        "operationId": "MccerController_send",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MccerTransitionDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Draft → Awaiting Settlement (requires a complete statement)",
        "tags": [
          "CRM — MCCER"
        ],
        "x-permissions": [
          "crm:mccer:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/mccer/{id}/verify": {
      "post": {
        "operationId": "MccerController_verify",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MccerTransitionDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Awaiting Settlement → Verified (every row Finance verified, settlement submitted)",
        "tags": [
          "CRM — MCCER"
        ],
        "x-permissions": [
          "crm:mccer:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/mccer/{id}/acknowledge": {
      "post": {
        "operationId": "MccerController_acknowledge",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MccerTransitionDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Acknowledge a Verified MCCER (the cardholder, or an MCCER approver)",
        "tags": [
          "CRM — MCCER"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/mccer/{id}/approve": {
      "post": {
        "operationId": "MccerController_approve",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MccerTransitionDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Verified (acknowledged) → Approved",
        "tags": [
          "CRM — MCCER"
        ],
        "x-permissions": [
          "crm:mccer:approve"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/mccer/{id}/return": {
      "post": {
        "operationId": "MccerController_returnMccer",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReturnMccerDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Return one step (Awaiting Settlement → Draft, Verified → Awaiting Settlement) with a reason",
        "tags": [
          "CRM — MCCER"
        ],
        "x-permissions": [
          "crm:mccer:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/cc-settlements": {
      "get": {
        "operationId": "CcSettlementController_findAll",
        "parameters": [
          {
            "name": "search",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "required": false,
            "in": "query",
            "description": "Status; one value or several comma-separated.",
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "DRAFT",
                  "SUBMITTED",
                  "COMPLETED"
                ]
              }
            }
          },
          {
            "name": "page",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "List settlements (own card for cardholders; all for Finance)",
        "tags": [
          "CRM — Corporate CC Usage Settlement"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      },
      "post": {
        "operationId": "CcSettlementController_create",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateCcSettlementDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Start the settlement of an MCCER that is Awaiting Settlement",
        "tags": [
          "CRM — Corporate CC Usage Settlement"
        ],
        "x-permissions": [
          "crm:cc-settlement:create"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/cc-settlements/pending-mccers": {
      "get": {
        "operationId": "CcSettlementController_pendingMccers",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "MCCERs Awaiting Settlement that have no settlement yet (caller scope)",
        "tags": [
          "CRM — Corporate CC Usage Settlement"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/cc-settlements/options/clients": {
      "get": {
        "operationId": "CcSettlementController_clientOptions",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Client name select — active client register entries",
        "tags": [
          "CRM — Corporate CC Usage Settlement"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/cc-settlements/{id}": {
      "get": {
        "operationId": "CcSettlementController_findOne",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Get one settlement with its rows, approval history, related records and notes",
        "tags": [
          "CRM — Corporate CC Usage Settlement"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      },
      "patch": {
        "operationId": "CcSettlementController_update",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateCcSettlementDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Save Date transaction and the cardholder-completed row fields (Draft only)",
        "tags": [
          "CRM — Corporate CC Usage Settlement"
        ],
        "x-permissions": [
          "crm:cc-settlement:update"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/cc-settlements/{id}/submit": {
      "post": {
        "operationId": "CcSettlementController_submit",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CcTransitionDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Draft → Submitted (freezes the rows; every Others row needs Detail)",
        "tags": [
          "CRM — Corporate CC Usage Settlement"
        ],
        "x-permissions": [
          "crm:cc-settlement:submit"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/cc-settlements/{id}/complete": {
      "post": {
        "operationId": "CcSettlementController_complete",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CcTransitionDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Submitted → Completed (Finance)",
        "tags": [
          "CRM — Corporate CC Usage Settlement"
        ],
        "x-permissions": [
          "crm:cc-settlement:complete"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/cc-settlements/{id}/return": {
      "post": {
        "operationId": "CcSettlementController_returnToCardholder",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReturnCcSettlementDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Submitted → Draft with a mandatory reason (Finance returns it to the cardholder)",
        "tags": [
          "CRM — Corporate CC Usage Settlement"
        ],
        "x-permissions": [
          "crm:cc-settlement:complete"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/cc-settlements/{id}/lines/{lineId}/attachment": {
      "post": {
        "operationId": "CcSettlementController_uploadAttachment",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lineId",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Upload / replace the proof of one row (image or PDF, max 10 MB; Draft only)",
        "tags": [
          "CRM — Corporate CC Usage Settlement"
        ],
        "x-permissions": [
          "crm:cc-settlement:update"
        ],
        "x-permissions-mode": "all"
      },
      "delete": {
        "operationId": "CcSettlementController_removeAttachment",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lineId",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Remove the proof of one row (Draft only)",
        "tags": [
          "CRM — Corporate CC Usage Settlement"
        ],
        "x-permissions": [
          "crm:cc-settlement:update"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/clearance": {
      "get": {
        "operationId": "ClearanceController_findAll",
        "parameters": [
          {
            "name": "search",
            "required": false,
            "in": "query",
            "schema": {
              "maxLength": 100,
              "type": "string"
            }
          },
          {
            "name": "status",
            "required": false,
            "in": "query",
            "description": "One value or several comma-separated.",
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "IN_PROGRESS",
                  "COMPLETED"
                ]
              }
            }
          },
          {
            "name": "page",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "List clearance requests (own / as approver; firm-wide for CRM managers)",
        "tags": [
          "CRM — Clearance"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/clearance/approver-options": {
      "get": {
        "operationId": "ClearanceController_approverOptions",
        "parameters": [
          {
            "name": "search",
            "required": false,
            "in": "query",
            "schema": {
              "maxLength": 100,
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Approver picker — active users, name asc, max 50",
        "tags": [
          "CRM — Clearance"
        ],
        "x-permissions": [
          "crm:clearance:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/clearance/generate": {
      "post": {
        "operationId": "ClearanceController_generate",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GenerateClearanceDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Generate clearance requests from employee resign dates (the \"BIPO resign event\" entry point)",
        "tags": [
          "CRM — Clearance"
        ],
        "x-permissions": [
          "crm:clearance:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/clearance/{id}": {
      "get": {
        "operationId": "ClearanceController_findOne",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Get one clearance with its rows, history, related records and notes",
        "tags": [
          "CRM — Clearance"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      },
      "patch": {
        "operationId": "ClearanceController_update",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateClearanceDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Save WP declarations (employee) and approver reassignments (HR) — listed rows only",
        "tags": [
          "CRM — Clearance"
        ],
        "x-permissions": [
          "crm:clearance:revise",
          "crm:clearance:manage"
        ],
        "x-permissions-mode": "any"
      }
    },
    "/api/crm/clearance/{id}/refresh": {
      "post": {
        "operationId": "ClearanceController_refresh",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RefreshClearanceDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Re-derive rows from projects / borrowings (decided rows are kept)",
        "tags": [
          "CRM — Clearance"
        ],
        "x-permissions": [
          "crm:clearance:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/clearance/{id}/rows/{rowId}/approve": {
      "post": {
        "operationId": "ClearanceController_approve",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "rowId",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ApproveClearanceRowDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Approve one row slot assigned to the caller",
        "tags": [
          "CRM — Clearance"
        ],
        "x-permissions": [
          "crm:clearance:decide"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/clearance/{id}/rows/{rowId}/reject": {
      "post": {
        "operationId": "ClearanceController_reject",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "rowId",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RejectClearanceRowDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Reject one row slot assigned to the caller (reason required)",
        "tags": [
          "CRM — Clearance"
        ],
        "x-permissions": [
          "crm:clearance:decide"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/clearance/{id}/rows/{rowId}/resubmit": {
      "post": {
        "operationId": "ClearanceController_resubmit",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "rowId",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ResubmitClearanceRowDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Revise and resubmit a Rejected row (both slots back to Pending)",
        "tags": [
          "CRM — Clearance"
        ],
        "x-permissions": [
          "crm:clearance:revise",
          "crm:clearance:manage"
        ],
        "x-permissions-mode": "any"
      }
    },
    "/api/crm/clearance/{id}/upload-listing": {
      "post": {
        "operationId": "ClearanceController_uploadListing",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Generate & upload listing — BIPO upload is a flag-gated stub (503 disabled / 502 failed)",
        "tags": [
          "CRM — Clearance"
        ],
        "x-permissions": [
          "crm:clearance:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/workflow/rules": {
      "get": {
        "operationId": "WorkflowController_listRules",
        "parameters": [
          {
            "name": "trigger",
            "required": true,
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "List workflow rules (optionally by trigger)",
        "tags": [
          "workflow"
        ],
        "x-permissions": [
          "settings:manage"
        ],
        "x-permissions-mode": "all"
      },
      "post": {
        "operationId": "WorkflowController_createRule",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateWorkflowRuleDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Create a workflow rule",
        "tags": [
          "workflow"
        ],
        "x-permissions": [
          "settings:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/workflow/rules/{id}/enabled": {
      "patch": {
        "operationId": "WorkflowController_setEnabled",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetEnabledDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Enable/disable a workflow rule",
        "tags": [
          "workflow"
        ],
        "x-permissions": [
          "settings:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/workflow/rules/{id}": {
      "delete": {
        "operationId": "WorkflowController_deleteRule",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Delete a workflow rule",
        "tags": [
          "workflow"
        ],
        "x-permissions": [
          "settings:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/workflow/emit": {
      "post": {
        "operationId": "WorkflowController_emit",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmitEventDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Manually fire a trigger with a context (operational/testing)",
        "tags": [
          "workflow"
        ],
        "x-permissions": [
          "settings:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/admin/journey/backfill": {
      "post": {
        "description": "Reconstructs journey rows from CrmDomainEvent history, timestamp columns and the m34 conversion records, only for facts before the first live journey row. Re-running inserts nothing new. dryRun=true only counts.",
        "operationId": "JourneyBackfillController_run",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/JourneyBackfillDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Rebuild the Customer Journey of history recorded before go-live (idempotent; BACKFILL provenance)",
        "tags": [
          "CRM — Customer Journey"
        ],
        "x-permissions": [
          "crm_admin:customer-govern"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/projects/summary": {
      "get": {
        "operationId": "ProjectsController_summary",
        "parameters": [],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProjectSummaryDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Project dashboard summary — totals and top projects (manager+ only)",
        "tags": [
          "Projects"
        ],
        "x-roles": [
          "admin",
          "partner",
          "manager"
        ]
      }
    },
    "/api/projects": {
      "get": {
        "operationId": "ProjectsController_findAll",
        "parameters": [
          {
            "name": "status",
            "required": false,
            "in": "query",
            "description": "Filter by status",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "divisionCode",
            "required": false,
            "in": "query",
            "description": "Filter by division code, e.g. A1, A2, CA",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "search",
            "required": false,
            "in": "query",
            "description": "Free-text search on project name",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sortBy",
            "required": false,
            "in": "query",
            "schema": {
              "enum": [
                "name",
                "startDate",
                "endDate",
                "budget",
                "hoursLogged"
              ],
              "type": "string"
            }
          },
          {
            "name": "order",
            "required": false,
            "in": "query",
            "schema": {
              "enum": [
                "asc",
                "desc"
              ],
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ProjectListItemDto"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "List all projects the user has access to, with computed metrics",
        "tags": [
          "Projects"
        ]
      },
      "post": {
        "operationId": "ProjectsController_createProject",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateProjectDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProjectDetailDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Create a new RSMID-native project (manager+ only)",
        "tags": [
          "Projects"
        ]
      }
    },
    "/api/projects/tasks/{taskId}": {
      "patch": {
        "operationId": "ProjectsController_updateTask",
        "parameters": [
          {
            "name": "taskId",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateProjectTaskDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProjectTaskDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Update a task (status, name, assignee, etc.)",
        "tags": [
          "Projects"
        ]
      }
    },
    "/api/projects/{id}": {
      "get": {
        "operationId": "ProjectsController_findOne",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProjectDetailDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Full project detail — includes tasks, team, and metrics",
        "tags": [
          "Projects"
        ]
      },
      "patch": {
        "operationId": "ProjectsController_updateProject",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateProjectDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProjectDetailDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Update a RSMID-native project (manager of project or admin/partner)",
        "tags": [
          "Projects"
        ]
      }
    },
    "/api/projects/{id}/tasks": {
      "get": {
        "operationId": "ProjectsController_tasks",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ProjectTaskDto"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Tasks for a project",
        "tags": [
          "Projects"
        ]
      },
      "post": {
        "operationId": "ProjectsController_createTask",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateProjectTaskDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProjectTaskDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Create a task in a RSMID-native project (manager+ only)",
        "tags": [
          "Projects"
        ]
      }
    },
    "/api/projects/{id}/team": {
      "get": {
        "operationId": "ProjectsController_team",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ProjectTeamMemberDto"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Team members for a project",
        "tags": [
          "Projects"
        ]
      },
      "post": {
        "operationId": "ProjectsController_addTeamMember",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AddProjectMemberDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProjectTeamMemberDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Add a team member to a RSMID-native project (manager+ only)",
        "tags": [
          "Projects"
        ]
      }
    },
    "/api/projects/{id}/sync-state": {
      "get": {
        "description": "The project source of truth (CR-01) is undecided — :1795 says NetSuite owns the Project master, :1811 says this application does. Until that is answered no push or pull happens at any lifecycle point, so a project that was activated is reported NOT_SYNCHRONISED with the reason and the blocking decision, never as a completed sync.",
        "operationId": "ProjectsController_syncState",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Has this project reached NetSuite, and if not, which decision is holding it?",
        "tags": [
          "Projects"
        ]
      }
    },
    "/api/projects/{id}/metrics": {
      "get": {
        "operationId": "ProjectsController_getMetrics",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProjectMetricsDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Project metrics — hours, budget utilization, team, timeline",
        "tags": [
          "Projects"
        ]
      }
    },
    "/api/project-templates": {
      "get": {
        "description": "Active-only by default. A template with no division is available firm-wide.",
        "operationId": "ProjectTemplateController_findAll",
        "parameters": [
          {
            "name": "divisionCode",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "includeInactive",
            "required": false,
            "in": "query",
            "description": "Defaults to active-only.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "search",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "List project templates",
        "tags": [
          "Project Templates"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      },
      "post": {
        "operationId": "ProjectTemplateController_create",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateProjectTemplateDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Create a project template",
        "tags": [
          "Project Templates"
        ],
        "x-permissions": [
          "crm:project-template:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/project-templates/{id}": {
      "get": {
        "operationId": "ProjectTemplateController_findOne",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Get one project template with its tasks",
        "tags": [
          "Project Templates"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      },
      "patch": {
        "operationId": "ProjectTemplateController_update",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateProjectTemplateDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Edit a template (supplying tasks replaces the whole list)",
        "tags": [
          "Project Templates"
        ],
        "x-permissions": [
          "crm:project-template:manage"
        ],
        "x-permissions-mode": "all"
      },
      "delete": {
        "operationId": "ProjectTemplateController_remove",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Retire a template (soft delete)",
        "tags": [
          "Project Templates"
        ],
        "x-permissions": [
          "project:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/project-templates/{id}/instantiate": {
      "post": {
        "description": "Task due dates derive from the project start date plus each task’s offsetDays. The result is an ordinary project — templates are an RSMID authoring aid and never write to NetSuite (project-master is inbound-only).",
        "operationId": "ProjectTemplateController_instantiate",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InstantiateTemplateDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Create a real project from this template",
        "tags": [
          "Project Templates"
        ],
        "x-permissions": [
          "project:create"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/project-templates": {
      "get": {
        "operationId": "ProjectTemplateController_findAll",
        "parameters": [
          {
            "name": "search",
            "required": false,
            "in": "query",
            "description": "Case-insensitive match on Template ID or Template Name.",
            "schema": {
              "maxLength": 100,
              "type": "string"
            }
          },
          {
            "name": "active",
            "required": false,
            "in": "query",
            "description": "Absent = active AND inactive templates.",
            "schema": {
              "enum": [
                "true",
                "false"
              ],
              "type": "string"
            }
          },
          {
            "name": "page",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "List Project Templates (task count and default hours derived from the grid)",
        "tags": [
          "CRM — Project Template"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      },
      "post": {
        "operationId": "ProjectTemplateController_create",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateProjectTemplateDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Create a Project Template (Template ID assigned: PT-00n)",
        "tags": [
          "CRM — Project Template"
        ],
        "x-permissions": [
          "crm:project-template:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/project-templates/{idOrCode}": {
      "get": {
        "operationId": "ProjectTemplateController_findOne",
        "parameters": [
          {
            "name": "idOrCode",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Get one Project Template by id or Template ID (PT-001) with its Tasks / Milestones grid",
        "tags": [
          "CRM — Project Template"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/project-templates/{id}": {
      "patch": {
        "operationId": "ProjectTemplateController_update",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateProjectTemplateDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Update name / assignment type / grid / active of a Project Template",
        "tags": [
          "CRM — Project Template"
        ],
        "x-permissions": [
          "crm:project-template:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/employees": {
      "get": {
        "operationId": "EmployeesController_findAll",
        "parameters": [
          {
            "name": "search",
            "required": false,
            "in": "query",
            "description": "Whitespace-separated terms, all must match; each term matches Employee ID, payroll number, name, email or grade.",
            "schema": {
              "maxLength": 100,
              "type": "string"
            }
          },
          {
            "name": "status",
            "required": false,
            "in": "query",
            "description": "Active / Resign process / Inactive; one value or several comma-separated.",
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "ACTIVE",
                  "RESIGN_PROCESS",
                  "INACTIVE"
                ]
              }
            }
          },
          {
            "name": "page",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "List the BIPO employee mirror (seniority order; search + Active / Resign process / Inactive)",
        "tags": [
          "CRM — Employee"
        ],
        "x-permissions": [
          "crm:employee:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/employees/{idOrCode}": {
      "get": {
        "operationId": "EmployeesController_findOne",
        "parameters": [
          {
            "name": "idOrCode",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "One employee by id, BIPO EmployeeCode or payroll number, with related records and notes",
        "tags": [
          "CRM — Employee"
        ],
        "x-permissions": [
          "crm:employee:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/reports/proposal-aging": {
      "get": {
        "operationId": "ProposalAgingController_report",
        "parameters": [
          {
            "name": "asOf",
            "required": false,
            "in": "query",
            "description": "As of date (YYYY-MM-DD). Defaults to today in Asia/Jakarta.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Proposal Aging — open (Draft/Pending/In Progress) proposals aged as of a date",
        "tags": [
          "CRM — Reports"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/reports/end-to-end-journey": {
      "get": {
        "operationId": "EndToEndJourneyController_report",
        "parameters": [
          {
            "name": "search",
            "required": false,
            "in": "query",
            "description": "Case-insensitive match on opportunity number, client name, company snapshot, customer name, proposal number or EL number.",
            "schema": {
              "maxLength": 100,
              "type": "string"
            }
          },
          {
            "name": "from",
            "required": false,
            "in": "query",
            "description": "Opportunity journey date on or after (YYYY-MM-DD).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "required": false,
            "in": "query",
            "description": "Opportunity journey date on or before (YYYY-MM-DD).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "description": "Rows returned (averages cover all rows).",
            "schema": {
              "minimum": 1,
              "maximum": 1000,
              "default": 500,
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "End to End Journey — Opportunity → Proposal → EL durations (days) per journey, with averages",
        "tags": [
          "CRM — Reports"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/reports/project-profitability": {
      "get": {
        "operationId": "ProjectProfitabilityController_report",
        "parameters": [
          {
            "name": "period",
            "required": false,
            "in": "query",
            "description": "YYYY-H1 | YYYY-H2 | YYYY-Q1..Q4. Absent → the half-year containing today (Asia/Jakarta).",
            "schema": {
              "example": "2026-H2",
              "type": "string"
            }
          },
          {
            "name": "subsidiary",
            "required": false,
            "in": "query",
            "description": "Subsidiary (Entity) code, or __none for projects without one.",
            "schema": {
              "maxLength": 64,
              "type": "string"
            }
          },
          {
            "name": "lob",
            "required": false,
            "in": "query",
            "description": "Line of Business code, or __none for projects without one.",
            "schema": {
              "maxLength": 64,
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Project Profitability — Revenue, Unbilled AR, Direct Cost, Gross Profit, Margin by Subsidiary/LOB/Division/Project, plus a monthly revenue matrix",
        "tags": [
          "CRM — Reports"
        ],
        "x-permissions": [
          "crm:read",
          "project:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/reports/actual-hours-wip": {
      "get": {
        "operationId": "ActualHoursWipController_report",
        "parameters": [
          {
            "name": "asOf",
            "required": false,
            "in": "query",
            "description": "As of date (YYYY-MM-DD); default = today in Asia/Jakarta. Only Aging depends on it.",
            "schema": {
              "example": "2026-09-13",
              "type": "string"
            }
          },
          {
            "name": "page",
            "required": false,
            "in": "query",
            "schema": {
              "minimum": 1,
              "default": 1,
              "type": "number"
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "description": "Rows per page (summary covers all rows).",
            "schema": {
              "minimum": 1,
              "maximum": 200,
              "default": 50,
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Actual Hours WIP — approved billable timesheet hours not yet covered by an IIR, per project × employee",
        "tags": [
          "CRM — Reports"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/reports/utilization": {
      "get": {
        "operationId": "UtilizationController_report",
        "parameters": [
          {
            "name": "period",
            "required": false,
            "in": "query",
            "description": "Period: a month (YYYY-MM) or a quarter (YYYY-Q1..Q4). Default = the current month (Asia/Jakarta).",
            "schema": {
              "example": "2026-08",
              "type": "string"
            }
          },
          {
            "name": "lobId",
            "required": false,
            "in": "query",
            "description": "LineOfBusiness.id — employees whose division belongs to this LOB.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "divisionId",
            "required": false,
            "in": "query",
            "description": "Division.id — employees of this division.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Utilization by Employee — Available / Exempt / Net / Utilized / Productive hours and Utilization / Productivity per employee",
        "tags": [
          "CRM — Reports"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/reports/netsuite-web-query": {
      "get": {
        "operationId": "NetsuiteWebQueryController_view",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "NetSuite Web Query — configured saved searches with deep links (no result rows: NetSuite REST cannot run saved searches)",
        "tags": [
          "CRM — Reports"
        ],
        "x-permissions": [
          "crm:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/reports/netsuite-web-query/config": {
      "put": {
        "operationId": "NetsuiteWebQueryController_updateConfig",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateNetsuiteWebQueryConfigDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Configure the NetSuite Web Query saved searches (admin)",
        "tags": [
          "CRM — Reports"
        ],
        "x-permissions": [
          "settings:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/reports/cfbc": {
      "get": {
        "operationId": "CfbcController_report",
        "parameters": [
          {
            "name": "year",
            "required": false,
            "in": "query",
            "description": "Calendar year of the Jan..Dec columns (header \"FY<year>\"). Default: current year in Asia/Jakarta.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "divisionId",
            "required": false,
            "in": "query",
            "description": "Division id OR code; \"all\" = All divisions (unrestricted callers only). Empty / absent = the default division for the caller.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "CFBC — per-project Confirmed Fee with monthly Fee Revenue / Cash Fee Revenue timing for one division-year",
        "tags": [
          "CRM — Reports"
        ],
        "x-permissions": [
          "crm:cfbc:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/crm/reports/cfbc/entries": {
      "put": {
        "operationId": "CfbcController_saveEntries",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SaveCfbcEntriesDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Save Fee Revenue / Cash Fee Revenue timing for one or more projects (optimistic lock per entry)",
        "tags": [
          "CRM — Reports"
        ],
        "x-permissions": [
          "crm:cfbc:update"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/netsuite/opportunities": {
      "get": {
        "operationId": "NsOpportunityController_findAll",
        "parameters": [
          {
            "name": "page",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "stage",
            "required": false,
            "in": "query",
            "schema": {
              "enum": [
                "LEAD",
                "QUALIFICATION",
                "PROPOSAL",
                "NEGOTIATION",
                "WON",
                "LOST"
              ],
              "type": "string"
            }
          },
          {
            "name": "syncStatus",
            "required": false,
            "in": "query",
            "schema": {
              "enum": [
                "PENDING",
                "SYNCED",
                "FAILED",
                "SKIPPED",
                "LOCAL_ONLY",
                "READY_TO_PUSH",
                "SYNCING",
                "RETRYING",
                "PARTIALLY_SYNCED"
              ],
              "type": "string"
            }
          },
          {
            "name": "search",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "List local-first opportunities (own for staff; all for managers)",
        "tags": [
          "NetSuite CRM Opportunity"
        ],
        "x-permissions": [
          "opportunity:read"
        ],
        "x-permissions-mode": "all"
      },
      "post": {
        "operationId": "NsOpportunityController_create",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateNsOpportunityDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Create an opportunity (LOCAL_ONLY; pass pushNow to push immediately)",
        "tags": [
          "NetSuite CRM Opportunity"
        ],
        "x-permissions": [
          "opportunity:create"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/netsuite/opportunities/{id}": {
      "get": {
        "operationId": "NsOpportunityController_findOne",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Get one opportunity (reconciles sync status)",
        "tags": [
          "NetSuite CRM Opportunity"
        ],
        "x-permissions": [
          "opportunity:read"
        ],
        "x-permissions-mode": "all"
      },
      "patch": {
        "operationId": "NsOpportunityController_update",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateNsOpportunityDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Edit an opportunity (local; a synced record becomes READY_TO_PUSH)",
        "tags": [
          "NetSuite CRM Opportunity"
        ],
        "x-permissions": [
          "opportunity:update"
        ],
        "x-permissions-mode": "all"
      },
      "delete": {
        "operationId": "NsOpportunityController_remove",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Soft-delete an opportunity",
        "tags": [
          "NetSuite CRM Opportunity"
        ],
        "x-permissions": [
          "opportunity:delete"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/netsuite/opportunities/{id}/push": {
      "post": {
        "operationId": "NsOpportunityController_push",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Push to NetSuite now (idempotent upsert)",
        "tags": [
          "NetSuite CRM Opportunity"
        ],
        "x-permissions": [
          "opportunity:push"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/netsuite/opportunities/{id}/push-later": {
      "post": {
        "operationId": "NsOpportunityController_pushLater",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Queue a deferred push (Push Later)",
        "tags": [
          "NetSuite CRM Opportunity"
        ],
        "x-permissions": [
          "opportunity:push"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/netsuite/opportunities/{id}/retry": {
      "post": {
        "operationId": "NsOpportunityController_retry",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Retry a failed push",
        "tags": [
          "NetSuite CRM Opportunity"
        ],
        "x-permissions": [
          "opportunity:push"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/netsuite/opportunities/{id}/sync-history": {
      "get": {
        "operationId": "NsOpportunityController_syncHistory",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Queue/sync history for this opportunity",
        "tags": [
          "NetSuite CRM Opportunity"
        ],
        "x-permissions": [
          "opportunity:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/netsuite/estimates/items": {
      "get": {
        "operationId": "NsEstimateController_items",
        "parameters": [
          {
            "name": "search",
            "required": true,
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "NetSuite item picker for estimate lines (live)",
        "tags": [
          "NetSuite Estimate / EL"
        ],
        "x-permissions": [
          "estimate:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/netsuite/estimates": {
      "get": {
        "operationId": "NsEstimateController_findAll",
        "parameters": [
          {
            "name": "page",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "status",
            "required": false,
            "in": "query",
            "schema": {
              "enum": [
                "DRAFT",
                "SENT",
                "ACCEPTED",
                "DECLINED"
              ],
              "type": "string"
            }
          },
          {
            "name": "syncStatus",
            "required": false,
            "in": "query",
            "schema": {
              "enum": [
                "PENDING",
                "SYNCED",
                "FAILED",
                "SKIPPED",
                "LOCAL_ONLY",
                "READY_TO_PUSH",
                "SYNCING",
                "RETRYING",
                "PARTIALLY_SYNCED"
              ],
              "type": "string"
            }
          },
          {
            "name": "search",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "List estimates (own for staff; all for managers)",
        "tags": [
          "NetSuite Estimate / EL"
        ],
        "x-permissions": [
          "estimate:read"
        ],
        "x-permissions-mode": "all"
      },
      "post": {
        "operationId": "NsEstimateController_create",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateNsEstimateDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Create an estimate (LOCAL_ONLY; pushNow to push)",
        "tags": [
          "NetSuite Estimate / EL"
        ],
        "x-permissions": [
          "estimate:create"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/netsuite/estimates/{id}": {
      "get": {
        "operationId": "NsEstimateController_findOne",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Get one estimate (reconciles sync status)",
        "tags": [
          "NetSuite Estimate / EL"
        ],
        "x-permissions": [
          "estimate:read"
        ],
        "x-permissions-mode": "all"
      },
      "patch": {
        "operationId": "NsEstimateController_update",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateNsEstimateDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Edit an estimate",
        "tags": [
          "NetSuite Estimate / EL"
        ],
        "x-permissions": [
          "estimate:update"
        ],
        "x-permissions-mode": "all"
      },
      "delete": {
        "operationId": "NsEstimateController_remove",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Soft-delete an estimate",
        "tags": [
          "NetSuite Estimate / EL"
        ],
        "x-permissions": [
          "estimate:delete"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/netsuite/estimates/{id}/push": {
      "post": {
        "operationId": "NsEstimateController_push",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Push to NetSuite now (idempotent upsert)",
        "tags": [
          "NetSuite Estimate / EL"
        ],
        "x-permissions": [
          "estimate:push"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/netsuite/estimates/{id}/push-later": {
      "post": {
        "operationId": "NsEstimateController_pushLater",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Queue a deferred push (Push Later)",
        "tags": [
          "NetSuite Estimate / EL"
        ],
        "x-permissions": [
          "estimate:push"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/netsuite/estimates/{id}/retry": {
      "post": {
        "operationId": "NsEstimateController_retry",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Retry a failed push",
        "tags": [
          "NetSuite Estimate / EL"
        ],
        "x-permissions": [
          "estimate:push"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/netsuite/estimates/{id}/sync-history": {
      "get": {
        "operationId": "NsEstimateController_syncHistory",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Queue/sync history for this estimate",
        "tags": [
          "NetSuite Estimate / EL"
        ],
        "x-permissions": [
          "estimate:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/netsuite/sales-orders/items": {
      "get": {
        "operationId": "NsSalesOrderController_items",
        "parameters": [
          {
            "name": "search",
            "required": true,
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "NetSuite item picker for sales-order lines (live)",
        "tags": [
          "NetSuite Sales Order / Kontrak-SPK"
        ],
        "x-permissions": [
          "sales_order:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/netsuite/sales-orders": {
      "get": {
        "operationId": "NsSalesOrderController_findAll",
        "parameters": [
          {
            "name": "page",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "status",
            "required": false,
            "in": "query",
            "schema": {
              "enum": [
                "DRAFT",
                "PENDING_APPROVAL",
                "APPROVED",
                "REJECTED",
                "CLOSED"
              ],
              "type": "string"
            }
          },
          {
            "name": "syncStatus",
            "required": false,
            "in": "query",
            "schema": {
              "enum": [
                "PENDING",
                "SYNCED",
                "FAILED",
                "SKIPPED",
                "LOCAL_ONLY",
                "READY_TO_PUSH",
                "SYNCING",
                "RETRYING",
                "PARTIALLY_SYNCED"
              ],
              "type": "string"
            }
          },
          {
            "name": "search",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "List sales orders (own for staff; all for managers)",
        "tags": [
          "NetSuite Sales Order / Kontrak-SPK"
        ],
        "x-permissions": [
          "sales_order:read"
        ],
        "x-permissions-mode": "all"
      },
      "post": {
        "operationId": "NsSalesOrderController_create",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateNsSalesOrderDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Create a sales order / SPK (LOCAL_ONLY; pushNow to push)",
        "tags": [
          "NetSuite Sales Order / Kontrak-SPK"
        ],
        "x-permissions": [
          "sales_order:create"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/netsuite/sales-orders/from-estimate": {
      "post": {
        "description": "Carries client, currency and lines across from the estimate.",
        "operationId": "NsSalesOrderController_fromEstimate",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateFromEstimateDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Raise an SPK from an ACCEPTED estimate",
        "tags": [
          "NetSuite Sales Order / Kontrak-SPK"
        ],
        "x-permissions": [
          "sales_order:create"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/netsuite/sales-orders/{id}": {
      "get": {
        "operationId": "NsSalesOrderController_findOne",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Get one sales order (reconciles sync status)",
        "tags": [
          "NetSuite Sales Order / Kontrak-SPK"
        ],
        "x-permissions": [
          "sales_order:read"
        ],
        "x-permissions-mode": "all"
      },
      "patch": {
        "operationId": "NsSalesOrderController_update",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateNsSalesOrderDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Edit a sales order",
        "tags": [
          "NetSuite Sales Order / Kontrak-SPK"
        ],
        "x-permissions": [
          "sales_order:update"
        ],
        "x-permissions-mode": "all"
      },
      "delete": {
        "operationId": "NsSalesOrderController_remove",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Soft-delete a sales order",
        "tags": [
          "NetSuite Sales Order / Kontrak-SPK"
        ],
        "x-permissions": [
          "sales_order:delete"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/netsuite/sales-orders/{id}/push": {
      "post": {
        "operationId": "NsSalesOrderController_push",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Push to NetSuite now (idempotent upsert)",
        "tags": [
          "NetSuite Sales Order / Kontrak-SPK"
        ],
        "x-permissions": [
          "sales_order:push"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/netsuite/sales-orders/{id}/push-later": {
      "post": {
        "operationId": "NsSalesOrderController_pushLater",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Queue a deferred push (Push Later)",
        "tags": [
          "NetSuite Sales Order / Kontrak-SPK"
        ],
        "x-permissions": [
          "sales_order:push"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/netsuite/sales-orders/{id}/retry": {
      "post": {
        "operationId": "NsSalesOrderController_retry",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Retry a failed push",
        "tags": [
          "NetSuite Sales Order / Kontrak-SPK"
        ],
        "x-permissions": [
          "sales_order:push"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/netsuite/sales-orders/{id}/sync-history": {
      "get": {
        "operationId": "NsSalesOrderController_syncHistory",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Queue/sync history for this sales order",
        "tags": [
          "NetSuite Sales Order / Kontrak-SPK"
        ],
        "x-permissions": [
          "sales_order:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/client-surveys/summary": {
      "get": {
        "description": "Drafts are excluded — an unsubmitted draft is not yet the client’s word.",
        "operationId": "ClientSurveyController_summary",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Firm-wide satisfaction summary",
        "tags": [
          "Client Survey"
        ],
        "x-permissions": [
          "survey:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/client-surveys": {
      "get": {
        "operationId": "ClientSurveyController_findAll",
        "parameters": [
          {
            "name": "page",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "status",
            "required": false,
            "in": "query",
            "schema": {
              "enum": [
                "DRAFT",
                "SENT",
                "SUBMITTED",
                "CLOSED"
              ],
              "type": "string"
            }
          },
          {
            "name": "serviceLine",
            "required": false,
            "in": "query",
            "schema": {
              "enum": [
                "AUDIT_ASSURANCE",
                "TAX",
                "ADVISORY",
                "TRANSACTION_SERVICES"
              ],
              "type": "string"
            }
          },
          {
            "name": "netsuiteClientId",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "search",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "List surveys (own for staff; firm-wide for managers)",
        "tags": [
          "Client Survey"
        ],
        "x-permissions": [
          "survey:read"
        ],
        "x-permissions-mode": "all"
      },
      "post": {
        "description": "overallRating is derived from the responses — it is not accepted from the caller.",
        "operationId": "ClientSurveyController_create",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateClientSurveyDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Record a client survey (DRAFT)",
        "tags": [
          "Client Survey"
        ],
        "x-permissions": [
          "survey:create"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/client-surveys/{id}": {
      "get": {
        "operationId": "ClientSurveyController_findOne",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Get one survey with its responses",
        "tags": [
          "Client Survey"
        ],
        "x-permissions": [
          "survey:read"
        ],
        "x-permissions-mode": "all"
      },
      "patch": {
        "operationId": "ClientSurveyController_update",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateClientSurveyDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Edit a DRAFT survey (supplying responses replaces the set)",
        "tags": [
          "Client Survey"
        ],
        "x-permissions": [
          "survey:create"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/client-surveys/{id}/submit": {
      "post": {
        "operationId": "ClientSurveyController_submit",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Submit a survey — it becomes read-only",
        "tags": [
          "Client Survey"
        ],
        "x-permissions": [
          "survey:respond"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/client-surveys/{id}/close": {
      "post": {
        "operationId": "ClientSurveyController_close",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Close a submitted survey (managers only)",
        "tags": [
          "Client Survey"
        ],
        "x-permissions": [
          "survey:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/reports": {
      "get": {
        "operationId": "ReportsController_list",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "List available reports",
        "tags": [
          "Reports & Analytics"
        ],
        "x-permissions": [
          "report:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/reports/{code}": {
      "get": {
        "description": "Results are cached in ReportSnapshot for 10 minutes; pass refresh=true to rebuild. Every execution is written to ReportAccessLog.",
        "operationId": "ReportsController_run",
        "parameters": [
          {
            "name": "code",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "required": false,
            "in": "query",
            "description": "Inclusive start date (YYYY-MM-DD).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "required": false,
            "in": "query",
            "description": "Inclusive end date (YYYY-MM-DD).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "divisionId",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "departmentCode",
            "required": false,
            "in": "query",
            "description": "NetSuite department code.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "employeeId",
            "required": false,
            "in": "query",
            "description": "RSMID user id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "clientId",
            "required": false,
            "in": "query",
            "description": "NetSuite customer internalId.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "projectId",
            "required": false,
            "in": "query",
            "description": "RSMID project id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "syncStatus",
            "required": false,
            "in": "query",
            "description": "Local-first sync status.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "refresh",
            "required": false,
            "in": "query",
            "description": "Bypass the snapshot cache and rebuild.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Run a report",
        "tags": [
          "Reports & Analytics"
        ],
        "x-permissions": [
          "report:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/reports/{code}/export.csv": {
      "get": {
        "description": "Pass ?section=<name> to pick a section; omit it to export the first tabular section. Unknown sections return 400 listing what is exportable. Runs through the same cached, access-logged report engine as the JSON route.",
        "operationId": "ReportsController_exportCsv",
        "parameters": [
          {
            "name": "code",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "required": false,
            "in": "query",
            "description": "Inclusive start date (YYYY-MM-DD).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "required": false,
            "in": "query",
            "description": "Inclusive end date (YYYY-MM-DD).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "divisionId",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "departmentCode",
            "required": false,
            "in": "query",
            "description": "NetSuite department code.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "employeeId",
            "required": false,
            "in": "query",
            "description": "RSMID user id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "clientId",
            "required": false,
            "in": "query",
            "description": "NetSuite customer internalId.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "projectId",
            "required": false,
            "in": "query",
            "description": "RSMID project id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "syncStatus",
            "required": false,
            "in": "query",
            "description": "Local-first sync status.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "refresh",
            "required": false,
            "in": "query",
            "description": "Bypass the snapshot cache and rebuild.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "section",
            "required": false,
            "in": "query",
            "description": "Tabular section to export; defaults to the first exportable one.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Export one tabular section of a report as CSV",
        "tags": [
          "Reports & Analytics"
        ],
        "x-permissions": [
          "report:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/netsuite/sso-url": {
      "get": {
        "operationId": "NetsuiteController_getSsoUrl",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Get NetSuite SSO URL and configuration status",
        "tags": [
          "NetSuite"
        ]
      }
    },
    "/api/netsuite/sso-launch-log": {
      "post": {
        "operationId": "NetsuiteController_logLaunch",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/NetsuiteLaunchLogDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Log a NetSuite or HRMS SSO launch event for audit trail",
        "tags": [
          "NetSuite"
        ]
      }
    },
    "/api/netsuite/master/customers": {
      "get": {
        "description": "NetSuite owns customer records; RSMID never writes them.",
        "operationId": "NetsuiteMasterController_customers",
        "parameters": [
          {
            "name": "page",
            "required": true,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "required": true,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "search",
            "required": true,
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Client master (live NetSuite pull, read-only)",
        "tags": [
          "NetSuite Master Data (read-only)"
        ],
        "x-permissions": [
          "opportunity:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/netsuite/master/projects": {
      "get": {
        "operationId": "NetsuiteMasterController_projects",
        "parameters": [
          {
            "name": "page",
            "required": true,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "required": true,
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Project master (live NetSuite pull, read-only)",
        "tags": [
          "NetSuite Master Data (read-only)"
        ],
        "x-permissions": [
          "project:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/netsuite/master/vendors": {
      "get": {
        "operationId": "NetsuiteMasterController_vendors",
        "parameters": [
          {
            "name": "page",
            "required": true,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "required": true,
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Vendors (live NetSuite pull, read-only)",
        "tags": [
          "NetSuite Master Data (read-only)"
        ],
        "x-permissions": [
          "expense:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/netsuite/master/invoices": {
      "get": {
        "operationId": "NetsuiteMasterController_invoices",
        "parameters": [
          {
            "name": "page",
            "required": true,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "required": true,
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Invoices (live NetSuite pull, read-only)",
        "tags": [
          "NetSuite Master Data (read-only)"
        ],
        "x-permissions": [
          "finance:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/netsuite/master/gl-accounts": {
      "get": {
        "operationId": "NetsuiteMasterController_glAccounts",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "GL accounts (live NetSuite pull, read-only)",
        "tags": [
          "NetSuite Master Data (read-only)"
        ],
        "x-permissions": [
          "finance:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/netsuite/master/departments": {
      "get": {
        "operationId": "NetsuiteMasterController_departments",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Departments (live NetSuite pull, read-only)",
        "tags": [
          "NetSuite Master Data (read-only)"
        ],
        "x-permissions": [
          "report:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/intranet/me": {
      "get": {
        "operationId": "IntranetController_me",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Employee intranet profile — user info, recent projects, timesheets, pending approvals",
        "tags": [
          "Intranet"
        ]
      }
    },
    "/api/intranet/shortcuts": {
      "get": {
        "operationId": "IntranetController_shortcuts",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Available integration shortcuts for the current user based on role",
        "tags": [
          "Intranet"
        ]
      }
    },
    "/api/budgets/divisions": {
      "get": {
        "operationId": "BudgetController_getDivisions",
        "parameters": [],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/DivisionDto"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "List all active divisions for budget scoping",
        "tags": [
          "Budget"
        ]
      }
    },
    "/api/budgets/projects": {
      "get": {
        "operationId": "BudgetController_getProjects",
        "parameters": [],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/BudgetProjectOptionDto"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "List RSMID and AperguSuite projects for project budget assignment",
        "tags": [
          "Budget"
        ],
        "x-roles": [
          "admin",
          "partner",
          "finance_admin",
          "manager"
        ]
      }
    },
    "/api/budgets/approvals/pending": {
      "get": {
        "operationId": "BudgetController_pendingRevisions",
        "parameters": [],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/BudgetRevisionItemDto"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Approval queue — pending budget revisions (partner/admin only)",
        "tags": [
          "Budget"
        ],
        "x-roles": [
          "admin",
          "partner"
        ]
      }
    },
    "/api/budgets/revisions/{id}/approve": {
      "post": {
        "operationId": "BudgetController_approveRevision",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReviewRevisionDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BudgetRevisionItemDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Approve a budget revision (partner/admin only)",
        "tags": [
          "Budget"
        ],
        "x-roles": [
          "admin",
          "partner"
        ]
      }
    },
    "/api/budgets/revisions/{id}/reject": {
      "post": {
        "operationId": "BudgetController_rejectRevision",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReviewRevisionDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BudgetRevisionItemDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Reject a budget revision (partner/admin only)",
        "tags": [
          "Budget"
        ],
        "x-roles": [
          "admin",
          "partner"
        ]
      }
    },
    "/api/budgets": {
      "get": {
        "operationId": "BudgetController_list",
        "parameters": [
          {
            "name": "budgetType",
            "required": false,
            "in": "query",
            "schema": {
              "enum": [
                "ORG_BUDGET",
                "PROJECT_BUDGET"
              ],
              "type": "string"
            }
          },
          {
            "name": "fiscalYear",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "divisionId",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "required": false,
            "in": "query",
            "schema": {
              "enum": [
                "DRAFT",
                "ACTIVE",
                "LOCKED",
                "ARCHIVED"
              ],
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/BudgetListItemDto"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "List budget plans (filtered by role: admin/partner/finance_admin see all, manager sees own division)",
        "tags": [
          "Budget"
        ],
        "x-roles": [
          "admin",
          "partner",
          "finance_admin",
          "manager"
        ]
      },
      "post": {
        "operationId": "BudgetController_create",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateBudgetDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BudgetDetailDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Create a new budget plan (admin/finance_admin only)",
        "tags": [
          "Budget"
        ],
        "x-roles": [
          "admin",
          "finance_admin"
        ]
      }
    },
    "/api/budgets/{id}": {
      "get": {
        "operationId": "BudgetController_findOne",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BudgetDetailDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Budget plan detail with monthly targets and revisions",
        "tags": [
          "Budget"
        ],
        "x-roles": [
          "admin",
          "partner",
          "finance_admin",
          "manager"
        ]
      },
      "patch": {
        "operationId": "BudgetController_update",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateBudgetDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BudgetDetailDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Update a budget plan (admin/finance_admin only)",
        "tags": [
          "Budget"
        ],
        "x-roles": [
          "admin",
          "finance_admin"
        ]
      }
    },
    "/api/budgets/{id}/monthly-targets": {
      "get": {
        "operationId": "BudgetController_monthlyTargets",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/MonthlyTargetDto"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Monthly target breakdown with actuals for a budget plan",
        "tags": [
          "Budget"
        ],
        "x-roles": [
          "admin",
          "partner",
          "finance_admin",
          "manager"
        ]
      },
      "patch": {
        "operationId": "BudgetController_updateMonthlyTargets",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateMonthlyTargetsDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/MonthlyTargetDto"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Update monthly target breakdown (admin/finance_admin only)",
        "tags": [
          "Budget"
        ],
        "x-roles": [
          "admin",
          "finance_admin"
        ]
      }
    },
    "/api/budgets/{id}/revisions": {
      "post": {
        "operationId": "BudgetController_submitRevision",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SubmitRevisionDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BudgetRevisionItemDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Submit a budget revision request",
        "tags": [
          "Budget"
        ],
        "x-roles": [
          "admin",
          "partner",
          "finance_admin",
          "manager"
        ]
      }
    },
    "/api/budgets/netsuite/account-map": {
      "get": {
        "description": "Data-driven via IntegrationFieldMapping; a measure with no account is not posted.",
        "operationId": "BudgetController_accountMap",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Configured budget measure → NetSuite GL account mapping",
        "tags": [
          "Budget"
        ],
        "x-permissions": [
          "budget:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/budgets/{id}/push": {
      "post": {
        "description": "Each mapped measure posts as its own NetSuite budgetImport line (periodAmount1..12). NetSuite exposes no single budget record, so measures post independently.",
        "operationId": "BudgetController_push",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PushBudgetDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Post an APPROVED budget to NetSuite",
        "tags": [
          "Budget"
        ],
        "x-permissions": [
          "budget:approve"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/budgets/{id}/retry": {
      "post": {
        "operationId": "BudgetController_retry",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Retry only the measures that failed to post",
        "tags": [
          "Budget"
        ],
        "x-permissions": [
          "budget:approve"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/budgets/{id}/sync-status": {
      "get": {
        "operationId": "BudgetController_syncStatus",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Per-measure NetSuite posting status for this budget",
        "tags": [
          "Budget"
        ],
        "x-permissions": [
          "budget:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/budgets/{id}/lines/{lineId}/sync-history": {
      "get": {
        "operationId": "BudgetController_lineHistory",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lineId",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Queue/sync history for one measure's budget line",
        "tags": [
          "Budget"
        ],
        "x-permissions": [
          "budget:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/budget-hours/template": {
      "get": {
        "operationId": "BudgetHoursController_getTemplate",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Rate card, team roles, LOBs and the editable template catalog",
        "tags": [
          "Budget Hours"
        ],
        "x-roles": [
          "admin",
          "partner",
          "finance_admin",
          "manager"
        ]
      }
    },
    "/api/budget-hours/resources": {
      "get": {
        "operationId": "BudgetHoursController_getResources",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Master Employee options for the team Resource field",
        "tags": [
          "Budget Hours"
        ],
        "x-roles": [
          "admin",
          "partner",
          "finance_admin",
          "manager"
        ]
      }
    },
    "/api/budget-hours/resources/{userId}/rate": {
      "get": {
        "description": "Returns an explicit \"grade unavailable — OQ-35\" state with a null level and null rate while the source of employee grade is unconfirmed. It never falls back to a default rate: a defaulted rate would misprice the engagement in a way that looks like a number rather than like a failure.",
        "operationId": "BudgetHoursController_rateForResource",
        "parameters": [
          {
            "name": "userId",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Rate-card level and rate for an assigned employee",
        "tags": [
          "Budget Hours"
        ],
        "x-roles": [
          "admin",
          "partner",
          "finance_admin",
          "manager"
        ]
      }
    },
    "/api/budget-hours/templates": {
      "post": {
        "operationId": "BudgetHoursController_createTemplate",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateBudgetTemplateDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Create a new budget template",
        "tags": [
          "Budget Hours"
        ],
        "x-roles": [
          "admin",
          "partner",
          "finance_admin",
          "manager"
        ]
      }
    },
    "/api/budget-hours/templates/{templateId}": {
      "patch": {
        "operationId": "BudgetHoursController_updateTemplate",
        "parameters": [
          {
            "name": "templateId",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateBudgetTemplateDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Edit a template (content changes bump its version)",
        "tags": [
          "Budget Hours"
        ],
        "x-roles": [
          "admin",
          "partner",
          "finance_admin",
          "manager"
        ]
      }
    },
    "/api/budget-hours/templates/{templateId}/clone": {
      "post": {
        "operationId": "BudgetHoursController_cloneTemplate",
        "parameters": [
          {
            "name": "templateId",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CloneBudgetTemplateDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Clone a template as a customizable copy",
        "tags": [
          "Budget Hours"
        ],
        "x-roles": [
          "admin",
          "partner",
          "finance_admin",
          "manager"
        ]
      }
    },
    "/api/budget-hours": {
      "get": {
        "operationId": "BudgetHoursController_list",
        "parameters": [
          {
            "name": "status",
            "required": false,
            "in": "query",
            "schema": {
              "enum": [
                "DRAFT",
                "SUBMITTED",
                "APPROVED",
                "REJECTED"
              ],
              "type": "string"
            }
          },
          {
            "name": "q",
            "required": false,
            "in": "query",
            "description": "Match against plan number / client / assignment",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "division",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "required": false,
            "in": "query",
            "schema": {
              "default": 1,
              "type": "number"
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "schema": {
              "default": 20,
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated plan list with computed totals"
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "List budget hours plans",
        "tags": [
          "Budget Hours"
        ],
        "x-roles": [
          "admin",
          "partner",
          "finance_admin",
          "manager"
        ]
      },
      "post": {
        "operationId": "BudgetHoursController_create",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateBudgetHoursPlanDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Create a plan (defaults to the firm activity template)",
        "tags": [
          "Budget Hours"
        ],
        "x-roles": [
          "admin",
          "partner",
          "finance_admin",
          "manager"
        ]
      }
    },
    "/api/budget-hours/{id}": {
      "get": {
        "operationId": "BudgetHoursController_findOne",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Plan detail with computed summary",
        "tags": [
          "Budget Hours"
        ],
        "x-roles": [
          "admin",
          "partner",
          "finance_admin",
          "manager"
        ]
      },
      "patch": {
        "operationId": "BudgetHoursController_update",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateBudgetHoursPlanDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Update header/matrix (DRAFT or REJECTED only)",
        "tags": [
          "Budget Hours"
        ],
        "x-roles": [
          "admin",
          "partner",
          "finance_admin",
          "manager"
        ]
      }
    },
    "/api/budget-hours/{id}/submit": {
      "post": {
        "operationId": "BudgetHoursController_submit",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Submit for approval",
        "tags": [
          "Budget Hours"
        ],
        "x-roles": [
          "admin",
          "partner",
          "finance_admin",
          "manager"
        ]
      }
    },
    "/api/budget-hours/{id}/approve": {
      "post": {
        "operationId": "BudgetHoursController_approve",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReviewBudgetHoursPlanDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Approve a submitted plan",
        "tags": [
          "Budget Hours"
        ],
        "x-roles": [
          "admin",
          "partner"
        ]
      }
    },
    "/api/budget-hours/{id}/reject": {
      "post": {
        "operationId": "BudgetHoursController_reject",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReviewBudgetHoursPlanDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Reject a submitted plan (reopens editing)",
        "tags": [
          "Budget Hours"
        ],
        "x-roles": [
          "admin",
          "partner"
        ]
      }
    },
    "/api/budget-hours/{id}/audit": {
      "get": {
        "operationId": "BudgetHoursController_audit",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Audit trail for a plan",
        "tags": [
          "Budget Hours"
        ],
        "x-roles": [
          "admin",
          "partner",
          "finance_admin",
          "manager"
        ]
      }
    },
    "/api/expenses/approvals/pending": {
      "get": {
        "operationId": "ExpensesController_pendingApprovals",
        "parameters": [],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ExpenseListItemDto"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Expense reports awaiting approval (stage-filtered by role)",
        "tags": [
          "Expenses"
        ],
        "x-roles": [
          "admin",
          "partner",
          "manager",
          "finance_admin"
        ]
      }
    },
    "/api/expenses": {
      "get": {
        "operationId": "ExpensesController_list",
        "parameters": [
          {
            "name": "status",
            "required": false,
            "in": "query",
            "schema": {
              "enum": [
                "DRAFT",
                "SUBMITTED",
                "MANAGER_APPROVED",
                "APPROVED",
                "REJECTED",
                "PAID"
              ],
              "type": "string"
            }
          },
          {
            "name": "employeeId",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "projectId",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "category",
            "required": false,
            "in": "query",
            "schema": {
              "enum": [
                "TRAVEL",
                "MEAL",
                "ACCOMMODATION",
                "TRANSPORTATION",
                "ENTERTAINMENT",
                "COMMUNICATION",
                "OFFICE_SUPPLIES",
                "OTHER"
              ],
              "type": "string"
            }
          },
          {
            "name": "search",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ExpenseListItemDto"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "List expense reports (own or all, by role)",
        "tags": [
          "Expenses"
        ]
      },
      "post": {
        "operationId": "ExpensesController_create",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateExpenseDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExpenseDetailDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Create a new expense report (DRAFT)",
        "tags": [
          "Expenses"
        ]
      }
    },
    "/api/expenses/{id}": {
      "get": {
        "operationId": "ExpensesController_findOne",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExpenseDetailDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Expense report detail with items and attachments",
        "tags": [
          "Expenses"
        ]
      },
      "patch": {
        "operationId": "ExpensesController_update",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateExpenseDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExpenseDetailDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Update a DRAFT or REJECTED expense report",
        "tags": [
          "Expenses"
        ]
      }
    },
    "/api/expenses/{id}/submit": {
      "post": {
        "operationId": "ExpensesController_submit",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExpenseDetailDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Submit a DRAFT expense report for manager approval",
        "tags": [
          "Expenses"
        ]
      }
    },
    "/api/expenses/{id}/approve": {
      "post": {
        "operationId": "ExpensesController_approve",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReviewExpenseDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExpenseDetailDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Approve expense (manager stage → finance stage, or finance stage → approved)",
        "tags": [
          "Expenses"
        ],
        "x-roles": [
          "admin",
          "partner",
          "manager",
          "finance_admin"
        ]
      }
    },
    "/api/expenses/{id}/reject": {
      "post": {
        "operationId": "ExpensesController_reject",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RejectExpenseDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExpenseDetailDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Reject an expense report (with mandatory reason)",
        "tags": [
          "Expenses"
        ],
        "x-roles": [
          "admin",
          "partner",
          "manager",
          "finance_admin"
        ]
      }
    },
    "/api/expenses/{id}/pay": {
      "post": {
        "operationId": "ExpensesController_markPaid",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExpenseDetailDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Mark an APPROVED expense report as PAID (finance_admin only)",
        "tags": [
          "Expenses"
        ],
        "x-roles": [
          "admin",
          "finance_admin"
        ]
      }
    },
    "/api/expenses/{id}/attachments": {
      "post": {
        "operationId": "ExpensesController_uploadAttachment",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExpenseAttachmentDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Upload a receipt attachment (image or PDF, max 10 MB)",
        "tags": [
          "Expenses"
        ]
      }
    },
    "/api/notifications": {
      "get": {
        "operationId": "NotificationsController_list",
        "parameters": [
          {
            "name": "unreadOnly",
            "required": false,
            "in": "query",
            "description": "Return only unread notifications",
            "schema": {
              "default": false,
              "type": "boolean"
            }
          },
          {
            "name": "take",
            "required": false,
            "in": "query",
            "description": "Max items to return",
            "schema": {
              "minimum": 1,
              "maximum": 100,
              "default": 20,
              "type": "number"
            }
          },
          {
            "name": "skip",
            "required": false,
            "in": "query",
            "description": "Items to skip (pagination offset)",
            "schema": {
              "minimum": 0,
              "default": 0,
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "List notifications for the current user (newest first)",
        "tags": [
          "Notifications"
        ]
      }
    },
    "/api/notifications/unread-count": {
      "get": {
        "operationId": "NotificationsController_unreadCount",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Unread notification count for the current user",
        "tags": [
          "Notifications"
        ]
      }
    },
    "/api/notifications/{id}/read": {
      "patch": {
        "operationId": "NotificationsController_markRead",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Mark a single notification as read",
        "tags": [
          "Notifications"
        ]
      }
    },
    "/api/notifications/read-all": {
      "post": {
        "operationId": "NotificationsController_markAllRead",
        "parameters": [],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Mark all notifications as read for the current user",
        "tags": [
          "Notifications"
        ]
      }
    },
    "/api/audit-logs": {
      "get": {
        "operationId": "AuditController_list",
        "parameters": [
          {
            "name": "userId",
            "required": false,
            "in": "query",
            "description": "Filter by acting user id",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "resource",
            "required": false,
            "in": "query",
            "description": "Filter by resource, e.g. 'budget_plan'",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "resourceId",
            "required": false,
            "in": "query",
            "description": "Filter by a specific resource id",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "action",
            "required": false,
            "in": "query",
            "description": "Filter by action",
            "schema": {
              "enum": [
                "CREATE",
                "READ",
                "UPDATE",
                "DELETE",
                "APPROVE",
                "REJECT",
                "SUBMIT",
                "CANCEL",
                "EXPORT",
                "LOGIN",
                "LOGOUT",
                "SYNC_PUSH",
                "SYNC_PULL",
                "REVISE",
                "CONVERT",
                "LINK"
              ],
              "type": "string"
            }
          },
          {
            "name": "from",
            "required": false,
            "in": "query",
            "description": "Created at >= (ISO 8601)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "required": false,
            "in": "query",
            "description": "Created at <= (ISO 8601)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "take",
            "required": false,
            "in": "query",
            "schema": {
              "minimum": 1,
              "maximum": 200,
              "default": 50,
              "type": "number"
            }
          },
          {
            "name": "skip",
            "required": false,
            "in": "query",
            "schema": {
              "minimum": 0,
              "default": 0,
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Query the audit trail (admin only)",
        "tags": [
          "Audit"
        ],
        "x-roles": [
          "admin"
        ]
      }
    },
    "/api/audit-logs/{id}": {
      "get": {
        "operationId": "AuditController_findOne",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Get a single audit log entry (admin only)",
        "tags": [
          "Audit"
        ],
        "x-roles": [
          "admin"
        ]
      }
    },
    "/api/approvals": {
      "get": {
        "operationId": "ApprovalsController_list",
        "parameters": [
          {
            "name": "role",
            "required": false,
            "in": "query",
            "description": "Whose approvals to list: those you must review, or those you raised",
            "schema": {
              "default": "reviewer",
              "enum": [
                "reviewer",
                "requester"
              ],
              "type": "string"
            }
          },
          {
            "name": "status",
            "required": false,
            "in": "query",
            "description": "Filter by status (default PENDING)",
            "schema": {
              "enum": [
                "PENDING",
                "APPROVED",
                "REJECTED",
                "CANCELLED",
                "EXPIRED"
              ],
              "type": "string"
            }
          },
          {
            "name": "type",
            "required": false,
            "in": "query",
            "description": "Filter by approval type",
            "schema": {
              "enum": [
                "TIMESHEET",
                "BUDGET_REVISION",
                "BUDGET_ALERT",
                "LEAVE_REQUEST",
                "EXPENSE",
                "CUSTOM"
              ],
              "type": "string"
            }
          },
          {
            "name": "take",
            "required": false,
            "in": "query",
            "schema": {
              "minimum": 1,
              "maximum": 100,
              "default": 25,
              "type": "number"
            }
          },
          {
            "name": "skip",
            "required": false,
            "in": "query",
            "schema": {
              "minimum": 0,
              "default": 0,
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Unified approvals inbox for the current user (reviewer or requester)",
        "tags": [
          "Approvals"
        ]
      }
    },
    "/api/approvals/{id}": {
      "get": {
        "operationId": "ApprovalsController_findOne",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Approval request detail (requester or assigned reviewer only)",
        "tags": [
          "Approvals"
        ]
      }
    },
    "/api/approval-processes": {
      "post": {
        "operationId": "ApprovalEngineController_create",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateApprovalProcessDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Create an approval process (DRAFT; pass submit:true to submit immediately)",
        "tags": [
          "approvals"
        ]
      }
    },
    "/api/approval-processes/inbox": {
      "get": {
        "operationId": "ApprovalEngineController_inbox",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Approval processes awaiting the current user (their inbox)",
        "tags": [
          "approvals"
        ],
        "x-permissions": [
          "approval:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/approval-processes/{id}": {
      "get": {
        "operationId": "ApprovalEngineController_get",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Full process detail: steps, timeline, comments, attachments, current approvers (participants and oversight only)",
        "tags": [
          "approvals"
        ],
        "x-permissions": [
          "approval:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/approval-processes/{id}/required-chain": {
      "get": {
        "description": "Reports the blueprint approval ladder for the process (roles, actions and the citation each step rests on) next to the steps actually configured, and names every step that cannot be resolved to a system identity while OQ-17 is unanswered. Read-only: approval authority and routing are unchanged — the permission guards remain the enforcement point.",
        "operationId": "ApprovalEngineController_requiredChain",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "What the blueprint requires for this approval, and whether it is enforceable yet",
        "tags": [
          "approvals"
        ],
        "x-permissions": [
          "approval:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/approval-processes/{id}/submit": {
      "post": {
        "operationId": "ApprovalEngineController_submit",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Submit a DRAFT / revision-requested process for approval (initiator only)",
        "tags": [
          "approvals"
        ]
      }
    },
    "/api/approval-processes/{id}/approve": {
      "post": {
        "operationId": "ApprovalEngineController_approve",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DecisionDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Approve the current step",
        "tags": [
          "approvals"
        ],
        "x-permissions": [
          "approval:decide"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/approval-processes/{id}/reject": {
      "post": {
        "operationId": "ApprovalEngineController_reject",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DecisionDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Reject (fails the whole process)",
        "tags": [
          "approvals"
        ],
        "x-permissions": [
          "approval:decide"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/approval-processes/{id}/request-revision": {
      "post": {
        "operationId": "ApprovalEngineController_requestRevision",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DecisionDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Send back to the initiator for revision",
        "tags": [
          "approvals"
        ],
        "x-permissions": [
          "approval:decide"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/approval-processes/{id}/cancel": {
      "post": {
        "operationId": "ApprovalEngineController_cancel",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DecisionDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Cancel the process (initiator only)",
        "tags": [
          "approvals"
        ]
      }
    },
    "/api/approval-processes/{id}/comments": {
      "post": {
        "operationId": "ApprovalEngineController_comment",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CommentDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Add a comment to the process timeline (participants and oversight only)",
        "tags": [
          "approvals"
        ],
        "x-permissions": [
          "approval:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/approval-processes/{id}/attachments": {
      "post": {
        "operationId": "ApprovalEngineController_addAttachment",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AttachmentDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Attach a document reference to the process (participants and oversight only)",
        "tags": [
          "approvals"
        ],
        "x-permissions": [
          "approval:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/announcements": {
      "get": {
        "operationId": "WorkplaceController_listAnnouncements",
        "parameters": [
          {
            "name": "category",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "take",
            "required": false,
            "in": "query",
            "schema": {
              "minimum": 1,
              "maximum": 200,
              "default": 50,
              "type": "number"
            }
          },
          {
            "name": "skip",
            "required": false,
            "in": "query",
            "schema": {
              "minimum": 0,
              "default": 0,
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "List published announcements (pinned first)",
        "tags": [
          "Workplace"
        ]
      },
      "post": {
        "operationId": "WorkplaceController_createAnnouncement",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateAnnouncementDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Create an announcement (admin / hr_admin)",
        "tags": [
          "Workplace"
        ],
        "x-roles": [
          "admin",
          "hr_admin"
        ]
      }
    },
    "/api/events": {
      "get": {
        "operationId": "WorkplaceController_listEvents",
        "parameters": [
          {
            "name": "category",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "take",
            "required": false,
            "in": "query",
            "schema": {
              "minimum": 1,
              "maximum": 200,
              "default": 50,
              "type": "number"
            }
          },
          {
            "name": "skip",
            "required": false,
            "in": "query",
            "schema": {
              "minimum": 0,
              "default": 0,
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "List events (soonest first)",
        "tags": [
          "Workplace"
        ]
      },
      "post": {
        "operationId": "WorkplaceController_createEvent",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateEventDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Create an event (admin / hr_admin)",
        "tags": [
          "Workplace"
        ],
        "x-roles": [
          "admin",
          "hr_admin"
        ]
      }
    },
    "/api/hr-documents": {
      "get": {
        "operationId": "WorkplaceController_listHrDocuments",
        "parameters": [
          {
            "name": "category",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "take",
            "required": false,
            "in": "query",
            "schema": {
              "minimum": 1,
              "maximum": 200,
              "default": 50,
              "type": "number"
            }
          },
          {
            "name": "skip",
            "required": false,
            "in": "query",
            "schema": {
              "minimum": 0,
              "default": 0,
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "List published HR documents",
        "tags": [
          "Workplace"
        ]
      },
      "post": {
        "operationId": "WorkplaceController_createHrDocument",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateHrDocumentDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Create an HR document (admin / hr_admin)",
        "tags": [
          "Workplace"
        ],
        "x-roles": [
          "admin",
          "hr_admin"
        ]
      }
    },
    "/api/hr-documents/{id}": {
      "get": {
        "description": "Unpublished documents are not retrievable here (A5-14). An authorised draft-preview path for hr_admin is deliberately absent: which roles may see an unpublished document is OQ-17, not a choice engineering may make.",
        "operationId": "WorkplaceController_getHrDocument",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Get one published HR document",
        "tags": [
          "Workplace"
        ]
      }
    },
    "/api/knowledge": {
      "get": {
        "description": "Non-managers always see PUBLISHED only. knowledge:manage holders see the full set and may filter with ?status=.",
        "operationId": "KnowledgeController_findAll",
        "parameters": [
          {
            "name": "page",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "status",
            "required": false,
            "in": "query",
            "schema": {
              "enum": [
                "DRAFT",
                "PUBLISHED",
                "ARCHIVED"
              ],
              "type": "string"
            }
          },
          {
            "name": "category",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "tag",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "search",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "List articles",
        "tags": [
          "Knowledge Base"
        ],
        "x-permissions": [
          "knowledge:read"
        ],
        "x-permissions-mode": "all"
      },
      "post": {
        "operationId": "KnowledgeController_create",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateKnowledgeArticleDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Create an article (DRAFT; slug derived from title)",
        "tags": [
          "Knowledge Base"
        ],
        "x-permissions": [
          "knowledge:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/knowledge/{slug}": {
      "get": {
        "description": "DRAFT is visible only to its author and knowledge:manage holders.",
        "operationId": "KnowledgeController_findBySlug",
        "parameters": [
          {
            "name": "slug",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Read an article by slug (increments view count)",
        "tags": [
          "Knowledge Base"
        ],
        "x-permissions": [
          "knowledge:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/knowledge/{id}": {
      "patch": {
        "operationId": "KnowledgeController_update",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateKnowledgeArticleDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Edit an article (title change regenerates the slug)",
        "tags": [
          "Knowledge Base"
        ],
        "x-permissions": [
          "knowledge:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/knowledge/{id}/publish": {
      "post": {
        "operationId": "KnowledgeController_publish",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Publish an article",
        "tags": [
          "Knowledge Base"
        ],
        "x-permissions": [
          "knowledge:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/knowledge/{id}/archive": {
      "post": {
        "operationId": "KnowledgeController_archive",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Archive an article (leaves the published surface)",
        "tags": [
          "Knowledge Base"
        ],
        "x-permissions": [
          "knowledge:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/gallery/albums": {
      "get": {
        "operationId": "GalleryController_findAlbums",
        "parameters": [
          {
            "name": "page",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "search",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "List albums (published only unless gallery:manage)",
        "tags": [
          "Gallery"
        ],
        "x-permissions": [
          "gallery:read"
        ],
        "x-permissions-mode": "all"
      },
      "post": {
        "operationId": "GalleryController_createAlbum",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateGalleryAlbumDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Create an album (unpublished)",
        "tags": [
          "Gallery"
        ],
        "x-permissions": [
          "gallery:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/gallery/albums/{id}": {
      "get": {
        "operationId": "GalleryController_findAlbum",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Get an album with its images (sorted)",
        "tags": [
          "Gallery"
        ],
        "x-permissions": [
          "gallery:read"
        ],
        "x-permissions-mode": "all"
      },
      "patch": {
        "operationId": "GalleryController_updateAlbum",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateGalleryAlbumDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Edit album details / set the cover image",
        "tags": [
          "Gallery"
        ],
        "x-permissions": [
          "gallery:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/gallery/albums/{id}/publish": {
      "post": {
        "operationId": "GalleryController_publishAlbum",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Publish an album to the firm",
        "tags": [
          "Gallery"
        ],
        "x-permissions": [
          "gallery:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/gallery/albums/{id}/images": {
      "post": {
        "operationId": "GalleryController_addImage",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AddGalleryImageDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Add an image to an album (URL reference — no file upload)",
        "tags": [
          "Gallery"
        ],
        "x-permissions": [
          "gallery:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/gallery/images/{id}": {
      "delete": {
        "operationId": "GalleryController_deleteImage",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Remove an image (clears the album cover if it pointed here)",
        "tags": [
          "Gallery"
        ],
        "x-permissions": [
          "gallery:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/meeting-rooms": {
      "get": {
        "operationId": "MeetingRoomsController_findAll",
        "parameters": [
          {
            "name": "includeInactive",
            "required": false,
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/Object"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "List rooms (active only; ?includeInactive=true for managers)",
        "tags": [
          "Meeting Rooms"
        ],
        "x-permissions": [
          "reservation:read"
        ],
        "x-permissions-mode": "all"
      },
      "post": {
        "operationId": "MeetingRoomsController_create",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateMeetingRoomDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Create a room",
        "tags": [
          "Meeting Rooms"
        ],
        "x-permissions": [
          "reservation:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/meeting-rooms/{id}": {
      "get": {
        "operationId": "MeetingRoomsController_findOne",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Get one room",
        "tags": [
          "Meeting Rooms"
        ],
        "x-permissions": [
          "reservation:read"
        ],
        "x-permissions-mode": "all"
      },
      "patch": {
        "operationId": "MeetingRoomsController_update",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateMeetingRoomDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Edit a room",
        "tags": [
          "Meeting Rooms"
        ],
        "x-permissions": [
          "reservation:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/meeting-rooms/{id}/deactivate": {
      "post": {
        "operationId": "MeetingRoomsController_deactivate",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Deactivate a room (never deleted — history keeps its reference)",
        "tags": [
          "Meeting Rooms"
        ],
        "x-permissions": [
          "reservation:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/reservations": {
      "get": {
        "operationId": "ReservationsController_findAll",
        "parameters": [
          {
            "name": "page",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "roomId",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "required": false,
            "in": "query",
            "schema": {
              "enum": [
                "PENDING",
                "CONFIRMED",
                "CANCELLED",
                "REJECTED"
              ],
              "type": "string"
            }
          },
          {
            "name": "from",
            "required": false,
            "in": "query",
            "description": "Reservations ending after this instant.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "required": false,
            "in": "query",
            "description": "Reservations starting before this instant.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "List reservations (own for staff; all for reservation:manage)",
        "tags": [
          "Room Reservations"
        ],
        "x-permissions": [
          "reservation:read"
        ],
        "x-permissions-mode": "all"
      },
      "post": {
        "description": "Overlapping CONFIRMED/PENDING bookings are refused. A booking longer than 4 hours or outside 07:00–19:00 is created PENDING with an approval routed to hr_admin; otherwise it is CONFIRMED immediately.",
        "operationId": "ReservationsController_create",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateReservationDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Book a room",
        "tags": [
          "Room Reservations"
        ],
        "x-permissions": [
          "reservation:create"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/reservations/{id}": {
      "get": {
        "operationId": "ReservationsController_findOne",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Get one reservation (organizer or reservation:manage)",
        "tags": [
          "Room Reservations"
        ],
        "x-permissions": [
          "reservation:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/reservations/{id}/cancel": {
      "post": {
        "operationId": "ReservationsController_cancel",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Cancel a reservation (organizer or manager) — frees the slot",
        "tags": [
          "Room Reservations"
        ],
        "x-permissions": [
          "reservation:create"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/reservations/{id}/confirm": {
      "post": {
        "description": "Manual step in this milestone: the approval engine exposes no finalization hook, so approval does not auto-confirm the reservation. Refused while the linked approval is not yet APPROVED.",
        "operationId": "ReservationsController_confirm",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Confirm a PENDING reservation after its approval is APPROVED",
        "tags": [
          "Room Reservations"
        ],
        "x-permissions": [
          "reservation:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/management/manager-dashboard": {
      "get": {
        "operationId": "ManagementController_managerDashboard",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Manager dashboard — approvals awaiting me + team queues",
        "tags": [
          "Management"
        ],
        "x-roles": [
          "manager",
          "partner",
          "admin"
        ]
      }
    },
    "/api/management/hr-dashboard": {
      "get": {
        "operationId": "ManagementController_hrDashboard",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "HR dashboard — headcount and workforce distribution",
        "tags": [
          "Management"
        ],
        "x-roles": [
          "hr_admin",
          "admin"
        ]
      }
    },
    "/api/ops/policies": {
      "get": {
        "description": "Seven business decisions are expressed as injectable policies. This reports which implementation is in force for each. A provisional policy stands in for an unanswered decision and is NOT the final business rule.",
        "operationId": "OpsController_policyStatus",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Decision-gated policies and which are still provisional",
        "tags": [
          "Operations"
        ],
        "x-permissions": [
          "integration:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/ops/jobs": {
      "get": {
        "operationId": "OpsController_jobHealth",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Background job health — every registered job with live + persisted stats",
        "tags": [
          "Operations"
        ],
        "x-permissions": [
          "integration:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/ops/jobs/{key}/runs": {
      "get": {
        "operationId": "OpsController_jobRuns",
        "parameters": [
          {
            "name": "key",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "take",
            "required": true,
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Durable run history for one background job",
        "tags": [
          "Operations"
        ],
        "x-permissions": [
          "integration:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/ops/schedules": {
      "get": {
        "operationId": "OpsController_schedules",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Scheduled jobs — cadence, where it came from, next fire time, feature flag and last outcome",
        "tags": [
          "Operations"
        ],
        "x-permissions": [
          "integration:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/ops/jobs/{key}/run": {
      "post": {
        "operationId": "OpsController_runJob",
        "parameters": [
          {
            "name": "key",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Run a scheduled job now. Recorded with trigger MANUAL; works even when the job's schedule flag is off.",
        "tags": [
          "Operations"
        ],
        "x-permissions": [
          "integration:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/ops/integrations": {
      "get": {
        "operationId": "OpsController_integrationHealth",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Integration health — providers, OAuth, Entra, queue, worker, last syncs",
        "tags": [
          "Operations"
        ],
        "x-permissions": [
          "integration:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/ops/sync-runs": {
      "get": {
        "operationId": "OpsController_syncRunHistory",
        "parameters": [
          {
            "name": "kind",
            "required": true,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "provider",
            "required": true,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "required": true,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "required": true,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "required": true,
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Synchronization history — one row per master-data refresh",
        "tags": [
          "Operations"
        ],
        "x-permissions": [
          "integration:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/ops/feature-flags": {
      "get": {
        "operationId": "OpsController_listFlags",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Feature-flag catalog with effective states (registry defaults + DB overrides)",
        "tags": [
          "Operations"
        ],
        "x-permissions": [
          "integration:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/ops/feature-flags/{key}": {
      "patch": {
        "operationId": "OpsController_setFlag",
        "parameters": [
          {
            "name": "key",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetFlagDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Enable/disable a feature flag (admin, audited)",
        "tags": [
          "Operations"
        ],
        "x-permissions": [
          "settings:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/finance/coa/accounts": {
      "get": {
        "operationId": "CoaController_accounts",
        "parameters": [
          {
            "name": "search",
            "required": true,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "includeInactive",
            "required": true,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "required": true,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "required": true,
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Cached COA accounts (hierarchical via parentNetsuiteId/isSummary)",
        "tags": [
          "Finance — Chart of Accounts"
        ],
        "x-permissions": [
          "finance:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/finance/coa/dimensions": {
      "get": {
        "operationId": "CoaController_dimensions",
        "parameters": [
          {
            "name": "type",
            "required": true,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "includeInactive",
            "required": true,
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Cached dimension masters (subsidiary / class / location / department)",
        "tags": [
          "Finance — Chart of Accounts"
        ],
        "x-permissions": [
          "finance:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/finance/coa/status": {
      "get": {
        "operationId": "CoaController_status",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Cache freshness — row counts and last sync per table",
        "tags": [
          "Finance — Chart of Accounts"
        ],
        "x-permissions": [
          "finance:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/finance/coa/refresh": {
      "post": {
        "operationId": "CoaController_refresh",
        "parameters": [],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Refresh accounts AND dimensions from NetSuite (flag-gated, failure-tolerant)",
        "tags": [
          "Finance — Chart of Accounts"
        ],
        "x-permissions": [
          "integration:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/finance/coa/accounts/refresh": {
      "post": {
        "operationId": "CoaController_refreshAccounts",
        "parameters": [],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Refresh the COA account cache from NetSuite",
        "tags": [
          "Finance — Chart of Accounts"
        ],
        "x-permissions": [
          "integration:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/finance/coa/dimensions/refresh": {
      "post": {
        "operationId": "CoaController_refreshDimensions",
        "parameters": [],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Refresh the four dimension masters from NetSuite",
        "tags": [
          "Finance — Chart of Accounts"
        ],
        "x-permissions": [
          "integration:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/hr/headcount-snapshots": {
      "get": {
        "operationId": "HeadcountController_list",
        "parameters": [
          {
            "name": "period",
            "required": true,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "required": true,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "required": true,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "dimension",
            "required": true,
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Snapshot rows (filter by period or from/to range, and dimension)",
        "tags": [
          "HR — Headcount Snapshots"
        ],
        "x-permissions": [
          "hr:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/hr/headcount-snapshots/status": {
      "get": {
        "operationId": "HeadcountController_status",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Job state + captured periods",
        "tags": [
          "HR — Headcount Snapshots"
        ],
        "x-permissions": [
          "hr:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/hr/headcount-snapshots/sources": {
      "get": {
        "operationId": "HeadcountController_sourceMatrix",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "HR data-provider capability matrix (what each source can serve, and why not)",
        "tags": [
          "HR — Headcount Snapshots"
        ],
        "x-permissions": [
          "hr:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/hr/headcount-snapshots/run": {
      "post": {
        "operationId": "HeadcountController_run",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RunSnapshotDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Capture (or re-capture) a period now — defaults to the current month",
        "tags": [
          "HR — Headcount Snapshots"
        ],
        "x-permissions": [
          "hr:manage"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/hr/connection/status": {
      "get": {
        "operationId": "BipoAuthController_status",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "BIPO HR integration connection status (internal/admin)",
        "tags": [
          "HR — Integration"
        ]
      }
    },
    "/api/hr/ess-sso": {
      "post": {
        "description": "Resolves the caller to their BIPO EmployeeCode (403 when unlinked) and returns { url }. The link is short-lived and single-use.",
        "operationId": "BipoEssSsoController_createEssSso",
        "parameters": [],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "One-time BIPO ESS deep link for the signed-in employee",
        "tags": [
          "HR — Integration"
        ]
      }
    },
    "/api/directory": {
      "get": {
        "description": "Returns the directory. BIPO provides no server-side pagination, so page/limit are not offered; department and companyCode narrow the result instead.",
        "operationId": "BipoEmployeeController_list",
        "parameters": [
          {
            "name": "department",
            "required": true,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "companyCode",
            "required": true,
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Employee directory",
        "tags": [
          "HR — Directory"
        ]
      }
    },
    "/api/directory/{code}": {
      "get": {
        "operationId": "BipoEmployeeController_getOne",
        "parameters": [
          {
            "name": "code",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Employee profile by BIPO EmployeeCode",
        "tags": [
          "HR — Directory"
        ]
      }
    },
    "/api/attendance/me": {
      "get": {
        "operationId": "BipoAttendanceController_mine",
        "parameters": [
          {
            "name": "fromDate",
            "required": true,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "toDate",
            "required": true,
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "My clocking records for a date range",
        "tags": [
          "HR — Attendance"
        ]
      }
    },
    "/api/attendance/team": {
      "get": {
        "description": "Scoped to the caller's direct reports, per BIPO's ManagerCode.",
        "operationId": "BipoAttendanceController_team",
        "parameters": [
          {
            "name": "employeeCode",
            "required": true,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "fromDate",
            "required": true,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "toDate",
            "required": true,
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "A direct report's clocking records",
        "tags": [
          "HR — Attendance"
        ]
      }
    },
    "/api/attendance/today": {
      "get": {
        "description": "Organisation-wide and therefore HR-only — BIPO returns every employee’s punches for the date, which must never be exposed to an ordinary employee.",
        "operationId": "BipoAttendanceController_today",
        "parameters": [
          {
            "name": "date",
            "required": true,
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Everyone's clocking records for a date (HR)",
        "tags": [
          "HR — Attendance"
        ],
        "x-permissions": [
          "hr:read"
        ],
        "x-permissions-mode": "all"
      }
    },
    "/api/attendance/clock": {
      "post": {
        "description": "Backed by an exact BIPO write contract (BIPO-CLK upsert). BIPO stores flat punches and derives in/out direction itself — it exposes no clock-in vs clock-out write, so this is one endpoint. clockCode selects the clock device/location.",
        "operationId": "BipoAttendanceController_clock",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RecordClockingDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Record a clocking punch",
        "tags": [
          "HR — Attendance"
        ]
      }
    },
    "/api/leave/balances": {
      "get": {
        "description": "NOT SUPPORTED BY BIPO. The authoritative BIPO API contract defines no leave-balance or entitlement interface, so this returns an integration error rather than invented figures. Retained pending confirmation of a real interface from BIPO.",
        "operationId": "BipoLeaveController_balances",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "My leave balances",
        "tags": [
          "HR — Leave"
        ]
      }
    },
    "/api/leave/requests": {
      "get": {
        "operationId": "BipoLeaveController_requests",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "My leave requests",
        "tags": [
          "HR — Leave"
        ]
      },
      "post": {
        "description": "leaveCode is a tenant-defined BIPO code (e.g. 'SGANL'). Supply halfStart/halfEnd for day-unit leave codes, or timeStart/timeEnd for hour-unit codes.",
        "operationId": "BipoLeaveController_submit",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SubmitLeaveRequestDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Submit a leave request",
        "tags": [
          "HR — Leave"
        ]
      }
    },
    "/api/leave/requests/details": {
      "get": {
        "operationId": "BipoLeaveController_requestDetails",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Per-day breakdown of my leave requests",
        "tags": [
          "HR — Leave"
        ]
      }
    },
    "/api/leave/team": {
      "get": {
        "description": "Scoped to the caller's direct reports, per BIPO's ManagerCode.",
        "operationId": "BipoLeaveController_team",
        "parameters": [
          {
            "name": "employeeCode",
            "required": true,
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "A direct report's leave requests",
        "tags": [
          "HR — Leave"
        ]
      }
    },
    "/api/leave/requests/cancel": {
      "post": {
        "description": "BIPO cancels leave by its natural key (employee + leave code + date range), not by a request id, so the key is supplied in the body rather than the path.",
        "operationId": "BipoLeaveController_cancel",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CancelLeaveRequestDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Cancel a leave request",
        "tags": [
          "HR — Leave"
        ]
      }
    },
    "/api/reimbursements": {
      "get": {
        "operationId": "BipoExpenseController_mine",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "My HR reimbursement claims",
        "tags": [
          "HR — Reimbursements"
        ]
      },
      "post": {
        "description": "NOT SUPPORTED BY BIPO. Claim records are read-only over the BIPO API in both HRMSv1 and HRMSv2 — the contract defines no claim-submission interface. Claims are raised in BIPO ESS. This returns an integration error rather than silently dropping the claim.",
        "operationId": "BipoExpenseController_submit",
        "parameters": [],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Submit an HR reimbursement claim",
        "tags": [
          "HR — Reimbursements"
        ]
      }
    },
    "/api/reimbursements/details": {
      "get": {
        "operationId": "BipoExpenseController_details",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Line items of my HR reimbursement claims",
        "tags": [
          "HR — Reimbursements"
        ]
      }
    },
    "/api/reimbursements/team": {
      "get": {
        "description": "Scoped to the caller's direct reports, per BIPO's ManagerCode.",
        "operationId": "BipoExpenseController_team",
        "parameters": [
          {
            "name": "employeeCode",
            "required": true,
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "A direct report's claims",
        "tags": [
          "HR — Reimbursements"
        ]
      }
    },
    "/api/payroll/summary": {
      "get": {
        "description": "NOT SUPPORTED BY BIPO. The authoritative BIPO API contract defines no such interface — the Payroll module covers only recurring items, allowance master, and transaction append. This returns an integration error rather than invented figures. Retained pending confirmation of a real interface from BIPO.",
        "operationId": "BipoPayrollController_summary",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "My payroll summary",
        "tags": [
          "HR — Payroll"
        ]
      }
    },
    "/api/payroll/payslips": {
      "get": {
        "description": "NOT SUPPORTED BY BIPO. The authoritative BIPO API contract defines no such interface — the Payroll module covers only recurring items, allowance master, and transaction append. This returns an integration error rather than invented figures. Retained pending confirmation of a real interface from BIPO.",
        "operationId": "BipoPayrollController_payslips",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "My payslips",
        "tags": [
          "HR — Payroll"
        ]
      }
    },
    "/api/organization/units": {
      "get": {
        "operationId": "BipoOrganizationController_units",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Flat list of organization units",
        "tags": [
          "HR — Organization"
        ]
      }
    },
    "/api/organization/chart": {
      "get": {
        "operationId": "BipoOrganizationController_chart",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "entra-jwt": []
          }
        ],
        "summary": "Nested organization chart",
        "tags": [
          "HR — Organization"
        ]
      }
    }
  },
  "info": {
    "title": "RSMID Platform API",
    "description": "",
    "version": "1.0.0",
    "contact": {}
  },
  "tags": [],
  "servers": [],
  "components": {
    "securitySchemes": {
      "entra-jwt": {
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "type": "http"
      },
      "api-key": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key"
      }
    },
    "schemas": {
      "CreateApiKeyDto": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "description": "Who or what uses the key, e.g. \"BIPO – UAT\".",
            "maxLength": 80,
            "example": "BIPO – UAT"
          },
          "note": {
            "type": "string",
            "description": "Internal note: contact person, ticket, purpose.",
            "maxLength": 500
          },
          "expiresAt": {
            "type": "string",
            "description": "When the key stops working (ISO-8601). Omit for no expiry.",
            "format": "date-time"
          }
        },
        "required": [
          "name"
        ]
      },
      "RevokeApiKeyDto": {
        "type": "object",
        "properties": {
          "reason": {
            "type": "string",
            "description": "Why the key is revoked (kept in the audit log).",
            "maxLength": 500
          }
        }
      },
      "MeResponseDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "RSMID internal user ID (CUID)"
          },
          "email": {
            "type": "string",
            "example": "user@rsmindonesia.co.id"
          },
          "name": {
            "type": "string",
            "example": "Budi Santoso",
            "description": "Display name from Entra ID"
          },
          "roles": {
            "example": [
              "manager"
            ],
            "description": "App roles from Entra ID — used for RBAC",
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "divisionId": {
            "type": "string",
            "nullable": true,
            "example": "clxxx...",
            "description": "Assigned division ID"
          },
          "entityId": {
            "type": "string",
            "nullable": true,
            "example": "clxxx...",
            "description": "Assigned entity ID"
          },
          "employeeProfileId": {
            "type": "string",
            "nullable": true,
            "example": "clxxx...",
            "description": "BIPO employee profile ID — null if not yet synced"
          }
        },
        "required": [
          "id",
          "email",
          "name",
          "roles",
          "divisionId",
          "entityId",
          "employeeProfileId"
        ]
      },
      "BipoRefreshEmployeeDto": {
        "type": "object",
        "properties": {
          "refreshAsAt": {
            "type": "string",
            "description": "Recompute as of this date (ISO 8601). The client formats it per API generation: DD-MMM-YYYY on v1, YYYY-MM-DD on v2."
          },
          "employeeRange": {
            "type": "string",
            "description": "BIPO's own SQL-fragment employee filter, passed verbatim, e.g. \"((TblEmployee.EmployeeCode IN ('SG0004')))\". Omit to refresh all."
          }
        }
      },
      "TriggerSyncDto": {
        "type": "object",
        "properties": {
          "provider": {
            "type": "string",
            "description": "Provider key, e.g. 'bipo' or 'netsuite'"
          },
          "resource": {
            "type": "string",
            "description": "Resource to sync, e.g. 'employee', 'invoice'"
          },
          "direction": {
            "type": "string",
            "enum": [
              "inbound",
              "outbound"
            ],
            "description": "inbound = external→RSMID, outbound = RSMID→external"
          },
          "since": {
            "type": "string",
            "description": "Delta watermark (ISO 8601) — only pull records changed since this time"
          },
          "limit": {
            "type": "number",
            "minimum": 1,
            "maximum": 500,
            "description": "Page size hint"
          }
        },
        "required": [
          "provider",
          "resource",
          "direction"
        ]
      },
      "SyncMetadataDto": {
        "type": "object",
        "properties": {}
      },
      "UpsertFieldMappingDto": {
        "type": "object",
        "properties": {}
      },
      "PullEmployeesDto": {
        "type": "object",
        "properties": {
          "since": {
            "type": "string",
            "description": "Delta watermark — only mirror BIPO employees changed at/after this ISO time. Filtered client-side against BIPO `UpdateOn`; BIPO has no server-side since filter."
          }
        }
      },
      "LinkEmployeeDto": {
        "type": "object",
        "properties": {
          "userId": {
            "type": "string",
            "description": "RSMID user id (cuid) to link."
          },
          "bipoEmployeeCode": {
            "type": "string",
            "description": "BIPO EmployeeCode, e.g. \"SG0001\". Binds the already-mirrored EmployeeProfile to this user. The profile must already exist — the App never invents an employee."
          },
          "netsuiteEmployeeId": {
            "type": "string",
            "description": "NetSuite employee internalId. Supply this when NetSuite carries no BIPO key, so no automatic Pattern-A match can ever succeed and the link must be made by hand."
          }
        },
        "required": [
          "userId"
        ]
      },
      "UnlinkEmployeeDto": {
        "type": "object",
        "properties": {
          "userId": {
            "type": "string",
            "description": "RSMID user id (cuid) to unlink."
          },
          "side": {
            "type": "string",
            "enum": [
              "bipo",
              "netsuite",
              "both"
            ],
            "description": "Which side of the crosswalk to drop."
          }
        },
        "required": [
          "userId",
          "side"
        ]
      },
      "CreateDailyEntryDto": {
        "type": "object",
        "properties": {
          "date": {
            "type": "string",
            "description": "ISO date of the work day"
          },
          "apergusuiteProjectId": {
            "type": "string"
          },
          "projectName": {
            "type": "string"
          },
          "taskId": {
            "type": "string"
          },
          "taskName": {
            "type": "string"
          },
          "hours": {
            "type": "number",
            "description": "Duration in hours (0.25 – 24). Legacy input — when startTime and endTime are both supplied the duration is computed server-side as (end − start) and this value is ignored."
          },
          "startTime": {
            "type": "string",
            "example": "08:00",
            "description": "Start time (HH:mm)"
          },
          "endTime": {
            "type": "string",
            "example": "16:00",
            "description": "End time (HH:mm)"
          },
          "description": {
            "type": "string"
          },
          "isBillable": {
            "type": "boolean",
            "default": true
          }
        },
        "required": [
          "date"
        ]
      },
      "UpdateDailyEntryDto": {
        "type": "object",
        "properties": {
          "apergusuiteProjectId": {
            "type": "string"
          },
          "projectName": {
            "type": "string"
          },
          "taskId": {
            "type": "string"
          },
          "taskName": {
            "type": "string"
          },
          "hours": {
            "type": "number",
            "description": "Legacy input — ignored when both times are supplied"
          },
          "startTime": {
            "type": "string",
            "example": "08:00",
            "description": "Start time (HH:mm)"
          },
          "endTime": {
            "type": "string",
            "example": "16:00",
            "description": "End time (HH:mm)"
          },
          "description": {
            "type": "string"
          },
          "isBillable": {
            "type": "boolean"
          }
        }
      },
      "CreateTimesheetDto": {
        "type": "object",
        "properties": {
          "weekStart": {
            "type": "string",
            "example": "2026-06-02",
            "description": "Monday of the target week (ISO date)"
          },
          "notes": {
            "type": "string"
          }
        },
        "required": [
          "weekStart"
        ]
      },
      "TimesheetEntryInputDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Existing entry ID to update; omit to create new"
          },
          "date": {
            "type": "string",
            "example": "2026-06-02"
          },
          "apergusuiteProjectId": {
            "type": "string"
          },
          "projectName": {
            "type": "string"
          },
          "taskId": {
            "type": "string"
          },
          "taskName": {
            "type": "string"
          },
          "hours": {
            "type": "number",
            "example": 4,
            "description": "Hours logged (0–24). Legacy input — ignored and recomputed server-side whenever startTime and endTime are both supplied."
          },
          "startTime": {
            "type": "string",
            "example": "08:00",
            "description": "Start time (HH:mm)"
          },
          "endTime": {
            "type": "string",
            "example": "16:00",
            "description": "End time (HH:mm)"
          },
          "description": {
            "type": "string"
          },
          "isBillable": {
            "type": "boolean",
            "default": true
          }
        },
        "required": [
          "date"
        ]
      },
      "UpdateTimesheetDto": {
        "type": "object",
        "properties": {
          "notes": {
            "type": "string"
          },
          "entries": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TimesheetEntryInputDto"
            }
          }
        }
      },
      "ReviewTimesheetDto": {
        "type": "object",
        "properties": {
          "reviewNotes": {
            "type": "string",
            "description": "Optional reviewer notes"
          }
        }
      },
      "LeadContactDto": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1
          },
          "title": {
            "type": "string",
            "description": "Jabatan."
          },
          "email": {
            "type": "string"
          },
          "phone": {
            "type": "string",
            "description": "No. HP."
          },
          "primary": {
            "type": "boolean",
            "description": "Primary contact (exactly one row)."
          }
        },
        "required": [
          "name"
        ]
      },
      "CommunicationLogDto": {
        "type": "object",
        "properties": {
          "date": {
            "type": "string",
            "description": "Tanggal (YYYY-MM-DD)."
          },
          "channel": {
            "type": "string",
            "description": "COMM_CHANNEL reference code (Meeting / Call / Email / WhatsApp)."
          },
          "pic": {
            "type": "string",
            "description": "PIC name."
          },
          "summary": {
            "type": "string",
            "description": "Ringkasan."
          },
          "attachment": {
            "type": "string",
            "description": "Attachment reference."
          }
        },
        "required": [
          "date",
          "channel"
        ]
      },
      "LeadAddressDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Stable row id (ADDR-xxx); assigned server-side when absent."
          },
          "type": {
            "type": "string",
            "description": "ADDRESS_TYPE reference code."
          },
          "address": {
            "type": "string",
            "description": "Alamat."
          },
          "city": {
            "type": "string"
          },
          "province": {
            "type": "string"
          },
          "postalCode": {
            "type": "string"
          },
          "country": {
            "type": "string"
          },
          "latitude": {
            "type": "number",
            "minimum": -90,
            "maximum": 90
          },
          "longitude": {
            "type": "number",
            "minimum": -180,
            "maximum": 180
          },
          "kmFromOffice": {
            "type": "number",
            "minimum": 0,
            "description": "KM dari kantor."
          }
        },
        "required": [
          "type",
          "address"
        ]
      },
      "CreateLeadDto": {
        "type": "object",
        "properties": {
          "transactionDate": {
            "type": "string",
            "description": "Date transaction (YYYY-MM-DD); defaults to the creation day."
          },
          "partnerName": {
            "type": "string",
            "description": "Partner (people-master lookup label)."
          },
          "contacts": {
            "description": "Contact tab rows; the primary row mirrors into contactName/Email/Phone.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/LeadContactDto"
            }
          },
          "budgetEstimation": {
            "type": "string"
          },
          "decisionMaker": {
            "type": "string"
          },
          "painPoint": {
            "type": "string",
            "description": "Kebutuhan / pain point."
          },
          "needTimeline": {
            "type": "string",
            "description": "Timeline kebutuhan."
          },
          "qualificationScore": {
            "type": "string",
            "description": "QUALIFICATION_SCORE reference code (Hot / Warm / Cold)."
          },
          "existingAuditor": {
            "type": "string"
          },
          "communications": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CommunicationLogDto"
            }
          },
          "addresses": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/LeadAddressDto"
            }
          },
          "referralBy": {
            "type": "string"
          },
          "campaign": {
            "type": "string"
          },
          "marketingConsent": {
            "type": "boolean"
          },
          "kamAccountId": {
            "type": "string",
            "nullable": true,
            "description": "KAM Reference (KamAccount id)."
          },
          "companyName": {
            "type": "string",
            "minLength": 2,
            "description": "Company legal name."
          },
          "contactName": {
            "type": "string"
          },
          "contactEmail": {
            "type": "string"
          },
          "contactPhone": {
            "type": "string"
          },
          "industry": {
            "type": "string"
          },
          "sector": {
            "type": "string"
          },
          "source": {
            "type": "string",
            "description": "Lead source: referral | website | event | …"
          },
          "leadType": {
            "type": "string",
            "enum": [
              "NEW",
              "RECURRING"
            ],
            "default": "NEW"
          },
          "serviceLine": {
            "type": "string",
            "enum": [
              "AUDIT_ASSURANCE",
              "TAX",
              "ADVISORY",
              "TRANSACTION_SERVICES"
            ]
          },
          "divisionId": {
            "type": "string"
          },
          "entityId": {
            "type": "string"
          },
          "notes": {
            "type": "string"
          }
        },
        "required": [
          "companyName"
        ]
      },
      "UpdateLeadDto": {
        "type": "object",
        "properties": {
          "transactionDate": {
            "type": "string",
            "description": "Date transaction (YYYY-MM-DD); defaults to the creation day."
          },
          "partnerName": {
            "type": "string",
            "description": "Partner (people-master lookup label)."
          },
          "contacts": {
            "description": "Contact tab rows; the primary row mirrors into contactName/Email/Phone.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/LeadContactDto"
            }
          },
          "budgetEstimation": {
            "type": "string"
          },
          "decisionMaker": {
            "type": "string"
          },
          "painPoint": {
            "type": "string",
            "description": "Kebutuhan / pain point."
          },
          "needTimeline": {
            "type": "string",
            "description": "Timeline kebutuhan."
          },
          "qualificationScore": {
            "type": "string",
            "description": "QUALIFICATION_SCORE reference code (Hot / Warm / Cold)."
          },
          "existingAuditor": {
            "type": "string"
          },
          "communications": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CommunicationLogDto"
            }
          },
          "addresses": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/LeadAddressDto"
            }
          },
          "referralBy": {
            "type": "string"
          },
          "campaign": {
            "type": "string"
          },
          "marketingConsent": {
            "type": "boolean"
          },
          "kamAccountId": {
            "type": "string",
            "nullable": true,
            "description": "KAM Reference (KamAccount id)."
          },
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          },
          "companyName": {
            "type": "string",
            "minLength": 2
          },
          "contactName": {
            "type": "string"
          },
          "contactEmail": {
            "type": "string"
          },
          "contactPhone": {
            "type": "string"
          },
          "industry": {
            "type": "string"
          },
          "sector": {
            "type": "string"
          },
          "source": {
            "type": "string"
          },
          "serviceLine": {
            "type": "string",
            "enum": [
              "AUDIT_ASSURANCE",
              "TAX",
              "ADVISORY",
              "TRANSACTION_SERVICES"
            ]
          },
          "divisionId": {
            "type": "string"
          },
          "entityId": {
            "type": "string"
          },
          "notes": {
            "type": "string"
          },
          "ownerId": {
            "type": "string",
            "description": "PIC owner (RsmidUser id). CRM managers only."
          }
        }
      },
      "NotifyLobDto": {
        "type": "object",
        "properties": {
          "lobId": {
            "type": "string",
            "description": "LineOfBusiness id to notify."
          },
          "message": {
            "type": "string"
          }
        },
        "required": [
          "lobId"
        ]
      },
      "AssignLobDto": {
        "type": "object",
        "properties": {
          "lobId": {
            "type": "string",
            "description": "LineOfBusiness id taking the lead."
          },
          "ownerId": {
            "type": "string",
            "description": "PIC owner (RsmidUser id) inside the LOB."
          }
        },
        "required": [
          "lobId"
        ]
      },
      "DefineLobInfoDto": {
        "type": "object",
        "properties": {
          "serviceNeed": {
            "type": "string",
            "minLength": 2,
            "description": "Service need."
          },
          "initialScope": {
            "type": "string",
            "description": "Initial scope."
          },
          "relatedServiceInfo": {
            "type": "string",
            "description": "Related service information."
          },
          "serviceLine": {
            "type": "string",
            "enum": [
              "AUDIT_ASSURANCE",
              "TAX",
              "ADVISORY",
              "TRANSACTION_SERVICES"
            ]
          }
        },
        "required": [
          "serviceNeed"
        ]
      },
      "QualifyLeadDto": {
        "type": "object",
        "properties": {
          "notes": {
            "type": "string"
          }
        }
      },
      "ConvertLeadDto": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string",
            "description": "Opportunity title; defaults to the lead company name."
          },
          "estimatedValue": {
            "type": "number",
            "minimum": 0
          },
          "currency": {
            "type": "string",
            "default": "IDR"
          },
          "description": {
            "type": "string"
          }
        }
      },
      "DisqualifyLeadDto": {
        "type": "object",
        "properties": {
          "reason": {
            "type": "string",
            "minLength": 3,
            "maxLength": 500,
            "description": "Why the lead is ruled out."
          }
        },
        "required": [
          "reason"
        ]
      },
      "ConvertLeadToCustomerDto": {
        "type": "object",
        "properties": {
          "engagementLetterId": {
            "type": "string",
            "description": "The APPROVED engagement letter of the lead's chain to convert through."
          },
          "existingCustomerId": {
            "type": "string",
            "description": "Link the lead to this existing Customer (CrmCustomerMaster id)."
          },
          "reason": {
            "type": "string",
            "minLength": 3,
            "maxLength": 500,
            "description": "Why the conversion is run by hand (audited)."
          }
        },
        "required": [
          "reason"
        ]
      },
      "OpportunityItemDto": {
        "type": "object",
        "properties": {
          "service": {
            "type": "string",
            "minLength": 1,
            "description": "Service."
          },
          "roleGrade": {
            "type": "string",
            "description": "ROLE_GRADE reference label."
          },
          "fee": {
            "type": "number",
            "minimum": 0,
            "description": "Fee (Rp)."
          }
        },
        "required": [
          "service",
          "fee"
        ]
      },
      "CreateOpportunityDto": {
        "type": "object",
        "properties": {
          "transactionDate": {
            "type": "string",
            "description": "Date transaction (YYYY-MM-DD)."
          },
          "clientType": {
            "type": "string",
            "description": "CLIENT_TYPE reference code (New / Recurring / Existing — new service)."
          },
          "serviceType": {
            "type": "string",
            "enum": [
              "AUDIT",
              "NON_AUDIT",
              "ACCOUNTING_REPORTING_ADVISORY",
              "BUSINESS_CORPORATE_SUPPORT",
              "CORPORATE_FINANCE_TRANSACTION_ADVISORY",
              "GOVERNANCE_RISK_CONTROL_CONSULTING",
              "TAX"
            ]
          },
          "expectedCloseDate": {
            "type": "string",
            "description": "Expected close date (YYYY-MM-DD)."
          },
          "probability": {
            "type": "number",
            "minimum": 0,
            "maximum": 100
          },
          "items": {
            "description": "Items tab; estimatedValue := SUM(fee).",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/OpportunityItemDto"
            }
          },
          "memo": {
            "type": "string",
            "description": "Header memo (short)."
          },
          "classCode": {
            "type": "string",
            "description": "CLASS reference code."
          },
          "locationCode": {
            "type": "string",
            "description": "LOCATION reference code."
          },
          "closingStatus": {
            "type": "string",
            "enum": [
              "WIN",
              "LOSS",
              "DECLINE",
              "CANCEL"
            ],
            "nullable": true
          },
          "lossTo": {
            "type": "string",
            "description": "Competitor — stored only when closingStatus = LOSS."
          },
          "closingReasons": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "Administration",
                "Commercial",
                "Technical",
                "Others"
              ]
            }
          },
          "closingMemo": {
            "type": "string",
            "description": "Reasoning memo — REQUIRED for LOSS / DECLINE / CANCEL (400 CLOSING_MEMO_REQUIRED)."
          },
          "communications": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CommunicationLogDto"
            }
          },
          "recurring": {
            "type": "boolean",
            "description": "Recurring-client flow confirmation (required for direct creation)."
          },
          "lobId": {
            "type": "string",
            "description": "LineOfBusiness id (defaults to your business context)."
          },
          "customerId": {
            "type": "string",
            "description": "Customer (CrmCustomerMaster id). Required while crm.customer-lifecycle is ON."
          },
          "title": {
            "type": "string",
            "minLength": 2
          },
          "clientName": {
            "type": "string",
            "minLength": 2,
            "description": "Required without customerId; ignored with one."
          },
          "clientEmail": {
            "type": "string"
          },
          "clientPhone": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "serviceLine": {
            "type": "string",
            "enum": [
              "AUDIT_ASSURANCE",
              "TAX",
              "ADVISORY",
              "TRANSACTION_SERVICES"
            ]
          },
          "divisionId": {
            "type": "string"
          },
          "entityId": {
            "type": "string"
          },
          "estimatedValue": {
            "type": "number",
            "minimum": 0
          },
          "currency": {
            "type": "string",
            "default": "IDR"
          },
          "notes": {
            "type": "string"
          }
        },
        "required": [
          "title"
        ]
      },
      "UpdateOpportunityDto": {
        "type": "object",
        "properties": {
          "transactionDate": {
            "type": "string",
            "description": "Date transaction (YYYY-MM-DD)."
          },
          "clientType": {
            "type": "string",
            "description": "CLIENT_TYPE reference code (New / Recurring / Existing — new service)."
          },
          "serviceType": {
            "type": "string",
            "enum": [
              "AUDIT",
              "NON_AUDIT",
              "ACCOUNTING_REPORTING_ADVISORY",
              "BUSINESS_CORPORATE_SUPPORT",
              "CORPORATE_FINANCE_TRANSACTION_ADVISORY",
              "GOVERNANCE_RISK_CONTROL_CONSULTING",
              "TAX"
            ]
          },
          "expectedCloseDate": {
            "type": "string",
            "description": "Expected close date (YYYY-MM-DD)."
          },
          "probability": {
            "type": "number",
            "minimum": 0,
            "maximum": 100
          },
          "items": {
            "description": "Items tab; estimatedValue := SUM(fee).",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/OpportunityItemDto"
            }
          },
          "memo": {
            "type": "string",
            "description": "Header memo (short)."
          },
          "classCode": {
            "type": "string",
            "description": "CLASS reference code."
          },
          "locationCode": {
            "type": "string",
            "description": "LOCATION reference code."
          },
          "closingStatus": {
            "type": "string",
            "enum": [
              "WIN",
              "LOSS",
              "DECLINE",
              "CANCEL"
            ],
            "nullable": true
          },
          "lossTo": {
            "type": "string",
            "description": "Competitor — stored only when closingStatus = LOSS."
          },
          "closingReasons": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "Administration",
                "Commercial",
                "Technical",
                "Others"
              ]
            }
          },
          "closingMemo": {
            "type": "string",
            "description": "Reasoning memo — REQUIRED for LOSS / DECLINE / CANCEL (400 CLOSING_MEMO_REQUIRED)."
          },
          "communications": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CommunicationLogDto"
            }
          },
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          },
          "title": {
            "type": "string",
            "minLength": 2
          },
          "clientName": {
            "type": "string",
            "deprecated": true,
            "description": "Read-only — accepted only when unchanged."
          },
          "clientEmail": {
            "type": "string",
            "deprecated": true,
            "description": "Read-only — accepted only when unchanged."
          },
          "clientPhone": {
            "type": "string",
            "deprecated": true,
            "description": "Read-only — accepted only when unchanged."
          },
          "description": {
            "type": "string"
          },
          "serviceLine": {
            "type": "string",
            "enum": [
              "AUDIT_ASSURANCE",
              "TAX",
              "ADVISORY",
              "TRANSACTION_SERVICES"
            ]
          },
          "divisionId": {
            "type": "string"
          },
          "entityId": {
            "type": "string"
          },
          "estimatedValue": {
            "type": "number",
            "minimum": 0
          },
          "currency": {
            "type": "string"
          },
          "notes": {
            "type": "string"
          },
          "reminderNextActionDate": {
            "type": "string",
            "description": "Manual follow-up date set by MBD (spec 5.2, MoM 21 Juli). Field only — no auto-reminder trigger."
          }
        }
      },
      "StartAcceptanceDto": {
        "type": "object",
        "properties": {
          "crossBorder": {
            "type": "boolean",
            "default": false,
            "description": "Adds the cross-border items to the acceptance checklist"
          }
        }
      },
      "LoseOpportunityDto": {
        "type": "object",
        "properties": {
          "lostReason": {
            "type": "string",
            "minLength": 2,
            "description": "Why the opportunity was lost / declined / cancelled."
          },
          "notes": {
            "type": "string"
          },
          "closingStatus": {
            "type": "string",
            "enum": [
              "LOSS",
              "DECLINE",
              "CANCEL"
            ],
            "description": "Loss / Decline / Cancel — defaults to the recorded closing status, else LOSS."
          },
          "lossTo": {
            "type": "string",
            "description": "Competitor — stored only when closingStatus = LOSS."
          },
          "closingReasons": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "Administration",
                "Commercial",
                "Technical",
                "Others"
              ]
            }
          },
          "closingMemo": {
            "type": "string",
            "description": "Reasoning memo — defaults to lostReason."
          }
        },
        "required": [
          "lostReason"
        ]
      },
      "TemplateItemDto": {
        "type": "object",
        "properties": {
          "checkType": {
            "type": "string",
            "minLength": 2
          },
          "label": {
            "type": "string",
            "minLength": 3
          },
          "required": {
            "type": "boolean",
            "default": true
          },
          "crossBorderOnly": {
            "type": "boolean",
            "default": false,
            "description": "Only applies to cross-border engagements."
          }
        },
        "required": [
          "checkType",
          "label"
        ]
      },
      "CreateTemplateVersionDto": {
        "type": "object",
        "properties": {
          "templateType": {
            "type": "string",
            "enum": [
              "ACCEPTANCE",
              "ENGAGEMENT_LETTER"
            ]
          },
          "name": {
            "type": "string",
            "minLength": 3
          },
          "lobId": {
            "type": "string",
            "description": "LineOfBusiness id for an LOB-specific template; omit for global. (Per-LOB matrices are an open item.)"
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TemplateItemDto"
            }
          }
        },
        "required": [
          "templateType",
          "name",
          "items"
        ]
      },
      "RecordCheckDto": {
        "type": "object",
        "properties": {
          "result": {
            "type": "string",
            "enum": [
              "PASS",
              "FAIL",
              "NA"
            ]
          },
          "evidenceAttachment": {
            "type": "string",
            "description": "Evidence reference (document id / URL). MANDATORY for required checks — a checkbox without evidence is not sufficient."
          },
          "comment": {
            "type": "string"
          }
        },
        "required": [
          "result"
        ]
      },
      "AddChecklistItemDto": {
        "type": "object",
        "properties": {
          "checkType": {
            "type": "string",
            "minLength": 2,
            "description": "Stable check type code."
          },
          "label": {
            "type": "string",
            "minLength": 3
          },
          "required": {
            "type": "boolean",
            "default": true
          }
        },
        "required": [
          "checkType",
          "label"
        ]
      },
      "SubmitChecklistDto": {
        "type": "object",
        "properties": {
          "riskRating": {
            "type": "string",
            "enum": [
              "LOW",
              "MEDIUM",
              "HIGH"
            ],
            "description": "Qualification assessment risk rating."
          },
          "notes": {
            "type": "string"
          }
        }
      },
      "RejectChecklistDto": {
        "type": "object",
        "properties": {
          "reason": {
            "type": "string",
            "minLength": 2
          }
        },
        "required": [
          "reason"
        ]
      },
      "ResourceRowDto": {
        "type": "object",
        "properties": {
          "employeeCode": {
            "type": "string",
            "nullable": true,
            "description": "EmployeeProfile.employeeNumber; null for free text."
          },
          "name": {
            "type": "string"
          },
          "level": {
            "type": "string"
          },
          "assignmentId": {
            "type": "string",
            "nullable": true,
            "description": "The assignment line the resource works on."
          }
        },
        "required": [
          "name",
          "level"
        ]
      },
      "ProposalLineDto": {
        "type": "object",
        "properties": {
          "description": {
            "type": "string",
            "minLength": 2
          },
          "quantity": {
            "type": "number",
            "minimum": 0,
            "default": 1
          },
          "rate": {
            "type": "number",
            "minimum": 0
          },
          "amount": {
            "type": "number",
            "minimum": 0
          }
        },
        "required": [
          "description",
          "amount"
        ]
      },
      "DeliverableDto": {
        "type": "object",
        "properties": {
          "description": {
            "type": "string",
            "minLength": 2
          },
          "dueNote": {
            "type": "string"
          }
        },
        "required": [
          "description"
        ]
      },
      "CreateProposalDto": {
        "type": "object",
        "properties": {
          "transactionDate": {
            "type": "string",
            "description": "Date transaction (YYYY-MM-DD)."
          },
          "memo": {
            "type": "string",
            "description": "Header memo (short)."
          },
          "notes": {
            "type": "string",
            "description": "Details tab notes."
          },
          "entityId": {
            "type": "string",
            "description": "Subsidiary — Entity id."
          },
          "classCode": {
            "type": "string",
            "description": "CLASS reference code."
          },
          "locationCode": {
            "type": "string",
            "description": "LOCATION reference code."
          },
          "communications": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CommunicationLogDto"
            }
          },
          "partnerName": {
            "type": "string",
            "description": "Partner (partner master label)."
          },
          "closingStatus": {
            "type": "string",
            "enum": [
              "WIN",
              "LOSS",
              "DECLINE",
              "CANCEL"
            ],
            "nullable": true
          },
          "lossTo": {
            "type": "string",
            "description": "Competitor — stored only when closingStatus = LOSS."
          },
          "closingReasons": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "Administration",
                "Commercial",
                "Technical",
                "Others"
              ]
            }
          },
          "closingMemo": {
            "type": "string",
            "description": "Reasoning memo — REQUIRED for LOSS / DECLINE / CANCEL (400 CLOSING_MEMO_REQUIRED)."
          },
          "resources": {
            "description": "Resources tab rows.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ResourceRowDto"
            }
          },
          "opportunityId": {
            "type": "string",
            "description": "Opportunity this proposal belongs to."
          },
          "title": {
            "type": "string",
            "minLength": 2
          },
          "description": {
            "type": "string"
          },
          "amount": {
            "type": "number",
            "minimum": 0,
            "description": "Professional fee."
          },
          "currency": {
            "type": "string",
            "default": "IDR"
          },
          "proposalType": {
            "type": "string",
            "enum": [
              "TENDER",
              "NON_TENDER"
            ]
          },
          "serviceType": {
            "type": "string",
            "enum": [
              "AUDIT",
              "NON_AUDIT",
              "ACCOUNTING_REPORTING_ADVISORY",
              "BUSINESS_CORPORATE_SUPPORT",
              "CORPORATE_FINANCE_TRANSACTION_ADVISORY",
              "GOVERNANCE_RISK_CONTROL_CONSULTING",
              "TAX"
            ]
          },
          "torReference": {
            "type": "string",
            "description": "TOR reference — mandatory for Tender before submit."
          },
          "opePolicy": {
            "type": "string"
          },
          "paymentTerms": {
            "type": "string"
          },
          "billingType": {
            "type": "string",
            "enum": [
              "FIXED_FEE",
              "TIME_AND_MATERIAL"
            ]
          },
          "lines": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProposalLineDto"
            }
          },
          "deliverables": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DeliverableDto"
            }
          },
          "cvAttachment": {
            "type": "string",
            "description": "CV attachment reference (optional; Tender-relevant)."
          }
        },
        "required": [
          "opportunityId",
          "title",
          "amount"
        ]
      },
      "UpdateProposalDto": {
        "type": "object",
        "properties": {
          "transactionDate": {
            "type": "string",
            "description": "Date transaction (YYYY-MM-DD)."
          },
          "memo": {
            "type": "string",
            "description": "Header memo (short)."
          },
          "notes": {
            "type": "string",
            "description": "Details tab notes."
          },
          "entityId": {
            "type": "string",
            "description": "Subsidiary — Entity id."
          },
          "classCode": {
            "type": "string",
            "description": "CLASS reference code."
          },
          "locationCode": {
            "type": "string",
            "description": "LOCATION reference code."
          },
          "communications": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CommunicationLogDto"
            }
          },
          "partnerName": {
            "type": "string",
            "description": "Partner (partner master label)."
          },
          "closingStatus": {
            "type": "string",
            "enum": [
              "WIN",
              "LOSS",
              "DECLINE",
              "CANCEL"
            ],
            "nullable": true
          },
          "lossTo": {
            "type": "string",
            "description": "Competitor — stored only when closingStatus = LOSS."
          },
          "closingReasons": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "Administration",
                "Commercial",
                "Technical",
                "Others"
              ]
            }
          },
          "closingMemo": {
            "type": "string",
            "description": "Reasoning memo — REQUIRED for LOSS / DECLINE / CANCEL (400 CLOSING_MEMO_REQUIRED)."
          },
          "resources": {
            "description": "Resources tab rows.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ResourceRowDto"
            }
          },
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          },
          "title": {
            "type": "string",
            "minLength": 2
          },
          "description": {
            "type": "string"
          },
          "amount": {
            "type": "number",
            "minimum": 0
          },
          "currency": {
            "type": "string"
          },
          "proposalType": {
            "type": "string",
            "enum": [
              "TENDER",
              "NON_TENDER"
            ]
          },
          "serviceType": {
            "type": "string",
            "enum": [
              "AUDIT",
              "NON_AUDIT",
              "ACCOUNTING_REPORTING_ADVISORY",
              "BUSINESS_CORPORATE_SUPPORT",
              "CORPORATE_FINANCE_TRANSACTION_ADVISORY",
              "GOVERNANCE_RISK_CONTROL_CONSULTING",
              "TAX"
            ]
          },
          "torReference": {
            "type": "string"
          },
          "opePolicy": {
            "type": "string"
          },
          "paymentTerms": {
            "type": "string"
          },
          "billingType": {
            "type": "string",
            "enum": [
              "FIXED_FEE",
              "TIME_AND_MATERIAL"
            ]
          },
          "lines": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProposalLineDto"
            }
          },
          "deliverables": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DeliverableDto"
            }
          },
          "cvAttachment": {
            "type": "string"
          }
        }
      },
      "ProjectStructureLineDto": {
        "type": "object",
        "properties": {
          "roleGrade": {
            "type": "string",
            "minLength": 2,
            "description": "Grade or role only — never a person."
          },
          "headcount": {
            "type": "number",
            "minimum": 1
          },
          "budgetHours": {
            "type": "number",
            "minimum": 0,
            "description": "Required for Fixed Fee billing."
          },
          "rateInternal": {
            "type": "number",
            "minimum": 0,
            "description": "Internal costing — never client-facing."
          },
          "ratePublish": {
            "type": "number",
            "minimum": 0,
            "description": "Client / negotiation rate."
          },
          "nonLaborCost": {
            "type": "number",
            "minimum": 0
          }
        },
        "required": [
          "roleGrade",
          "headcount"
        ]
      },
      "ReplaceProjectStructureDto": {
        "type": "object",
        "properties": {
          "lines": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProjectStructureLineDto"
            }
          }
        },
        "required": [
          "lines"
        ]
      },
      "SwaSplitEntryDto": {
        "type": "object",
        "properties": {
          "divisionId": {
            "type": "string",
            "description": "Division id (optional when label-only)."
          },
          "label": {
            "type": "string",
            "minLength": 1
          },
          "ratio": {
            "type": "number",
            "minimum": 0,
            "description": "Percentage share; all entries must sum to 100."
          }
        },
        "required": [
          "label",
          "ratio"
        ]
      },
      "BudgetHoursRowDto": {
        "type": "object",
        "properties": {
          "section": {
            "type": "string",
            "description": "Section header row."
          },
          "name": {
            "type": "string",
            "description": "Activity name (required without section)."
          },
          "hrs": {
            "description": "Hours per grade — exactly 6 (Partner … Intern).",
            "type": "array",
            "items": {
              "type": "number"
            }
          }
        }
      },
      "BudgetHoursDocDto": {
        "type": "object",
        "properties": {
          "templateCode": {
            "type": "string",
            "nullable": true,
            "description": "PROJECT_TEMPLATE reference code."
          },
          "rows": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BudgetHoursRowDto"
            }
          },
          "rates": {
            "description": "Rate Per Hour (B) per grade, Rp'000 — exactly 6.",
            "type": "array",
            "items": {
              "type": "number"
            }
          },
          "fee": {
            "type": "number",
            "nullable": true,
            "description": "Proposed Fee (F); null = follows the line fee."
          }
        },
        "required": [
          "rows",
          "rates"
        ]
      },
      "StructureTerminDto": {
        "type": "object",
        "properties": {
          "pct": {
            "type": "number",
            "minimum": 0,
            "maximum": 100
          },
          "date": {
            "type": "string",
            "nullable": true,
            "description": "YYYY-MM-DD."
          }
        },
        "required": [
          "pct"
        ]
      },
      "StructureMilestoneDto": {
        "type": "object",
        "properties": {
          "task": {
            "type": "string",
            "description": "A Budget Hours row name."
          },
          "pct": {
            "type": "number",
            "minimum": 0,
            "maximum": 100
          }
        },
        "required": [
          "task",
          "pct"
        ]
      },
      "StructureTcbDto": {
        "type": "object",
        "properties": {
          "freq": {
            "type": "string",
            "enum": [
              "WEEKLY",
              "MONTHLY",
              "ANNUALLY"
            ]
          },
          "every": {
            "type": "number",
            "minimum": 1
          }
        },
        "required": [
          "freq",
          "every"
        ]
      },
      "SharedWorkLineDto": {
        "type": "object",
        "properties": {
          "swid": {
            "type": "string",
            "description": "Shared Work ID — assigned by the EL create, accepted back unchanged."
          },
          "lobId": {
            "type": "string",
            "nullable": true
          },
          "divisionId": {
            "type": "string",
            "nullable": true
          },
          "scope": {
            "type": "string"
          },
          "fee": {
            "type": "number",
            "minimum": 0
          },
          "hours": {
            "type": "number",
            "minimum": 0
          }
        },
        "required": [
          "scope",
          "fee",
          "hours"
        ]
      },
      "SubconLineDto": {
        "type": "object",
        "properties": {
          "vendor": {
            "type": "string"
          },
          "amount": {
            "type": "number",
            "minimum": 0
          }
        },
        "required": [
          "vendor",
          "amount"
        ]
      },
      "AssignmentStructureDocDto": {
        "type": "object",
        "properties": {
          "subcon": {
            "type": "number",
            "minimum": 0
          },
          "referral": {
            "type": "number",
            "minimum": 0
          },
          "ojk": {
            "type": "number",
            "minimum": 0
          },
          "ope": {
            "type": "number",
            "minimum": 0
          },
          "inboundPct": {
            "type": "number",
            "minimum": 0,
            "maximum": 100
          },
          "topType": {
            "type": "string",
            "enum": [
              "FIXED_TERM",
              "MILESTONE",
              "TIME_CHARGE"
            ]
          },
          "termins": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/StructureTerminDto"
            }
          },
          "milestones": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/StructureMilestoneDto"
            }
          },
          "tcb": {
            "$ref": "#/components/schemas/StructureTcbDto"
          },
          "sharedWorks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SharedWorkLineDto"
            }
          },
          "subconLines": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SubconLineDto"
            }
          },
          "preparedBy": {
            "type": "string"
          }
        },
        "required": [
          "subcon",
          "referral",
          "ojk",
          "ope",
          "inboundPct",
          "topType",
          "termins",
          "milestones",
          "tcb",
          "sharedWorks",
          "subconLines",
          "preparedBy"
        ]
      },
      "UpsertProposalAssignmentDto": {
        "type": "object",
        "properties": {
          "assignmentName": {
            "type": "string",
            "minLength": 2
          },
          "assignmentType": {
            "type": "string",
            "description": "ASSIGNMENT_TYPE reference code (AUDIT / TAX / CONSULTING)."
          },
          "lobId": {
            "type": "string",
            "description": "LineOfBusiness id."
          },
          "divisionId": {
            "type": "string",
            "description": "Division id."
          },
          "proposedFee": {
            "type": "number",
            "minimum": 0,
            "description": "Proposed Fee of the line (Σ = proposal amount)."
          },
          "opeFlag": {
            "type": "boolean",
            "default": false
          },
          "opeAmount": {
            "type": "number",
            "minimum": 0,
            "description": "Mandatory when opeFlag; ≤ proposedFee."
          },
          "swaActive": {
            "type": "boolean",
            "default": false,
            "description": "SWA active for this line."
          },
          "swaType": {
            "type": "string",
            "description": "SWA_TYPE reference code."
          },
          "swaSplitRatio": {
            "description": "Mandatory when SWA is active.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SwaSplitEntryDto"
            }
          },
          "budgetHours": {
            "$ref": "#/components/schemas/BudgetHoursDocDto"
          },
          "projectStructure": {
            "$ref": "#/components/schemas/AssignmentStructureDocDto"
          },
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          }
        },
        "required": [
          "assignmentName"
        ]
      },
      "RejectProposalDto": {
        "type": "object",
        "properties": {
          "reason": {
            "type": "string",
            "minLength": 2,
            "description": "Why the proposal was rejected / needs revision."
          }
        },
        "required": [
          "reason"
        ]
      },
      "CreateEngagementLetterDto": {
        "type": "object",
        "properties": {
          "partnerName": {
            "type": "string",
            "description": "Partner (partner master label); defaults from the proposal at create."
          },
          "resources": {
            "description": "Resources tab rows.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ResourceRowDto"
            }
          },
          "transactionDate": {
            "type": "string",
            "description": "Date transaction (YYYY-MM-DD)."
          },
          "billingType": {
            "type": "string",
            "enum": [
              "FIXED_FEE",
              "CHARGE_BASED"
            ],
            "description": "Fixed Fee vs Charge-Based (spec 5.5)."
          },
          "paymentTermCode": {
            "type": "string",
            "description": "PAYMENT_TERM reference code (Net 30 / 45 / 60)."
          },
          "paymentDueRule": {
            "type": "string",
            "description": "Payment due rule (\"30 hari setelah invoice\")."
          },
          "auditFlag": {
            "type": "boolean"
          },
          "qaReviewStatus": {
            "type": "string",
            "description": "QA_REVIEW_STATUS reference code."
          },
          "attachmentReference": {
            "type": "string",
            "description": "Signed EL / contract / SPK attachment reference."
          },
          "signedDate": {
            "type": "string",
            "description": "Actual signature date (YYYY-MM-DD). Governance: no back-date delta is ever displayed (spec 5.5)."
          },
          "opePolicy": {
            "type": "string",
            "description": "OPE policy (detail allocation lives on assignments)."
          },
          "entityId": {
            "type": "string",
            "description": "Subsidiary — Entity id."
          },
          "lobId": {
            "type": "string",
            "description": "LineOfBusiness id."
          },
          "divisionId": {
            "type": "string",
            "description": "Division id."
          },
          "classCode": {
            "type": "string",
            "description": "CLASS reference code."
          },
          "locationCode": {
            "type": "string",
            "description": "LOCATION reference code."
          },
          "communications": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CommunicationLogDto"
            }
          },
          "proposalId": {
            "type": "string",
            "description": "ACCEPTED proposal this EL formalizes."
          },
          "cf": {
            "type": "number",
            "minimum": 0,
            "description": "Confirmed fee (CF) — the only header fee. Immutable at APPROVED. Defaults to the proposal amount."
          },
          "assignmentStructure": {
            "type": "string",
            "enum": [
              "SINGLE",
              "MULTIPLE"
            ],
            "default": "SINGLE"
          },
          "clientName": {
            "type": "string",
            "description": "Legal-name override for the company snapshot. Accepted only while the chain has no Customer (C7); once it has one the name comes from the Customer and a different value is refused with 400 CLIENT_NAME_FROM_CUSTOMER. Defaults to the proposal's company snapshot."
          },
          "currency": {
            "type": "string",
            "default": "IDR"
          },
          "notes": {
            "type": "string"
          },
          "negotiationNotes": {
            "type": "string"
          },
          "crossBorder": {
            "type": "boolean",
            "description": "Engagement is cross-border (activates Global Independence checks)."
          }
        },
        "required": [
          "proposalId"
        ]
      },
      "BillingTermDto": {
        "type": "object",
        "properties": {
          "termin": {
            "type": "number",
            "minimum": 1
          },
          "description": {
            "type": "string",
            "minLength": 2
          },
          "invoicePercentage": {
            "type": "number",
            "minimum": 0,
            "description": "Invoice percentage of CF."
          },
          "dueDate": {
            "type": "string",
            "description": "Due date (YYYY-MM-DD) or due rule."
          }
        },
        "required": [
          "termin",
          "description",
          "invoicePercentage"
        ]
      },
      "UpdateEngagementLetterDto": {
        "type": "object",
        "properties": {
          "partnerName": {
            "type": "string",
            "description": "Partner (partner master label); defaults from the proposal at create."
          },
          "resources": {
            "description": "Resources tab rows.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ResourceRowDto"
            }
          },
          "transactionDate": {
            "type": "string",
            "description": "Date transaction (YYYY-MM-DD)."
          },
          "billingType": {
            "type": "string",
            "enum": [
              "FIXED_FEE",
              "CHARGE_BASED"
            ],
            "description": "Fixed Fee vs Charge-Based (spec 5.5)."
          },
          "paymentTermCode": {
            "type": "string",
            "description": "PAYMENT_TERM reference code (Net 30 / 45 / 60)."
          },
          "paymentDueRule": {
            "type": "string",
            "description": "Payment due rule (\"30 hari setelah invoice\")."
          },
          "auditFlag": {
            "type": "boolean"
          },
          "qaReviewStatus": {
            "type": "string",
            "description": "QA_REVIEW_STATUS reference code."
          },
          "attachmentReference": {
            "type": "string",
            "description": "Signed EL / contract / SPK attachment reference."
          },
          "signedDate": {
            "type": "string",
            "description": "Actual signature date (YYYY-MM-DD). Governance: no back-date delta is ever displayed (spec 5.5)."
          },
          "opePolicy": {
            "type": "string",
            "description": "OPE policy (detail allocation lives on assignments)."
          },
          "entityId": {
            "type": "string",
            "description": "Subsidiary — Entity id."
          },
          "lobId": {
            "type": "string",
            "description": "LineOfBusiness id."
          },
          "divisionId": {
            "type": "string",
            "description": "Division id."
          },
          "classCode": {
            "type": "string",
            "description": "CLASS reference code."
          },
          "locationCode": {
            "type": "string",
            "description": "LOCATION reference code."
          },
          "communications": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CommunicationLogDto"
            }
          },
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          },
          "cf": {
            "type": "number",
            "minimum": 0
          },
          "assignmentStructure": {
            "type": "string",
            "enum": [
              "SINGLE",
              "MULTIPLE"
            ]
          },
          "clientName": {
            "type": "string",
            "deprecated": true,
            "description": "Frozen company snapshot — never written. A value different from the stored client name is refused with 400 SNAPSHOT_IMMUTABLE; rename the Customer instead."
          },
          "currency": {
            "type": "string"
          },
          "notes": {
            "type": "string"
          },
          "negotiationNotes": {
            "type": "string"
          },
          "billingTerms": {
            "description": "Billing schedule / termin.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BillingTermDto"
            }
          },
          "statementOfWork": {
            "type": "string",
            "description": "Statement of Work."
          }
        }
      },
      "QaReviewEngagementLetterDto": {
        "type": "object",
        "properties": {
          "comments": {
            "type": "string",
            "maxLength": 1000
          }
        }
      },
      "DeclineEngagementLetterDto": {
        "type": "object",
        "properties": {
          "reason": {
            "type": "string",
            "minLength": 3,
            "maxLength": 500,
            "description": "Why the client declined — audited."
          },
          "closingReasons": {
            "type": "array",
            "description": "Closing reasons (Administration / Commercial / Technical / Others).",
            "items": {
              "type": "string",
              "enum": [
                "Administration",
                "Commercial",
                "Technical",
                "Others"
              ]
            }
          },
          "closingMemo": {
            "type": "string",
            "description": "Reasoning memo; when absent, `reason` is stored as the memo."
          }
        },
        "required": [
          "reason"
        ]
      },
      "ApproveEngagementLetterDto": {
        "type": "object",
        "properties": {
          "signedDate": {
            "type": "string",
            "description": "Signed date (actual), YYYY-MM-DD."
          },
          "attachmentReference": {
            "type": "string",
            "description": "Final EL / Contract / SPK (signed) — document reference.",
            "maxLength": 300
          }
        }
      },
      "CancelEngagementLetterDto": {
        "type": "object",
        "properties": {
          "reason": {
            "type": "string",
            "minLength": 3,
            "maxLength": 500,
            "description": "Why the engagement letter is cancelled — audited."
          },
          "closingReasons": {
            "type": "array",
            "description": "Closing reasons (RSM UI reference 2026-10-11).",
            "items": {
              "type": "string",
              "enum": [
                "Administration",
                "Commercial",
                "Technical",
                "Others"
              ]
            }
          },
          "closingMemo": {
            "type": "string",
            "description": "Reasoning memo; when absent, `reason` is stored as the memo."
          }
        },
        "required": [
          "reason"
        ]
      },
      "AmendmentAssignmentDto": {
        "type": "object",
        "properties": {
          "existingAssignmentId": {
            "type": "string",
            "description": "Existing assignment id when this row updates one; omit for a NEW assignment."
          },
          "assignmentName": {
            "type": "string",
            "minLength": 2
          },
          "lobId": {
            "type": "string"
          },
          "divisionId": {
            "type": "string"
          },
          "hours": {
            "type": "number",
            "minimum": 0
          },
          "prorateAmount": {
            "type": "number",
            "minimum": 0
          },
          "proratePercentage": {
            "type": "number",
            "minimum": 0
          },
          "opeFlag": {
            "type": "boolean",
            "default": false
          },
          "opeAmount": {
            "type": "number",
            "minimum": 0
          }
        },
        "required": [
          "assignmentName"
        ]
      },
      "CreateAmendmentDto": {
        "type": "object",
        "properties": {
          "reason": {
            "type": "string",
            "minLength": 5,
            "description": "Business reason for the amendment."
          },
          "assignments": {
            "description": "The FULL post-amendment assignment set. Every existing assignment must appear (prospective only — assignments with actuals are never removed).",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AmendmentAssignmentDto"
            }
          }
        },
        "required": [
          "reason",
          "assignments"
        ]
      },
      "DecideAmendmentDto": {
        "type": "object",
        "properties": {
          "comment": {
            "type": "string"
          }
        }
      },
      "DedupeDecisionDto": {
        "type": "object",
        "properties": {
          "decision": {
            "type": "string",
            "enum": [
              "OVERRIDE_CREATE",
              "LINK_EXISTING"
            ],
            "description": "OVERRIDE_CREATE (central-Admin governed): approve creating a new customer despite candidates. LINK_EXISTING: adopt an existing NetSuite customer (with the customer lifecycle enabled, an existing local-only Customer is accepted as well)."
          },
          "externalId": {
            "type": "string",
            "description": "NetSuite customer internalId when linking."
          },
          "masterId": {
            "type": "string",
            "description": "Local register (CrmCustomerMaster) id to merge with. It must carry an ERP identity unless the customer lifecycle is enabled, which also accepts a local-only Customer."
          },
          "reason": {
            "type": "string",
            "minLength": 2,
            "description": "Business reason — audited."
          }
        },
        "required": [
          "decision",
          "reason"
        ]
      },
      "UpsertAssignmentDto": {
        "type": "object",
        "properties": {
          "assignmentName": {
            "type": "string",
            "minLength": 2
          },
          "assignmentType": {
            "type": "string",
            "description": "ASSIGNMENT_TYPE reference code (AUDIT / TAX / CONSULTING)."
          },
          "budgetHours": {
            "description": "When present and `hours` is absent, hours = grand total (E).",
            "allOf": [
              {
                "$ref": "#/components/schemas/BudgetHoursDocDto"
              }
            ]
          },
          "projectStructure": {
            "$ref": "#/components/schemas/AssignmentStructureDocDto"
          },
          "lobId": {
            "type": "string",
            "description": "LineOfBusiness id."
          },
          "divisionId": {
            "type": "string",
            "description": "Division id."
          },
          "hours": {
            "type": "number",
            "minimum": 0
          },
          "prorateAmount": {
            "type": "number",
            "minimum": 0,
            "description": "Share of EL.cf (one-of with percentage)."
          },
          "proratePercentage": {
            "type": "number",
            "minimum": 0,
            "description": "Percentage share (one-of with amount)."
          },
          "swaActive": {
            "type": "boolean",
            "default": false,
            "description": "SWA active for this assignment."
          },
          "swaType": {
            "type": "string",
            "description": "SWA type (collaboration model between divisions)."
          },
          "swaSplitRatio": {
            "description": "Mandatory when SWA is active.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SwaSplitEntryDto"
            }
          },
          "opeFlag": {
            "type": "boolean",
            "default": false
          },
          "opeAmount": {
            "type": "number",
            "minimum": 0,
            "description": "Mandatory when opeFlag; ≤ prorateAmount."
          }
        },
        "required": [
          "assignmentName"
        ]
      },
      "UpdateAssignmentDto": {
        "type": "object",
        "properties": {
          "assignmentName": {
            "type": "string",
            "minLength": 2
          },
          "assignmentType": {
            "type": "string",
            "description": "ASSIGNMENT_TYPE reference code (AUDIT / TAX / CONSULTING)."
          },
          "budgetHours": {
            "description": "When present and `hours` is absent, hours = grand total (E).",
            "allOf": [
              {
                "$ref": "#/components/schemas/BudgetHoursDocDto"
              }
            ]
          },
          "projectStructure": {
            "$ref": "#/components/schemas/AssignmentStructureDocDto"
          },
          "lobId": {
            "type": "string",
            "description": "LineOfBusiness id."
          },
          "divisionId": {
            "type": "string",
            "description": "Division id."
          },
          "hours": {
            "type": "number",
            "minimum": 0
          },
          "prorateAmount": {
            "type": "number",
            "minimum": 0,
            "description": "Share of EL.cf (one-of with percentage)."
          },
          "proratePercentage": {
            "type": "number",
            "minimum": 0,
            "description": "Percentage share (one-of with amount)."
          },
          "swaActive": {
            "type": "boolean",
            "default": false,
            "description": "SWA active for this assignment."
          },
          "swaType": {
            "type": "string",
            "description": "SWA type (collaboration model between divisions)."
          },
          "swaSplitRatio": {
            "description": "Mandatory when SWA is active.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SwaSplitEntryDto"
            }
          },
          "opeFlag": {
            "type": "boolean",
            "default": false
          },
          "opeAmount": {
            "type": "number",
            "minimum": 0,
            "description": "Mandatory when opeFlag; ≤ prorateAmount."
          },
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          }
        },
        "required": [
          "assignmentName"
        ]
      },
      "AdjustAssignmentCfDto": {
        "type": "object",
        "properties": {
          "cfPenyesuaian": {
            "type": "number",
            "minimum": 0
          },
          "reason": {
            "type": "string",
            "minLength": 2,
            "description": "Business reason — audited."
          }
        },
        "required": [
          "cfPenyesuaian",
          "reason"
        ]
      },
      "KamStakeholderInputDto": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          },
          "position": {
            "type": "string",
            "nullable": true
          },
          "relationshipNotes": {
            "type": "string",
            "nullable": true
          }
        },
        "required": [
          "name"
        ]
      },
      "KamActivityInputDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Existing activity id (absent = new row)."
          },
          "memberName": {
            "type": "string",
            "nullable": true,
            "description": "KAM Member (free text)."
          },
          "date": {
            "type": "string",
            "description": "Activity date (YYYY-MM-DD)."
          },
          "type": {
            "type": "string",
            "enum": [
              "MEETING",
              "CALL",
              "VISIT",
              "EMAIL"
            ],
            "nullable": true
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "attendees": {
            "type": "string",
            "nullable": true,
            "description": "Attendee names, comma-separated."
          },
          "followUp": {
            "type": "string",
            "nullable": true
          },
          "effectiveness": {
            "type": "string",
            "enum": [
              "LOW",
              "MEDIUM",
              "HIGH"
            ],
            "nullable": true
          },
          "notes": {
            "type": "string",
            "nullable": true
          }
        },
        "required": [
          "date"
        ]
      },
      "CreateKamAccountDto": {
        "type": "object",
        "properties": {
          "dateCreated": {
            "type": "string",
            "description": "Date Created (YYYY-MM-DD); default today (Asia/Jakarta)."
          },
          "accountManagerName": {
            "type": "string",
            "nullable": true,
            "description": "LCSP / Account Manager (free text)."
          },
          "lcspId": {
            "type": "string",
            "nullable": true,
            "description": "LCSP user (RsmidUser id)."
          },
          "clientCode": {
            "type": "string",
            "nullable": true,
            "description": "Client ID (manual)."
          },
          "clientName": {
            "type": "string",
            "nullable": true,
            "description": "Client Name (manual, new or existing client)."
          },
          "customerId": {
            "type": "string",
            "nullable": true,
            "description": "Client register link (CrmCustomerMaster id); null unlinks."
          },
          "businessDefinition": {
            "type": "string",
            "nullable": true
          },
          "engagementHistorySummary": {
            "type": "string",
            "nullable": true
          },
          "planDescription": {
            "type": "string",
            "nullable": true
          },
          "potentialServiceOpportunity": {
            "type": "string",
            "nullable": true
          },
          "stakeholders": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/KamStakeholderInputDto"
            }
          },
          "activities": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/KamActivityInputDto"
            }
          },
          "communications": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CommunicationLogDto"
            }
          },
          "accountName": {
            "type": "string",
            "minLength": 2,
            "maxLength": 200,
            "description": "Key Account Name."
          },
          "targetPeriod": {
            "type": "string",
            "example": "2026-2027",
            "description": "Target Period."
          }
        },
        "required": [
          "accountName",
          "targetPeriod"
        ]
      },
      "UpdateKamAccountDto": {
        "type": "object",
        "properties": {
          "dateCreated": {
            "type": "string",
            "description": "Date Created (YYYY-MM-DD); default today (Asia/Jakarta)."
          },
          "accountManagerName": {
            "type": "string",
            "nullable": true,
            "description": "LCSP / Account Manager (free text)."
          },
          "lcspId": {
            "type": "string",
            "nullable": true,
            "description": "LCSP user (RsmidUser id)."
          },
          "clientCode": {
            "type": "string",
            "nullable": true,
            "description": "Client ID (manual)."
          },
          "clientName": {
            "type": "string",
            "nullable": true,
            "description": "Client Name (manual, new or existing client)."
          },
          "customerId": {
            "type": "string",
            "nullable": true,
            "description": "Client register link (CrmCustomerMaster id); null unlinks."
          },
          "businessDefinition": {
            "type": "string",
            "nullable": true
          },
          "engagementHistorySummary": {
            "type": "string",
            "nullable": true
          },
          "planDescription": {
            "type": "string",
            "nullable": true
          },
          "potentialServiceOpportunity": {
            "type": "string",
            "nullable": true
          },
          "stakeholders": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/KamStakeholderInputDto"
            }
          },
          "activities": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/KamActivityInputDto"
            }
          },
          "communications": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CommunicationLogDto"
            }
          },
          "accountName": {
            "type": "string",
            "minLength": 2,
            "maxLength": 200
          },
          "targetPeriod": {
            "type": "string",
            "example": "2026-2027"
          },
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          }
        }
      },
      "KamTransitionDto": {
        "type": "object",
        "properties": {
          "comments": {
            "type": "string",
            "maxLength": 1000
          },
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          }
        }
      },
      "RejectKamDto": {
        "type": "object",
        "properties": {
          "reason": {
            "type": "string",
            "minLength": 2,
            "maxLength": 1000
          },
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          }
        },
        "required": [
          "reason"
        ]
      },
      "SetBusinessContextDto": {
        "type": "object",
        "properties": {
          "businessRoles": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "MBD",
                "PARTNER",
                "LOB",
                "DIVISION",
                "ADMIN",
                "APPROVAL_AUTHORITY",
                "PM"
              ]
            }
          },
          "lobId": {
            "type": "string",
            "description": "LineOfBusiness id the user acts for."
          },
          "divisionId": {
            "type": "string",
            "description": "Division id the user acts for."
          }
        },
        "required": [
          "businessRoles"
        ]
      },
      "UpdateCustomerProfileDto": {
        "type": "object",
        "properties": {
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          },
          "parentId": {
            "type": "string",
            "description": "Parent group (another CrmCustomerMaster id); null detaches."
          },
          "revenueTier": {
            "type": "string",
            "enum": [
              "T1",
              "T2",
              "T3",
              "T4",
              "T5"
            ],
            "description": "Manual until OQ-20 (derivation basis) is ratified."
          },
          "clientSegment": {
            "type": "string",
            "enum": [
              "SOE",
              "CORPORATE_GROUP",
              "OTHER"
            ]
          },
          "sectorType": {
            "type": "string",
            "enum": [
              "GOVERNMENT",
              "PRIVATE"
            ]
          },
          "industryCode": {
            "type": "string",
            "description": "INDUSTRY reference code flagged as a Framework 2027 code (FIN, ENR, …)."
          },
          "strategicImportance": {
            "type": "string",
            "enum": [
              "KEY_ACCOUNT",
              "NON_KEY"
            ]
          },
          "classificationEffectiveDate": {
            "type": "string",
            "description": "YYYY-MM-DD"
          },
          "classificationReviewedAt": {
            "type": "string",
            "description": "Last review date, YYYY-MM-DD"
          },
          "classificationNotes": {
            "type": "string",
            "maxLength": 1000
          },
          "financePicName": {
            "type": "string",
            "maxLength": 120
          },
          "financePicEmail": {
            "type": "string"
          },
          "npwpNumber": {
            "type": "string",
            "description": "NPWP — digits only, 15 or 16 digits."
          },
          "ojkCategory": {
            "type": "string",
            "enum": [
              "IKNB",
              "PASAR_MODAL",
              "PERBANKAN"
            ]
          },
          "rsmReferral": {
            "type": "string",
            "maxLength": 200
          }
        }
      },
      "UpdateClientIdentityDto": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 2,
            "maxLength": 200
          },
          "picName": {
            "type": "string",
            "maxLength": 120,
            "description": "PIC (person in charge) at the client."
          },
          "email": {
            "type": "string",
            "maxLength": 200
          },
          "phone": {
            "type": "string",
            "maxLength": 60
          },
          "address": {
            "type": "string",
            "maxLength": 500,
            "description": "One formatted line."
          },
          "website": {
            "type": "string",
            "maxLength": 300
          },
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          }
        },
        "required": [
          "expectedVersion"
        ]
      },
      "SetClientStatusDto": {
        "type": "object",
        "properties": {
          "isActive": {
            "type": "boolean"
          },
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          }
        },
        "required": [
          "isActive",
          "expectedVersion"
        ]
      },
      "CreateSectorDto": {
        "type": "object",
        "properties": {
          "code": {
            "type": "string",
            "example": "FIN",
            "description": "Sector code; uppercased + trimmed; immutable after create."
          },
          "label": {
            "type": "string",
            "minLength": 2,
            "maxLength": 120,
            "description": "Sector (the label stored on the Lead)."
          },
          "industryType": {
            "type": "string",
            "enum": [
              "Manufacturing",
              "Trading",
              "Services",
              "Property & Construction",
              "Financial Services",
              "Others"
            ],
            "nullable": true
          },
          "sortOrder": {
            "type": "number",
            "minimum": 0,
            "maximum": 9999,
            "description": "Default = max sortOrder of SECTOR rows + 1."
          }
        },
        "required": [
          "code",
          "label"
        ]
      },
      "UpdateSectorDto": {
        "type": "object",
        "properties": {
          "label": {
            "type": "string",
            "minLength": 2,
            "maxLength": 120
          },
          "industryType": {
            "type": "string",
            "enum": [
              "Manufacturing",
              "Trading",
              "Services",
              "Property & Construction",
              "Financial Services",
              "Others"
            ],
            "nullable": true,
            "description": "null removes it."
          },
          "sortOrder": {
            "type": "number",
            "minimum": 0,
            "maximum": 9999
          },
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          }
        }
      },
      "SectorTransitionDto": {
        "type": "object",
        "properties": {
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          }
        }
      },
      "CreateCrmProjectDto": {
        "type": "object",
        "properties": {
          "assignmentId": {
            "type": "string",
            "description": "EL assignment (CrmAssignment id) the project is created from."
          }
        },
        "required": [
          "assignmentId"
        ]
      },
      "ProjectAllocationInputDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "employeeCode": {
            "type": "string",
            "description": "EmployeeProfile.employeeNumber."
          },
          "startDate": {
            "type": "string"
          },
          "endDate": {
            "type": "string"
          },
          "allocate": {
            "type": "number",
            "minimum": 0
          },
          "unit": {
            "type": "string",
            "enum": [
              "HOURS",
              "PERCENT"
            ]
          },
          "taskId": {
            "type": "string",
            "nullable": true,
            "description": "Leaf task of this project."
          },
          "allocationType": {
            "type": "string",
            "enum": [
              "HARD",
              "SOFT"
            ],
            "default": "HARD"
          },
          "notes": {
            "type": "string",
            "nullable": true
          }
        },
        "required": [
          "employeeCode",
          "startDate",
          "endDate",
          "allocate",
          "unit"
        ]
      },
      "ProjectResourceInputDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "employeeCode": {
            "type": "string"
          },
          "role": {
            "type": "string",
            "enum": [
              "ENGAGEMENT_PARTNER",
              "PROJECT_MANAGER",
              "TEAM_MEMBER"
            ],
            "default": "TEAM_MEMBER"
          },
          "billingClass": {
            "type": "string",
            "enum": [
              "PARTNER",
              "MANAGER",
              "SENIOR_ASSOCIATE",
              "ASSOCIATE"
            ],
            "default": "ASSOCIATE"
          },
          "overrideCost": {
            "type": "number",
            "nullable": true,
            "minimum": 0
          }
        },
        "required": [
          "employeeCode"
        ]
      },
      "UpdateCrmProjectDto": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 2,
            "maxLength": 200
          },
          "transactionDate": {
            "type": "string",
            "nullable": true
          },
          "startDate": {
            "type": "string",
            "nullable": true
          },
          "endDate": {
            "type": "string",
            "nullable": true
          },
          "projectType": {
            "type": "string",
            "enum": [
              "Audit",
              "Tax",
              "Consulting"
            ]
          },
          "managerId": {
            "type": "string",
            "nullable": true,
            "description": "Project manager (RsmidUser id)."
          },
          "billingMethod": {
            "type": "string",
            "enum": [
              "FIXED_FEE",
              "CHARGE_BASED"
            ],
            "nullable": true
          },
          "currency": {
            "type": "string",
            "example": "IDR"
          },
          "resourceAllocations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProjectAllocationInputDto"
            }
          },
          "resourceDetails": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProjectResourceInputDto"
            }
          },
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          }
        }
      },
      "ProjectTaskBulkRowDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "isMilestone": {
            "type": "boolean"
          },
          "startDate": {
            "type": "string",
            "nullable": true
          },
          "endDate": {
            "type": "string",
            "nullable": true
          },
          "plannedWork": {
            "type": "number",
            "minimum": 0,
            "maximum": 9999.99
          }
        }
      },
      "BulkProjectTasksDto": {
        "type": "object",
        "properties": {
          "rows": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProjectTaskBulkRowDto"
            }
          },
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          }
        },
        "required": [
          "rows"
        ]
      },
      "ProjectTaskAssigneeInputDto": {
        "type": "object",
        "properties": {
          "employeeCode": {
            "type": "string"
          },
          "hours": {
            "type": "number",
            "minimum": 0,
            "maximum": 9999
          }
        },
        "required": [
          "employeeCode",
          "hours"
        ]
      },
      "CreateProjectTaskDto": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "TODO",
              "IN_PROGRESS",
              "REVIEW",
              "DONE"
            ]
          },
          "priority": {
            "type": "number",
            "description": "0=normal, 1=high, 2=urgent"
          },
          "assigneeId": {
            "type": "string",
            "description": "RSMID user ID of the assignee"
          },
          "dueDate": {
            "type": "string"
          },
          "estimatedHours": {
            "type": "number"
          },
          "sortOrder": {
            "type": "number"
          }
        },
        "required": [
          "name"
        ]
      },
      "UpdateProjectTaskDto": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "TODO",
              "IN_PROGRESS",
              "REVIEW",
              "DONE"
            ]
          },
          "priority": {
            "type": "number"
          },
          "assigneeId": {
            "type": "string"
          },
          "dueDate": {
            "type": "string"
          },
          "estimatedHours": {
            "type": "number"
          },
          "sortOrder": {
            "type": "number"
          }
        }
      },
      "ProjectTransitionDto": {
        "type": "object",
        "properties": {
          "comments": {
            "type": "string",
            "maxLength": 1000
          },
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          }
        }
      },
      "ProjectReasonDto": {
        "type": "object",
        "properties": {
          "reason": {
            "type": "string",
            "minLength": 2,
            "maxLength": 1000
          },
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          }
        },
        "required": [
          "reason"
        ]
      },
      "CreateProjectIirDto": {
        "type": "object",
        "properties": {
          "termin": {
            "type": "number",
            "minimum": 1
          }
        },
        "required": [
          "termin"
        ]
      },
      "CreateCrmTimesheetDto": {
        "type": "object",
        "properties": {
          "date": {
            "type": "string",
            "description": "Entry date (YYYY-MM-DD)."
          },
          "projectRef": {
            "type": "string",
            "description": "RsmidProject id from /options, or the literal \"office\" (Non-billable office time)."
          },
          "taskId": {
            "type": "string",
            "nullable": true,
            "description": "RsmidProjectTask id of the chosen project."
          },
          "startTime": {
            "type": "string",
            "example": "08:00"
          },
          "endTime": {
            "type": "string",
            "example": "16:00"
          },
          "memo": {
            "type": "string",
            "nullable": true,
            "maxLength": 500
          },
          "employeeId": {
            "type": "string",
            "description": "Reviewer-only (timesheet:approve): book the entry for another employee."
          }
        },
        "required": [
          "date",
          "projectRef",
          "startTime",
          "endTime"
        ]
      },
      "UpdateCrmTimesheetDto": {
        "type": "object",
        "properties": {
          "date": {
            "type": "string"
          },
          "projectRef": {
            "type": "string",
            "description": "RsmidProject id or \"office\"; a change without taskId clears the task."
          },
          "taskId": {
            "type": "string",
            "nullable": true
          },
          "startTime": {
            "type": "string"
          },
          "endTime": {
            "type": "string"
          },
          "memo": {
            "type": "string",
            "nullable": true,
            "maxLength": 500
          },
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          }
        }
      },
      "CrmTimesheetTransitionDto": {
        "type": "object",
        "properties": {
          "comments": {
            "type": "string",
            "maxLength": 1000
          },
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          }
        }
      },
      "RejectCrmTimesheetDto": {
        "type": "object",
        "properties": {
          "reason": {
            "type": "string",
            "minLength": 2,
            "maxLength": 1000
          },
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          }
        },
        "required": [
          "reason"
        ]
      },
      "CreateIirFromTermDto": {
        "type": "object",
        "properties": {
          "projectId": {
            "type": "string",
            "description": "Project (RsmidProject id) whose IIR Plan the installment belongs to."
          },
          "termSequence": {
            "type": "number",
            "minimum": 1,
            "description": "Installment number (IIR Plan \"Termin N\")."
          }
        },
        "required": [
          "projectId",
          "termSequence"
        ]
      },
      "UpdateIirDto": {
        "type": "object",
        "properties": {
          "invoiceAlias": {
            "type": "string",
            "nullable": true,
            "maxLength": 200,
            "description": "Invoice alias (bill-to name)."
          },
          "invoiceGroupingMemo": {
            "type": "string",
            "nullable": true,
            "maxLength": 2000,
            "description": "Invoice grouping memo."
          },
          "integrationNote": {
            "type": "string",
            "nullable": true,
            "maxLength": 2000,
            "description": "Integration note (invoice alias scheme)."
          },
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          }
        }
      },
      "IirTransitionDto": {
        "type": "object",
        "properties": {
          "comments": {
            "type": "string",
            "maxLength": 1000
          },
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          }
        }
      },
      "RejectIirDto": {
        "type": "object",
        "properties": {
          "reason": {
            "type": "string",
            "minLength": 2,
            "maxLength": 1000
          },
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          }
        },
        "required": [
          "reason"
        ]
      },
      "CreateReleaseChecklistDto": {
        "type": "object",
        "properties": {
          "projectId": {
            "type": "string",
            "description": "RsmidProject id — one checklist per project."
          },
          "releaseType": {
            "type": "string",
            "enum": [
              "AUDIT",
              "TAX",
              "CONSULTING"
            ],
            "description": "Default: assignment type, else Audit project."
          },
          "checklistDate": {
            "type": "string",
            "description": "Date (YYYY-MM-DD); default today (Asia/Jakarta)."
          }
        },
        "required": [
          "projectId"
        ]
      },
      "EngagementDetailsInputDto": {
        "type": "object",
        "properties": {
          "signingNumber": {
            "type": "number",
            "nullable": true,
            "minimum": 1,
            "maximum": 7
          },
          "ownership": {
            "type": "string",
            "nullable": true
          },
          "goPublic": {
            "type": "boolean",
            "nullable": true
          },
          "auditType": {
            "type": "string",
            "nullable": true
          },
          "accountingType": {
            "type": "string",
            "nullable": true
          },
          "industryCode": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "FinancialSummaryInputDto": {
        "type": "object",
        "properties": {
          "currency": {
            "type": "string",
            "nullable": true
          },
          "totalAssets": {
            "type": "number",
            "nullable": true
          },
          "totalLiabilities": {
            "type": "number",
            "nullable": true
          },
          "revenue": {
            "type": "number",
            "nullable": true
          },
          "profitBeforeTax": {
            "type": "number",
            "nullable": true
          },
          "netProfit": {
            "type": "number",
            "nullable": true
          },
          "totalComprehensiveIncome": {
            "type": "number",
            "nullable": true
          }
        }
      },
      "ServiceDetailsInputDto": {
        "type": "object",
        "properties": {
          "ownership": {
            "type": "string",
            "nullable": true
          },
          "goPublic": {
            "type": "boolean",
            "nullable": true
          },
          "industryCode": {
            "type": "string",
            "nullable": true
          },
          "assuranceServiceType": {
            "type": "string",
            "nullable": true,
            "description": "null = \"— bukan jasa asurans\""
          },
          "nonAssuranceServiceType": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "ReleaseReviewerInputDto": {
        "type": "object",
        "properties": {
          "stage": {
            "type": "string",
            "enum": [
              "MANAGER",
              "EP",
              "QA",
              "CONCURRING"
            ]
          },
          "assigneeName": {
            "type": "string",
            "nullable": true,
            "maxLength": 120
          }
        },
        "required": [
          "stage"
        ]
      },
      "UpdateReleaseChecklistDto": {
        "type": "object",
        "properties": {
          "checklistDate": {
            "type": "string",
            "description": "Date (YYYY-MM-DD)."
          },
          "releaseType": {
            "type": "string",
            "enum": [
              "AUDIT",
              "TAX",
              "CONSULTING"
            ],
            "description": "Editable only while Draft."
          },
          "engagementDetails": {
            "$ref": "#/components/schemas/EngagementDetailsInputDto"
          },
          "opinionType": {
            "type": "string",
            "enum": [
              "WTP",
              "WDP",
              "TMP",
              "TIDAK_WAJAR"
            ],
            "nullable": true
          },
          "financialSummary": {
            "$ref": "#/components/schemas/FinancialSummaryInputDto"
          },
          "serviceDetails": {
            "$ref": "#/components/schemas/ServiceDetailsInputDto"
          },
          "reviewers": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ReleaseReviewerInputDto"
            }
          },
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          }
        }
      },
      "ReleaseCommentDto": {
        "type": "object",
        "properties": {
          "comments": {
            "type": "string",
            "maxLength": 1000
          },
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          }
        }
      },
      "ReleasePaymentCheckDto": {
        "type": "object",
        "properties": {
          "paymentStatus": {
            "type": "string",
            "enum": [
              "LUNAS",
              "OUTSTANDING"
            ]
          },
          "remarks": {
            "type": "string",
            "maxLength": 1000
          },
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          }
        },
        "required": [
          "paymentStatus"
        ]
      },
      "ReleaseAgreementDto": {
        "type": "object",
        "properties": {
          "decision": {
            "type": "string",
            "enum": [
              "AGREED",
              "DISAGREE"
            ]
          },
          "remarks": {
            "type": "string",
            "maxLength": 1000,
            "description": "Required (≥ 2 chars) for DISAGREE."
          },
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          }
        },
        "required": [
          "decision"
        ]
      },
      "ReleaseOpinionRequestDto": {
        "type": "object",
        "properties": {
          "opinionNumber": {
            "type": "string",
            "minLength": 3,
            "maxLength": 80,
            "description": "Opinion number from the KAP opinion register."
          },
          "comments": {
            "type": "string",
            "maxLength": 1000
          },
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          }
        },
        "required": [
          "opinionNumber"
        ]
      },
      "ReleaseDeliveryDto": {
        "type": "object",
        "properties": {
          "dateSentToClient": {
            "type": "string",
            "description": "Date sent to client (YYYY-MM-DD, not in the future)."
          },
          "archived": {
            "type": "boolean",
            "description": "true = \"Already archived\"."
          },
          "comments": {
            "type": "string",
            "maxLength": 1000
          },
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          }
        }
      },
      "ClientSurveyRespondentInputDto": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          },
          "email": {
            "type": "string",
            "nullable": true,
            "maxLength": 200
          },
          "phone": {
            "type": "string",
            "nullable": true,
            "maxLength": 40
          }
        },
        "required": [
          "name"
        ]
      },
      "CreateClientSurveyDto": {
        "type": "object",
        "properties": {
          "clientName": {
            "type": "string",
            "minLength": 2
          },
          "engagementTitle": {
            "type": "string",
            "minLength": 2
          },
          "serviceLine": {
            "type": "string",
            "enum": [
              "AUDIT_ASSURANCE",
              "TAX",
              "ADVISORY",
              "TRANSACTION_SERVICES"
            ]
          },
          "surveyDate": {
            "type": "string",
            "description": "YYYY-MM-DD"
          },
          "netsuiteClientId": {
            "type": "string",
            "description": "NetSuite customer internalId (client master)."
          },
          "apergusuiteCustomerId": {
            "type": "string"
          },
          "apergusuiteOpportunityId": {
            "type": "string"
          },
          "notes": {
            "type": "string"
          },
          "responses": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SurveyResponseDto"
            }
          }
        },
        "required": [
          "clientName",
          "engagementTitle",
          "serviceLine",
          "surveyDate",
          "responses"
        ]
      },
      "ClientSurveyAnswersInputDto": {
        "type": "object",
        "properties": {
          "q1": {
            "type": "string",
            "nullable": true,
            "enum": [
              "SANGAT_PUAS",
              "PUAS",
              "NETRAL",
              "TIDAK_PUAS",
              "SANGAT_TIDAK_PUAS"
            ]
          },
          "q2": {
            "type": "string",
            "nullable": true,
            "enum": [
              "SANGAT_SIGNIFIKAN",
              "SIGNIFIKAN",
              "CUKUP",
              "SEDIKIT",
              "TIDAK_SAMA_SEKALI"
            ]
          },
          "q3": {
            "type": "number",
            "nullable": true,
            "minimum": 0,
            "maximum": 10
          },
          "q4": {
            "type": "string",
            "nullable": true,
            "enum": [
              "SANGAT_PUAS",
              "PUAS",
              "NETRAL",
              "TIDAK_PUAS",
              "SANGAT_TIDAK_PUAS"
            ]
          },
          "q5": {
            "type": "string",
            "nullable": true,
            "enum": [
              "SANGAT_MEMAHAMI",
              "MEMAHAMI",
              "CUKUP_MEMAHAMI",
              "NETRAL",
              "TIDAK_MEMAHAMI",
              "SANGAT_TIDAK_MEMAHAMI"
            ]
          },
          "q6": {
            "type": "string",
            "nullable": true,
            "enum": [
              "SANGAT_PUAS",
              "PUAS",
              "NETRAL",
              "TIDAK_PUAS",
              "SANGAT_TIDAK_PUAS"
            ]
          },
          "q7": {
            "type": "string",
            "nullable": true,
            "enum": [
              "SANGAT_BAIK",
              "BAIK",
              "CUKUP",
              "KURANG_BAIK",
              "SANGAT_BURUK"
            ]
          },
          "q8": {
            "type": "string",
            "nullable": true,
            "enum": [
              "SANGAT_PUAS",
              "PUAS",
              "NETRAL",
              "TIDAK_PUAS",
              "SANGAT_TIDAK_PUAS"
            ]
          }
        }
      },
      "UpdateClientSurveyDto": {
        "type": "object",
        "properties": {
          "clientName": {
            "type": "string"
          },
          "engagementTitle": {
            "type": "string"
          },
          "serviceLine": {
            "type": "string",
            "enum": [
              "AUDIT_ASSURANCE",
              "TAX",
              "ADVISORY",
              "TRANSACTION_SERVICES"
            ]
          },
          "surveyDate": {
            "type": "string"
          },
          "netsuiteClientId": {
            "type": "string"
          },
          "notes": {
            "type": "string"
          },
          "responses": {
            "description": "Replaces the whole response set.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SurveyResponseDto"
            }
          }
        }
      },
      "SendClientSurveyDto": {
        "type": "object",
        "properties": {
          "dateSent": {
            "type": "string",
            "description": "Date sent (YYYY-MM-DD); default the stored date, else today (Asia/Jakarta)."
          },
          "comments": {
            "type": "string",
            "maxLength": 1000
          },
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          }
        }
      },
      "CompleteClientSurveyDto": {
        "type": "object",
        "properties": {
          "dateCompleted": {
            "type": "string",
            "description": "Date completed (YYYY-MM-DD); default the stored date, else today (Asia/Jakarta)."
          },
          "comments": {
            "type": "string",
            "maxLength": 1000
          },
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          }
        }
      },
      "WpHardcopyInputDto": {
        "type": "object",
        "properties": {
          "containsPersonalData": {
            "type": "boolean",
            "description": "Contains personal data? (No / Yes)."
          },
          "applicable": {
            "type": "boolean",
            "description": "Status: Applicable (true) / Not Applicable (false)."
          },
          "reason": {
            "type": "string",
            "nullable": true,
            "maxLength": 500,
            "description": "Reason (shown when Not Applicable)."
          },
          "permanentFileCount": {
            "type": "number",
            "nullable": true,
            "minimum": 0,
            "maximum": 9999
          },
          "currentFileCount": {
            "type": "number",
            "nullable": true,
            "minimum": 0,
            "maximum": 9999
          }
        }
      },
      "WpDigitalInputDto": {
        "type": "object",
        "properties": {
          "containsPersonalData": {
            "type": "boolean",
            "description": "Contains personal data? (No / Yes)."
          },
          "applicable": {
            "type": "boolean",
            "description": "Status: Applicable (true) / Not Applicable (false)."
          },
          "reason": {
            "type": "string",
            "nullable": true,
            "maxLength": 500,
            "description": "Reason (shown when Not Applicable)."
          },
          "fileName": {
            "type": "string",
            "nullable": true,
            "maxLength": 255
          }
        }
      },
      "WpDocumentsInputDto": {
        "type": "object",
        "properties": {
          "hardcopy": {
            "$ref": "#/components/schemas/WpHardcopyInputDto"
          },
          "softcopy": {
            "$ref": "#/components/schemas/WpDigitalInputDto"
          },
          "orb": {
            "$ref": "#/components/schemas/WpDigitalInputDto"
          }
        }
      },
      "CreateWpSubmissionDto": {
        "type": "object",
        "properties": {
          "projectId": {
            "type": "string",
            "description": "Project (RsmidProject id) the WP Submission belongs to."
          },
          "documents": {
            "$ref": "#/components/schemas/WpDocumentsInputDto"
          }
        },
        "required": [
          "projectId"
        ]
      },
      "WpFinalReportInputDto": {
        "type": "object",
        "properties": {
          "hardcopy": {
            "type": "boolean",
            "description": "Hardcopy? (Ya)."
          },
          "softcopy": {
            "type": "boolean",
            "description": "Softcopy? (Ya)."
          },
          "hardcopyStorage": {
            "type": "string",
            "enum": [
              "OFFICE",
              "MMI"
            ]
          },
          "shelfLocation": {
            "type": "string",
            "nullable": true,
            "maxLength": 200
          },
          "barcode": {
            "type": "string",
            "nullable": true,
            "maxLength": 100
          }
        }
      },
      "WpWorkingPaperInputDto": {
        "type": "object",
        "properties": {
          "storage": {
            "type": "string",
            "enum": [
              "OFFICE",
              "MMI"
            ]
          },
          "shelfLocation": {
            "type": "string",
            "nullable": true,
            "maxLength": 200
          },
          "barcode": {
            "type": "string",
            "nullable": true,
            "maxLength": 100
          }
        }
      },
      "WpStorageInputDto": {
        "type": "object",
        "properties": {
          "finalReport": {
            "$ref": "#/components/schemas/WpFinalReportInputDto"
          },
          "workingPaper": {
            "$ref": "#/components/schemas/WpWorkingPaperInputDto"
          }
        }
      },
      "UpdateWpSubmissionDto": {
        "type": "object",
        "properties": {
          "documents": {
            "description": "Group \"documents\" — Draft only (ECS).",
            "allOf": [
              {
                "$ref": "#/components/schemas/WpDocumentsInputDto"
              }
            ]
          },
          "hardcopyReceivedDate": {
            "type": "string",
            "nullable": true,
            "description": "Group \"receiptGa\" — WP Hardcopy received date (YYYY-MM-DD)."
          },
          "hardcopyReceivedBy": {
            "type": "string",
            "nullable": true,
            "maxLength": 120,
            "description": "Received by (GA)."
          },
          "digitalReceivedDate": {
            "type": "string",
            "nullable": true,
            "description": "Group \"receiptIt\" — Softcopy/ORB received date (YYYY-MM-DD)."
          },
          "digitalReceivedBy": {
            "type": "string",
            "nullable": true,
            "maxLength": 120,
            "description": "Received by (IT Team)."
          },
          "storage": {
            "description": "Group \"storage\" — filled by GA/IT after Partner approval.",
            "allOf": [
              {
                "$ref": "#/components/schemas/WpStorageInputDto"
              }
            ]
          },
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          }
        }
      },
      "WpTransitionDto": {
        "type": "object",
        "properties": {
          "comments": {
            "type": "string",
            "maxLength": 1000
          },
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          }
        }
      },
      "RejectWpDto": {
        "type": "object",
        "properties": {
          "reason": {
            "type": "string",
            "minLength": 2,
            "maxLength": 1000
          },
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          }
        },
        "required": [
          "reason"
        ]
      },
      "CreateBorrowingDto": {
        "type": "object",
        "properties": {
          "documentTypes": {
            "type": "array",
            "description": "Document type being borrowed.",
            "items": {
              "type": "string",
              "enum": [
                "FINAL_REPORT",
                "WP_HARDCOPY",
                "SMART",
                "ORB",
                "SOFTCOPY"
              ]
            }
          },
          "purpose": {
            "type": "string",
            "nullable": true,
            "maxLength": 2000,
            "description": "Reason for borrowing."
          },
          "crossDivision": {
            "type": "boolean",
            "description": "Cross division? (default: caller division ≠ assignment division)."
          },
          "externalWarehouse": {
            "type": "boolean",
            "description": "Document in external warehouse (MMI)?"
          },
          "assignmentId": {
            "type": "string",
            "description": "CrmAssignment id whose archived documents are borrowed."
          }
        },
        "required": [
          "assignmentId"
        ]
      },
      "UpdateBorrowingDto": {
        "type": "object",
        "properties": {
          "documentTypes": {
            "type": "array",
            "description": "Document type being borrowed.",
            "items": {
              "type": "string",
              "enum": [
                "FINAL_REPORT",
                "WP_HARDCOPY",
                "SMART",
                "ORB",
                "SOFTCOPY"
              ]
            }
          },
          "purpose": {
            "type": "string",
            "nullable": true,
            "maxLength": 2000,
            "description": "Reason for borrowing."
          },
          "crossDivision": {
            "type": "boolean",
            "description": "Cross division? (default: caller division ≠ assignment division)."
          },
          "externalWarehouse": {
            "type": "boolean",
            "description": "Document in external warehouse (MMI)?"
          },
          "assignmentId": {
            "type": "string"
          },
          "borrowingDate": {
            "type": "string",
            "nullable": true
          },
          "documentCount": {
            "type": "number",
            "nullable": true,
            "minimum": 1,
            "maximum": 9999
          },
          "returnDate": {
            "type": "string",
            "nullable": true
          },
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          }
        }
      },
      "BorrowingSubmitDto": {
        "type": "object",
        "properties": {
          "comments": {
            "type": "string",
            "maxLength": 1000
          },
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          }
        }
      },
      "BorrowingApproveDto": {
        "type": "object",
        "properties": {
          "comments": {
            "type": "string",
            "maxLength": 1000
          },
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          },
          "step": {
            "type": "string",
            "enum": [
              "MANAGER",
              "PARTNER",
              "MPLOB",
              "PROJECT_OWNER",
              "CAO"
            ]
          }
        },
        "required": [
          "step"
        ]
      },
      "BorrowingRejectDto": {
        "type": "object",
        "properties": {
          "step": {
            "type": "string",
            "enum": [
              "MANAGER",
              "PARTNER",
              "MPLOB",
              "PROJECT_OWNER",
              "CAO"
            ]
          },
          "reason": {
            "type": "string",
            "minLength": 2,
            "maxLength": 1000
          },
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          }
        },
        "required": [
          "step",
          "reason"
        ]
      },
      "SpbEmployeeItemInputDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Existing line id (absent = new row)."
          },
          "category": {
            "type": "string",
            "enum": [
              "TRANSPORTATION",
              "ACCOMMODATION",
              "MEAL",
              "OTHER"
            ],
            "description": "Transport | Akomodasi | Meals | Lain-lain."
          },
          "date": {
            "type": "string",
            "nullable": true,
            "description": "Line date (YYYY-MM-DD); required on Submit."
          },
          "projectId": {
            "type": "string",
            "nullable": true
          },
          "taskId": {
            "type": "string",
            "nullable": true
          },
          "currency": {
            "type": "string",
            "enum": [
              "IDR",
              "USD"
            ]
          },
          "foreignAmount": {
            "type": "number",
            "minimum": 0,
            "description": "Original value in the line currency."
          },
          "exchangeRate": {
            "type": "number",
            "description": "Rate to IDR (> 0)."
          },
          "billable": {
            "type": "boolean"
          }
        },
        "required": [
          "category",
          "currency",
          "foreignAmount",
          "exchangeRate",
          "billable"
        ]
      },
      "CreateSpbEmployeeDto": {
        "type": "object",
        "properties": {
          "transactionDate": {
            "type": "string",
            "description": "Date transaction (YYYY-MM-DD); default today (Asia/Jakarta)."
          },
          "notes": {
            "type": "string",
            "nullable": true,
            "description": "Details › Catatan."
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SpbEmployeeItemInputDto"
            }
          }
        }
      },
      "UpdateSpbEmployeeDto": {
        "type": "object",
        "properties": {
          "transactionDate": {
            "type": "string",
            "description": "Date transaction (YYYY-MM-DD); default today (Asia/Jakarta)."
          },
          "notes": {
            "type": "string",
            "nullable": true,
            "description": "Details › Catatan."
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SpbEmployeeItemInputDto"
            }
          },
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          }
        }
      },
      "SpbTransitionDto": {
        "type": "object",
        "properties": {
          "comments": {
            "type": "string",
            "maxLength": 1000
          },
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          }
        }
      },
      "RejectSpbDto": {
        "type": "object",
        "properties": {
          "reason": {
            "type": "string",
            "minLength": 2,
            "maxLength": 1000
          },
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          }
        },
        "required": [
          "reason"
        ]
      },
      "UploadSpbEvidenceDto": {
        "type": "object",
        "properties": {
          "itemId": {
            "type": "string",
            "description": "Line the receipt belongs to; absent = SPB-level evidence."
          }
        }
      },
      "SpbVendorItemDto": {
        "type": "object",
        "properties": {
          "itemName": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "description": "Item / Service."
          },
          "quantity": {
            "type": "number",
            "minimum": 1,
            "maximum": 1000000,
            "description": "Qty."
          },
          "estimatedAmount": {
            "type": "number",
            "minimum": 0,
            "description": "Estimated amount (Rp)."
          }
        },
        "required": [
          "itemName",
          "quantity",
          "estimatedAmount"
        ]
      },
      "CreateSpbVendorDto": {
        "type": "object",
        "properties": {
          "projectId": {
            "type": "string",
            "nullable": true,
            "description": "Project (RsmidProject id); null unlinks."
          },
          "notes": {
            "type": "string",
            "nullable": true,
            "description": "Details › Catatan."
          },
          "items": {
            "description": "Items tab — replaces the tab when present.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SpbVendorItemDto"
            }
          },
          "netsuiteVendorId": {
            "type": "string",
            "example": "1423",
            "description": "Vendor * — NetSuite vendor internal id."
          },
          "transactionDate": {
            "type": "string",
            "description": "Date transaction (YYYY-MM-DD); default today (Asia/Jakarta)."
          }
        },
        "required": [
          "netsuiteVendorId"
        ]
      },
      "UpdateSpbVendorDto": {
        "type": "object",
        "properties": {
          "projectId": {
            "type": "string",
            "nullable": true,
            "description": "Project (RsmidProject id); null unlinks."
          },
          "notes": {
            "type": "string",
            "nullable": true,
            "description": "Details › Catatan."
          },
          "items": {
            "description": "Items tab — replaces the tab when present.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SpbVendorItemDto"
            }
          },
          "netsuiteVendorId": {
            "type": "string",
            "example": "1423"
          },
          "transactionDate": {
            "type": "string",
            "description": "Date transaction (YYYY-MM-DD); cannot be cleared."
          },
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          }
        }
      },
      "SpbVendorTransitionDto": {
        "type": "object",
        "properties": {
          "comments": {
            "type": "string",
            "maxLength": 1000
          },
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          }
        }
      },
      "RejectSpbVendorDto": {
        "type": "object",
        "properties": {
          "reason": {
            "type": "string",
            "minLength": 2,
            "maxLength": 1000
          },
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          }
        },
        "required": [
          "reason"
        ]
      },
      "TaAdvanceItemInputDto": {
        "type": "object",
        "properties": {
          "item": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "description": "Item (Chart of Account) — free text."
          },
          "qty": {
            "type": "number",
            "minimum": 1,
            "maximum": 9999,
            "default": 1
          },
          "rate": {
            "type": "number",
            "minimum": 0,
            "default": 0
          },
          "taxCode": {
            "type": "string",
            "enum": [
              "PPN",
              "NON_PPN"
            ],
            "nullable": true
          },
          "wht": {
            "type": "string",
            "enum": [
              "PPH21",
              "PPH23"
            ],
            "nullable": true
          }
        },
        "required": [
          "item",
          "qty",
          "rate"
        ]
      },
      "CreateTaAdvanceDto": {
        "type": "object",
        "properties": {
          "projectId": {
            "type": "string",
            "nullable": true,
            "description": "Project reference (RsmidProject id); required on submit."
          },
          "transactionDate": {
            "type": "string",
            "description": "Date transaction (YYYY-MM-DD); default today (Asia/Jakarta)."
          },
          "isOpe": {
            "type": "boolean",
            "description": "OPE? Yes / No (default Yes)."
          },
          "tripType": {
            "type": "string",
            "enum": [
              "DOMESTIC",
              "INTERNATIONAL"
            ]
          },
          "destination": {
            "type": "string",
            "nullable": true,
            "maxLength": 120
          },
          "departureDate": {
            "type": "string",
            "nullable": true,
            "description": "Departure date (YYYY-MM-DD)."
          },
          "returnDate": {
            "type": "string",
            "nullable": true,
            "description": "Return date (YYYY-MM-DD), ≥ departure date."
          },
          "items": {
            "description": "Items tab; when present REPLACES the rows.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TaAdvanceItemInputDto"
            }
          }
        }
      },
      "UpdateTaAdvanceDto": {
        "type": "object",
        "properties": {
          "projectId": {
            "type": "string",
            "nullable": true,
            "description": "Project reference (RsmidProject id); required on submit."
          },
          "transactionDate": {
            "type": "string",
            "description": "Date transaction (YYYY-MM-DD); default today (Asia/Jakarta)."
          },
          "isOpe": {
            "type": "boolean",
            "description": "OPE? Yes / No (default Yes)."
          },
          "tripType": {
            "type": "string",
            "enum": [
              "DOMESTIC",
              "INTERNATIONAL"
            ]
          },
          "destination": {
            "type": "string",
            "nullable": true,
            "maxLength": 120
          },
          "departureDate": {
            "type": "string",
            "nullable": true,
            "description": "Departure date (YYYY-MM-DD)."
          },
          "returnDate": {
            "type": "string",
            "nullable": true,
            "description": "Return date (YYYY-MM-DD), ≥ departure date."
          },
          "items": {
            "description": "Items tab; when present REPLACES the rows.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TaAdvanceItemInputDto"
            }
          },
          "paymentRequestStatus": {
            "type": "string",
            "enum": [
              "DIAJUKAN"
            ],
            "nullable": true,
            "description": "null = not yet requested; \"Selesai\" is set only by Mark as Paid."
          },
          "paymentDate": {
            "type": "string",
            "nullable": true,
            "description": "Payment date (YYYY-MM-DD)."
          },
          "amountPaid": {
            "type": "number",
            "nullable": true,
            "minimum": 0.01
          },
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          }
        }
      },
      "TaTransitionDto": {
        "type": "object",
        "properties": {
          "comments": {
            "type": "string",
            "maxLength": 1000
          },
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          }
        }
      },
      "RejectTaAdvanceDto": {
        "type": "object",
        "properties": {
          "reason": {
            "type": "string",
            "minLength": 2,
            "maxLength": 1000
          },
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          }
        },
        "required": [
          "reason"
        ]
      },
      "PayTaAdvanceDto": {
        "type": "object",
        "properties": {
          "paymentDate": {
            "type": "string",
            "description": "Payment date (YYYY-MM-DD)."
          },
          "amountPaid": {
            "type": "number",
            "minimum": 0.01
          },
          "comments": {
            "type": "string",
            "maxLength": 1000
          },
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          }
        },
        "required": [
          "paymentDate",
          "amountPaid"
        ]
      },
      "TaSettlementLineInputDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Existing line id (absent = new row)."
          },
          "item": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "example": "Hotel 4 malam"
          },
          "qty": {
            "type": "number",
            "minimum": 1,
            "maximum": 100000,
            "example": 4
          },
          "rate": {
            "type": "number",
            "minimum": 0,
            "example": 650000,
            "description": "Rate (artifact step 10000 is a spinner hint only)."
          }
        },
        "required": [
          "item",
          "qty",
          "rate"
        ]
      },
      "CreateTaSettlementDto": {
        "type": "object",
        "properties": {
          "taAdvanceId": {
            "type": "string",
            "description": "The Approved TA Advance being settled."
          },
          "transactionDate": {
            "type": "string",
            "description": "Date transaction (YYYY-MM-DD); default today (Asia/Jakarta)."
          },
          "expenseLines": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TaSettlementLineInputDto"
            }
          },
          "transferDate": {
            "type": "string",
            "nullable": true,
            "description": "Transfer to Company date (Over Payment only)."
          }
        },
        "required": [
          "taAdvanceId"
        ]
      },
      "UpdateTaSettlementDto": {
        "type": "object",
        "properties": {
          "transactionDate": {
            "type": "string",
            "description": "Date transaction (YYYY-MM-DD)."
          },
          "expenseLines": {
            "description": "Replaces the tab; synced by line id.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TaSettlementLineInputDto"
            }
          },
          "transferDate": {
            "type": "string",
            "nullable": true,
            "description": "Transfer date; null clears."
          },
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          }
        }
      },
      "TaSettlementTransitionDto": {
        "type": "object",
        "properties": {
          "comments": {
            "type": "string",
            "maxLength": 1000
          },
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          }
        }
      },
      "RejectTaSettlementDto": {
        "type": "object",
        "properties": {
          "reason": {
            "type": "string",
            "minLength": 2,
            "maxLength": 1000
          },
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          }
        },
        "required": [
          "reason"
        ]
      },
      "TaSettlementPaymentDto": {
        "type": "object",
        "properties": {
          "additionalPaymentDate": {
            "type": "string",
            "description": "Additional payment date (YYYY-MM-DD)."
          },
          "additionalAmountPaid": {
            "type": "number",
            "minimum": 0.01,
            "description": "Additional amount paid."
          },
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          }
        },
        "required": [
          "additionalPaymentDate",
          "additionalAmountPaid"
        ]
      },
      "MccerTransactionInputDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Existing row id (absent = new row)."
          },
          "date": {
            "type": "string",
            "description": "Date (YYYY-MM-DD)."
          },
          "merchant": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "description": "Merchant name."
          },
          "amount": {
            "type": "number",
            "description": "Amount (any sign, ≤ 2 dp)."
          }
        },
        "required": [
          "date",
          "merchant",
          "amount"
        ]
      },
      "CreateMccerDto": {
        "type": "object",
        "properties": {
          "statementDate": {
            "type": "string",
            "nullable": true,
            "description": "Statement date (YYYY-MM-DD); required to send."
          },
          "paymentDueDate": {
            "type": "string",
            "nullable": true,
            "description": "Payment due date (YYYY-MM-DD); ≥ statement date."
          },
          "outstandingBalance": {
            "type": "number",
            "nullable": true,
            "description": "Account outstanding balance (any sign, ≤ 2 dp)."
          },
          "transactions": {
            "description": "Replaces the Transactions tab.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MccerTransactionInputDto"
            }
          },
          "cardholderUserId": {
            "type": "string",
            "description": "Cardholder (RsmidUser id, ACTIVE)."
          },
          "cardLast4": {
            "type": "string",
            "example": "4471",
            "description": "Last 4 digits of the corporate card."
          },
          "usageMonth": {
            "type": "number",
            "minimum": 1,
            "maximum": 12
          },
          "usageYear": {
            "type": "number",
            "minimum": 2000,
            "maximum": 2100
          },
          "dateTransaction": {
            "type": "string",
            "description": "Date transaction (YYYY-MM-DD); default today (Asia/Jakarta)."
          }
        },
        "required": [
          "cardholderUserId",
          "cardLast4",
          "usageMonth",
          "usageYear"
        ]
      },
      "MccerLineVerificationDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "financeVerified": {
            "type": "boolean"
          }
        },
        "required": [
          "id",
          "financeVerified"
        ]
      },
      "UpdateMccerDto": {
        "type": "object",
        "properties": {
          "statementDate": {
            "type": "string",
            "nullable": true,
            "description": "Statement date (YYYY-MM-DD); required to send."
          },
          "paymentDueDate": {
            "type": "string",
            "nullable": true,
            "description": "Payment due date (YYYY-MM-DD); ≥ statement date."
          },
          "outstandingBalance": {
            "type": "number",
            "nullable": true,
            "description": "Account outstanding balance (any sign, ≤ 2 dp)."
          },
          "transactions": {
            "description": "Replaces the Transactions tab.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MccerTransactionInputDto"
            }
          },
          "cardholderUserId": {
            "type": "string"
          },
          "cardLast4": {
            "type": "string",
            "example": "4471"
          },
          "usageMonth": {
            "type": "number",
            "minimum": 1,
            "maximum": 12
          },
          "usageYear": {
            "type": "number",
            "minimum": 2000,
            "maximum": 2100
          },
          "dateTransaction": {
            "type": "string"
          },
          "lineVerification": {
            "description": "Finance verified flags (Awaiting Settlement).",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MccerLineVerificationDto"
            }
          },
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          }
        }
      },
      "MccerTransitionDto": {
        "type": "object",
        "properties": {
          "comments": {
            "type": "string",
            "maxLength": 1000
          },
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          }
        }
      },
      "ReturnMccerDto": {
        "type": "object",
        "properties": {
          "reason": {
            "type": "string",
            "minLength": 2,
            "maxLength": 1000
          },
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          }
        },
        "required": [
          "reason"
        ]
      },
      "CreateCcSettlementDto": {
        "type": "object",
        "properties": {
          "mccerId": {
            "type": "string",
            "description": "The MCCER this settlement completes (must be Awaiting Settlement)."
          },
          "transactionDate": {
            "type": "string",
            "description": "Date transaction (YYYY-MM-DD); default today (Asia/Jakarta)."
          }
        },
        "required": [
          "mccerId"
        ]
      },
      "CcSettlementLineInputDto": {
        "type": "object",
        "properties": {
          "mccerLineId": {
            "type": "string",
            "description": "MCCER line id the row completes."
          },
          "description": {
            "type": "string",
            "enum": [
              "TRANSPORTS",
              "PROFESSIONAL_DEVELOPMENT",
              "FOOD_AND_BEVERAGE",
              "ACCOMMODATION",
              "SOFTWARE_SUBSCRIPTION",
              "FLIGHT_TICKET"
            ]
          },
          "customerId": {
            "type": "string",
            "nullable": true,
            "description": "Client register id; null = \"— Others (isi Detail)\"."
          },
          "assignment": {
            "type": "string",
            "nullable": true
          },
          "elNumber": {
            "type": "string",
            "nullable": true
          },
          "detail": {
            "type": "string",
            "nullable": true
          },
          "kind": {
            "type": "string",
            "enum": [
              "EXPENSES",
              "OPE"
            ]
          },
          "notes": {
            "type": "string",
            "nullable": true
          },
          "ref": {
            "type": "string",
            "nullable": true
          }
        },
        "required": [
          "mccerLineId",
          "description",
          "kind"
        ]
      },
      "UpdateCcSettlementDto": {
        "type": "object",
        "properties": {
          "transactionDate": {
            "type": "string",
            "description": "Date transaction (YYYY-MM-DD)."
          },
          "lines": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CcSettlementLineInputDto"
            }
          },
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          }
        }
      },
      "CcTransitionDto": {
        "type": "object",
        "properties": {
          "comments": {
            "type": "string",
            "maxLength": 1000
          },
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          }
        }
      },
      "ReturnCcSettlementDto": {
        "type": "object",
        "properties": {
          "reason": {
            "type": "string",
            "minLength": 2,
            "maxLength": 1000
          },
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          }
        },
        "required": [
          "reason"
        ]
      },
      "GenerateClearanceDto": {
        "type": "object",
        "properties": {
          "employeeId": {
            "type": "string",
            "description": "One EmployeeProfile id; absent = every recent resign date."
          },
          "since": {
            "type": "string",
            "description": "YYYY-MM-DD; default today (Asia/Jakarta) − 30 days."
          }
        }
      },
      "ClearanceRowPatchDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "wpType": {
            "type": "string",
            "enum": [
              "HARDCOPY",
              "ORB_SOFTCOPY"
            ],
            "nullable": true
          },
          "note": {
            "type": "string",
            "nullable": true,
            "maxLength": 1000
          },
          "approver1UserId": {
            "type": "string",
            "nullable": true,
            "description": "RsmidUser id; null = unassigned (CRM clearance manage only)."
          },
          "approver2UserId": {
            "type": "string",
            "nullable": true,
            "description": "RsmidUser id; null = unassigned (CRM clearance manage only)."
          }
        },
        "required": [
          "id"
        ]
      },
      "UpdateClearanceDto": {
        "type": "object",
        "properties": {
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          },
          "rows": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ClearanceRowPatchDto"
            }
          }
        }
      },
      "RefreshClearanceDto": {
        "type": "object",
        "properties": {
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          }
        }
      },
      "ApproveClearanceRowDto": {
        "type": "object",
        "properties": {
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          },
          "slot": {
            "type": "number",
            "enum": [
              1,
              2
            ]
          },
          "comments": {
            "type": "string",
            "maxLength": 1000
          }
        },
        "required": [
          "slot"
        ]
      },
      "RejectClearanceRowDto": {
        "type": "object",
        "properties": {
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          },
          "slot": {
            "type": "number",
            "enum": [
              1,
              2
            ]
          },
          "reason": {
            "type": "string",
            "minLength": 2,
            "maxLength": 1000
          }
        },
        "required": [
          "slot",
          "reason"
        ]
      },
      "ResubmitClearanceRowDto": {
        "type": "object",
        "properties": {
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          },
          "wpType": {
            "type": "string",
            "enum": [
              "HARDCOPY",
              "ORB_SOFTCOPY"
            ],
            "nullable": true
          },
          "note": {
            "type": "string",
            "nullable": true,
            "maxLength": 1000
          }
        }
      },
      "CreateWorkflowRuleDto": {
        "type": "object",
        "properties": {}
      },
      "SetEnabledDto": {
        "type": "object",
        "properties": {}
      },
      "EmitEventDto": {
        "type": "object",
        "properties": {}
      },
      "JourneyBackfillDto": {
        "type": "object",
        "properties": {
          "dryRun": {
            "type": "boolean",
            "description": "true = count what would be reconstructed and write nothing but the run audit row."
          }
        },
        "required": [
          "dryRun"
        ]
      },
      "ProjectStatusCountDto": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string"
          },
          "count": {
            "type": "number"
          }
        },
        "required": [
          "status",
          "count"
        ]
      },
      "ProjectSummaryDto": {
        "type": "object",
        "properties": {
          "totalProjects": {
            "type": "number"
          },
          "activeProjects": {
            "type": "number"
          },
          "completedProjects": {
            "type": "number"
          },
          "totalHoursLogged": {
            "type": "number"
          },
          "byStatus": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProjectStatusCountDto"
            }
          },
          "topProjects": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "totalProjects",
          "activeProjects",
          "completedProjects",
          "totalHoursLogged",
          "byStatus",
          "topProjects"
        ]
      },
      "ProjectListItemDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "startDate": {
            "type": "string",
            "nullable": true
          },
          "endDate": {
            "type": "string",
            "nullable": true
          },
          "budget": {
            "type": "number",
            "nullable": true
          },
          "divisionCode": {
            "type": "string",
            "nullable": true
          },
          "customerName": {
            "type": "string",
            "nullable": true
          },
          "hoursLogged": {
            "type": "number"
          },
          "taskCount": {
            "type": "number"
          },
          "teamSize": {
            "type": "number"
          },
          "timelineProgressPct": {
            "type": "number",
            "nullable": true
          },
          "daysRemaining": {
            "type": "number",
            "nullable": true
          },
          "createdAt": {
            "type": "string"
          },
          "updatedAt": {
            "type": "string"
          },
          "source": {
            "type": "string",
            "enum": [
              "apergusuite",
              "rsmid"
            ]
          },
          "isEditable": {
            "type": "boolean",
            "description": "True for RSMID-native projects that can be edited"
          },
          "netsuiteSyncState": {
            "type": "string",
            "enum": [
              "SYNCHRONISED",
              "NOT_SYNCHRONISED",
              "UNKNOWN"
            ],
            "description": "Whether this project exists in NetSuite. NOT_SYNCHRONISED while the project source of truth (CR-01) is undecided — no push or pull is performed in either direction."
          },
          "netsuiteSyncLabel": {
            "type": "string",
            "description": "Badge text for the sync state, e.g. \"Not synchronised — CR-01\"."
          }
        },
        "required": [
          "id",
          "name",
          "status",
          "hoursLogged",
          "taskCount",
          "teamSize",
          "createdAt",
          "updatedAt"
        ]
      },
      "CreateProjectDto": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 2
          },
          "description": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "PLANNING",
              "ACTIVE",
              "ON_HOLD",
              "COMPLETED",
              "CANCELLED"
            ]
          },
          "divisionCode": {
            "type": "string"
          },
          "customerName": {
            "type": "string",
            "description": "Company name — used only when no engagement letter and no customerId is given (snapshot source MANUAL)."
          },
          "customerId": {
            "type": "string",
            "description": "Customer (CrmCustomerMaster id) — used only when no engagement letter is linked; with an EL the Customer is server-derived from it."
          },
          "managerId": {
            "type": "string",
            "description": "RSMID user ID of the project manager"
          },
          "apergusuiteOpportunityId": {
            "type": "string",
            "description": "AperguSuite opportunity ID this project converts from"
          },
          "startDate": {
            "type": "string",
            "description": "ISO date string YYYY-MM-DD"
          },
          "endDate": {
            "type": "string",
            "description": "ISO date string YYYY-MM-DD"
          },
          "budget": {
            "type": "number",
            "description": "Budget in IDR"
          },
          "engagementLetterId": {
            "type": "string",
            "description": "CRM engagement letter ID this project was created from"
          },
          "elNumber": {
            "type": "string",
            "description": "EL / SPK reference number, e.g. EL-2026-0231"
          },
          "assignmentId": {
            "type": "string",
            "description": "CRM assignment ID this project was created from"
          },
          "assignmentCode": {
            "type": "string",
            "deprecated": true,
            "readOnly": true,
            "description": "IGNORED on write (W-18). The Assignment Code is server-generated on the CRM assignment and mirrored here from `assignmentId`. Sending it has no effect."
          },
          "transactionDate": {
            "type": "string",
            "description": "Date transaction — ISO date YYYY-MM-DD (defaults to today on create)"
          },
          "projectType": {
            "type": "string",
            "enum": [
              "Audit",
              "Non-Audit"
            ]
          },
          "releaseChecklistType": {
            "type": "string",
            "enum": [
              "Audit",
              "Assurance Non-Audit (Non-Asurans)",
              "Non-Audit"
            ]
          },
          "billingMethod": {
            "type": "string",
            "enum": [
              "FIXED_FEE",
              "CHARGE_BASED"
            ]
          },
          "confirmedFee": {
            "type": "number",
            "description": "Confirmed fee in IDR (read-only in the UI when the project came from an EL)"
          },
          "entityCode": {
            "type": "string",
            "description": "Subsidiary code — CRM entity master"
          },
          "lobCode": {
            "type": "string",
            "description": "Line of business code — CRM LOB master"
          },
          "classCode": {
            "type": "string",
            "description": "Class code — CRM reference master type CLASS"
          },
          "locationCode": {
            "type": "string",
            "description": "Location code — CRM reference master type LOCATION"
          },
          "opeAmount": {
            "type": "number",
            "description": "Amount OPE in IDR — frozen from the assignment"
          }
        },
        "required": [
          "name"
        ]
      },
      "ProjectTaskDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "projectId": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "dueDate": {
            "type": "string",
            "nullable": true
          },
          "estimatedHours": {
            "type": "number",
            "nullable": true
          },
          "hoursLogged": {
            "type": "number"
          },
          "progressPct": {
            "type": "number",
            "nullable": true
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "assigneeId": {
            "type": "string",
            "nullable": true
          },
          "assigneeName": {
            "type": "string",
            "nullable": true
          },
          "priority": {
            "type": "number"
          },
          "sortOrder": {
            "type": "number"
          },
          "source": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "projectId",
          "name",
          "status",
          "hoursLogged"
        ]
      },
      "ProjectTeamMemberDto": {
        "type": "object",
        "properties": {
          "userId": {
            "type": "string"
          },
          "displayName": {
            "type": "string"
          },
          "email": {
            "type": "string"
          },
          "roles": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "hoursLogged": {
            "type": "number"
          },
          "lastActiveDate": {
            "type": "string",
            "nullable": true
          },
          "role": {
            "type": "string",
            "nullable": true
          }
        },
        "required": [
          "userId",
          "displayName",
          "email",
          "roles",
          "hoursLogged"
        ]
      },
      "ProjectMetricsDto": {
        "type": "object",
        "properties": {
          "totalHoursLogged": {
            "type": "number"
          },
          "billableHours": {
            "type": "number"
          },
          "nonBillableHours": {
            "type": "number"
          },
          "budget": {
            "type": "number",
            "nullable": true
          },
          "estimatedCost": {
            "type": "number",
            "nullable": true
          },
          "budgetUtilizationPct": {
            "type": "number",
            "nullable": true
          },
          "activeMembers": {
            "type": "number"
          },
          "daysRemaining": {
            "type": "number",
            "nullable": true
          },
          "timelineProgressPct": {
            "type": "number",
            "nullable": true
          },
          "taskTotal": {
            "type": "number"
          },
          "taskDone": {
            "type": "number"
          },
          "taskCompletionPct": {
            "type": "number",
            "nullable": true
          }
        },
        "required": [
          "totalHoursLogged",
          "billableHours",
          "nonBillableHours",
          "activeMembers"
        ]
      },
      "ProjectDetailDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "startDate": {
            "type": "string",
            "nullable": true
          },
          "endDate": {
            "type": "string",
            "nullable": true
          },
          "budget": {
            "type": "number",
            "nullable": true
          },
          "divisionCode": {
            "type": "string",
            "nullable": true
          },
          "customerName": {
            "type": "string",
            "nullable": true
          },
          "hoursLogged": {
            "type": "number"
          },
          "taskCount": {
            "type": "number"
          },
          "teamSize": {
            "type": "number"
          },
          "timelineProgressPct": {
            "type": "number",
            "nullable": true
          },
          "daysRemaining": {
            "type": "number",
            "nullable": true
          },
          "createdAt": {
            "type": "string"
          },
          "updatedAt": {
            "type": "string"
          },
          "source": {
            "type": "string",
            "enum": [
              "apergusuite",
              "rsmid"
            ]
          },
          "isEditable": {
            "type": "boolean",
            "description": "True for RSMID-native projects that can be edited"
          },
          "netsuiteSyncState": {
            "type": "string",
            "enum": [
              "SYNCHRONISED",
              "NOT_SYNCHRONISED",
              "UNKNOWN"
            ],
            "description": "Whether this project exists in NetSuite. NOT_SYNCHRONISED while the project source of truth (CR-01) is undecided — no push or pull is performed in either direction."
          },
          "netsuiteSyncLabel": {
            "type": "string",
            "description": "Badge text for the sync state, e.g. \"Not synchronised — CR-01\"."
          },
          "opportunityId": {
            "type": "string",
            "nullable": true
          },
          "managerId": {
            "type": "string",
            "nullable": true
          },
          "managerName": {
            "type": "string",
            "nullable": true
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "engagementLetterId": {
            "type": "string",
            "nullable": true
          },
          "elNumber": {
            "type": "string",
            "nullable": true
          },
          "projectCode": {
            "type": "string",
            "nullable": true,
            "description": "Server-generated Project ID (PRJ-2026-0001); null for legacy and AperguSuite rows"
          },
          "assignmentId": {
            "type": "string",
            "nullable": true
          },
          "assignmentCode": {
            "type": "string",
            "nullable": true
          },
          "transactionDate": {
            "type": "string",
            "nullable": true,
            "description": "YYYY-MM-DD"
          },
          "projectType": {
            "type": "string",
            "nullable": true,
            "enum": [
              "Audit",
              "Non-Audit"
            ]
          },
          "releaseChecklistType": {
            "type": "string",
            "nullable": true,
            "enum": [
              "Audit",
              "Assurance Non-Audit (Non-Asurans)",
              "Non-Audit"
            ]
          },
          "billingMethod": {
            "type": "string",
            "nullable": true,
            "enum": [
              "FIXED_FEE",
              "CHARGE_BASED"
            ]
          },
          "confirmedFee": {
            "type": "number",
            "nullable": true
          },
          "approvedById": {
            "type": "string",
            "nullable": true
          },
          "approvedByName": {
            "type": "string",
            "nullable": true
          },
          "approvedAt": {
            "type": "string",
            "nullable": true,
            "description": "ISO timestamp"
          },
          "netsuiteProjectId": {
            "type": "string",
            "nullable": true
          },
          "netsuiteSync": {
            "type": "object",
            "description": "Full NetSuite synchronisation state with the reason and the decision blocking it (CR-01). An activation that performed no sync reports NOT_SYNCHRONISED here — never success."
          },
          "currency": {
            "type": "string"
          },
          "entityCode": {
            "type": "string",
            "nullable": true
          },
          "lobCode": {
            "type": "string",
            "nullable": true
          },
          "classCode": {
            "type": "string",
            "nullable": true
          },
          "locationCode": {
            "type": "string",
            "nullable": true
          },
          "opeAmount": {
            "type": "number",
            "nullable": true
          },
          "customerId": {
            "type": "string",
            "nullable": true,
            "description": "The Customer (CrmCustomerMaster id); server-derived from the EL."
          },
          "customer": {
            "type": "object",
            "nullable": true,
            "description": "The live Customer, for the link only — display data comes from `snapshot`."
          },
          "snapshot": {
            "type": "object",
            "nullable": true,
            "description": "Company snapshot captured at create; null on legacy rows (fall back to customerName)."
          },
          "proposalId": {
            "type": "string",
            "nullable": true,
            "description": "CRM proposal of the EL this project was created from."
          },
          "tasks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProjectTaskDto"
            }
          },
          "team": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProjectTeamMemberDto"
            }
          },
          "metrics": {
            "$ref": "#/components/schemas/ProjectMetricsDto"
          }
        },
        "required": [
          "id",
          "name",
          "status",
          "hoursLogged",
          "taskCount",
          "teamSize",
          "createdAt",
          "updatedAt",
          "tasks",
          "team",
          "metrics"
        ]
      },
      "UpdateProjectDto": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "PLANNING",
              "ACTIVE",
              "ON_HOLD",
              "COMPLETED",
              "CANCELLED"
            ]
          },
          "divisionCode": {
            "type": "string"
          },
          "customerName": {
            "type": "string",
            "description": "Rejected (400 SNAPSHOT_IMMUTABLE) once the company snapshot is captured; a legacy project may set it once."
          },
          "managerId": {
            "type": "string"
          },
          "apergusuiteOpportunityId": {
            "type": "string"
          },
          "startDate": {
            "type": "string"
          },
          "endDate": {
            "type": "string"
          },
          "budget": {
            "type": "number"
          },
          "engagementLetterId": {
            "type": "string",
            "description": "CRM engagement letter ID this project was created from"
          },
          "elNumber": {
            "type": "string",
            "description": "EL / SPK reference number, e.g. EL-2026-0231"
          },
          "assignmentId": {
            "type": "string",
            "description": "CRM assignment ID this project was created from"
          },
          "assignmentCode": {
            "type": "string",
            "deprecated": true,
            "readOnly": true,
            "description": "IGNORED on write (W-18). The Assignment Code is server-generated on the CRM assignment and mirrored here from `assignmentId`. Sending it has no effect."
          },
          "transactionDate": {
            "type": "string",
            "description": "Date transaction — ISO date YYYY-MM-DD (defaults to today on create)"
          },
          "projectType": {
            "type": "string",
            "enum": [
              "Audit",
              "Non-Audit"
            ]
          },
          "releaseChecklistType": {
            "type": "string",
            "enum": [
              "Audit",
              "Assurance Non-Audit (Non-Asurans)",
              "Non-Audit"
            ]
          },
          "billingMethod": {
            "type": "string",
            "enum": [
              "FIXED_FEE",
              "CHARGE_BASED"
            ]
          },
          "confirmedFee": {
            "type": "number",
            "description": "Confirmed fee in IDR (read-only in the UI when the project came from an EL)"
          },
          "entityCode": {
            "type": "string",
            "description": "Subsidiary code — CRM entity master"
          },
          "lobCode": {
            "type": "string",
            "description": "Line of business code — CRM LOB master"
          },
          "classCode": {
            "type": "string",
            "description": "Class code — CRM reference master type CLASS"
          },
          "locationCode": {
            "type": "string",
            "description": "Location code — CRM reference master type LOCATION"
          },
          "opeAmount": {
            "type": "number",
            "description": "Amount OPE in IDR — frozen from the assignment"
          }
        }
      },
      "AddProjectMemberDto": {
        "type": "object",
        "properties": {
          "userId": {
            "type": "string",
            "description": "RSMID user ID — provide userId OR email"
          },
          "email": {
            "type": "string",
            "description": "User email — alternative to userId"
          },
          "role": {
            "type": "string",
            "enum": [
              "manager",
              "lead",
              "member"
            ]
          }
        }
      },
      "ProjectTemplateTaskDto": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 2
          },
          "description": {
            "type": "string"
          },
          "priority": {
            "type": "number",
            "description": "0=normal, 1=high, 2=urgent"
          },
          "estimatedHours": {
            "type": "number"
          },
          "offsetDays": {
            "type": "number",
            "description": "Days after project start that this task is due."
          },
          "sortOrder": {
            "type": "number"
          }
        },
        "required": [
          "name"
        ]
      },
      "CreateProjectTemplateDto": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 2,
            "maxLength": 200,
            "description": "Template Name (stored as the reference label)."
          },
          "assignmentType": {
            "type": "string",
            "maxLength": 40,
            "description": "ASSIGNMENT_TYPE reference code (AUDIT / TAX / CONSULTING)."
          },
          "rows": {
            "description": "Tasks / Milestones grid — section rows and activity rows.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BudgetHoursRowDto"
            }
          },
          "active": {
            "type": "boolean",
            "default": true
          }
        },
        "required": [
          "name",
          "assignmentType",
          "rows"
        ]
      },
      "UpdateProjectTemplateDto": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 2,
            "maxLength": 200
          },
          "assignmentType": {
            "type": "string",
            "maxLength": 40
          },
          "rows": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BudgetHoursRowDto"
            }
          },
          "active": {
            "type": "boolean"
          },
          "expectedVersion": {
            "type": "number",
            "minimum": 1
          }
        }
      },
      "InstantiateTemplateDto": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "description": "Name for the new project."
          },
          "startDate": {
            "type": "string",
            "description": "Project start date — task due dates derive from this."
          },
          "customerName": {
            "type": "string"
          },
          "divisionCode": {
            "type": "string"
          },
          "managerId": {
            "type": "string",
            "description": "RSMID user id of the project manager."
          },
          "budget": {
            "type": "number"
          },
          "durationDays": {
            "type": "number",
            "description": "Overrides the template default duration."
          }
        },
        "required": [
          "name",
          "startDate"
        ]
      },
      "NetsuiteSavedSearchInputDto": {
        "type": "object",
        "properties": {
          "searchId": {
            "type": "string",
            "example": "2289",
            "description": "Saved search internal id (digits) or script id (customsearch…)."
          },
          "title": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120,
            "example": "NetSuite Saved Search 2289"
          },
          "description": {
            "type": "string",
            "nullable": true,
            "maxLength": 500
          }
        },
        "required": [
          "searchId",
          "title"
        ]
      },
      "UpdateNetsuiteWebQueryConfigDto": {
        "type": "object",
        "properties": {
          "uiBaseUrl": {
            "type": "string",
            "nullable": true,
            "example": "https://td3074692.app.netsuite.com",
            "description": "NetSuite UI host override; null/empty = derive from NETSUITE_ACCOUNT_ID."
          },
          "searches": {
            "maxItems": 20,
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/NetsuiteSavedSearchInputDto"
            }
          },
          "defaultSearchId": {
            "type": "string",
            "nullable": true,
            "example": "2289",
            "description": "One of searches[].searchId; null = first row."
          },
          "expectedVersion": {
            "type": "number",
            "description": "configVersion the client read (optimistic lock; 0 = defaults)."
          }
        },
        "required": [
          "searches"
        ]
      },
      "CfbcEntryInputDto": {
        "type": "object",
        "properties": {
          "projectId": {
            "type": "string"
          },
          "feeRevenue": {
            "description": "Fee Revenue (billing timing), 12 values, index 0 = Jan.",
            "type": "array",
            "items": {
              "type": "number"
            }
          },
          "cashFeeRevenue": {
            "description": "Cash Fee Revenue (collection timing), 12 values, index 0 = Jan.",
            "type": "array",
            "items": {
              "type": "number"
            }
          },
          "expectedVersion": {
            "type": "number",
            "description": "rowVersion the client read; 0 = \"I saw no entry\"."
          }
        },
        "required": [
          "projectId",
          "feeRevenue",
          "cashFeeRevenue"
        ]
      },
      "SaveCfbcEntriesDto": {
        "type": "object",
        "properties": {
          "year": {
            "type": "number"
          },
          "divisionId": {
            "type": "string",
            "nullable": true,
            "description": "Only selects the division view returned after the save."
          },
          "entries": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CfbcEntryInputDto"
            }
          }
        },
        "required": [
          "year",
          "entries"
        ]
      },
      "CreateNsOpportunityDto": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string",
            "example": "RSM Audit Engagement — PT Indofood FY2026"
          },
          "clientName": {
            "type": "string",
            "example": "PT Indofood Sukses Makmur Tbk"
          },
          "clientEmail": {
            "type": "string"
          },
          "clientPhone": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "stage": {
            "type": "string",
            "enum": [
              "LEAD",
              "QUALIFICATION",
              "PROPOSAL",
              "NEGOTIATION",
              "WON",
              "LOST"
            ],
            "default": "LEAD"
          },
          "probability": {
            "type": "number",
            "minimum": 0,
            "maximum": 100
          },
          "estimatedValue": {
            "type": "number",
            "example": 500000000
          },
          "currency": {
            "type": "string",
            "default": "IDR"
          },
          "divisionId": {
            "type": "string"
          },
          "serviceLine": {
            "type": "string",
            "enum": [
              "AUDIT_ASSURANCE",
              "TAX",
              "ADVISORY",
              "TRANSACTION_SERVICES"
            ]
          },
          "expectedCloseDate": {
            "type": "string",
            "example": "2026-09-30"
          },
          "notes": {
            "type": "string"
          },
          "netsuiteClientId": {
            "type": "string",
            "description": "NetSuite customer internalId this opportunity is for."
          },
          "pushNow": {
            "type": "boolean",
            "description": "Save and immediately push to NetSuite."
          }
        },
        "required": [
          "title",
          "clientName"
        ]
      },
      "UpdateNsOpportunityDto": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string"
          },
          "clientName": {
            "type": "string"
          },
          "clientEmail": {
            "type": "string"
          },
          "clientPhone": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "stage": {
            "type": "string",
            "enum": [
              "LEAD",
              "QUALIFICATION",
              "PROPOSAL",
              "NEGOTIATION",
              "WON",
              "LOST"
            ]
          },
          "probability": {
            "type": "number"
          },
          "estimatedValue": {
            "type": "number"
          },
          "currency": {
            "type": "string"
          },
          "divisionId": {
            "type": "string"
          },
          "serviceLine": {
            "type": "string",
            "enum": [
              "AUDIT_ASSURANCE",
              "TAX",
              "ADVISORY",
              "TRANSACTION_SERVICES"
            ]
          },
          "expectedCloseDate": {
            "type": "string"
          },
          "notes": {
            "type": "string"
          },
          "netsuiteClientId": {
            "type": "string"
          }
        }
      },
      "EstimateLineDto": {
        "type": "object",
        "properties": {
          "description": {
            "type": "string"
          },
          "quantity": {
            "type": "number"
          },
          "rate": {
            "type": "number"
          },
          "amount": {
            "type": "number"
          },
          "netsuiteItemId": {
            "type": "string",
            "description": "NetSuite item internalId (required to push)."
          }
        },
        "required": [
          "quantity",
          "amount"
        ]
      },
      "CreateNsEstimateDto": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string"
          },
          "clientName": {
            "type": "string"
          },
          "netsuiteClientId": {
            "type": "string"
          },
          "opportunityId": {
            "type": "string"
          },
          "estimateDate": {
            "type": "string"
          },
          "expiryDate": {
            "type": "string"
          },
          "currency": {
            "type": "string"
          },
          "memo": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "DRAFT",
              "SENT",
              "ACCEPTED",
              "DECLINED"
            ]
          },
          "lines": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EstimateLineDto"
            }
          },
          "pushNow": {
            "type": "boolean"
          }
        },
        "required": [
          "title",
          "clientName",
          "lines"
        ]
      },
      "UpdateNsEstimateDto": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string"
          },
          "clientName": {
            "type": "string"
          },
          "netsuiteClientId": {
            "type": "string"
          },
          "opportunityId": {
            "type": "string"
          },
          "estimateDate": {
            "type": "string"
          },
          "expiryDate": {
            "type": "string"
          },
          "currency": {
            "type": "string"
          },
          "memo": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "DRAFT",
              "SENT",
              "ACCEPTED",
              "DECLINED"
            ]
          },
          "lines": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EstimateLineDto"
            }
          }
        }
      },
      "SalesOrderLineDto": {
        "type": "object",
        "properties": {
          "description": {
            "type": "string"
          },
          "quantity": {
            "type": "number"
          },
          "rate": {
            "type": "number"
          },
          "amount": {
            "type": "number"
          },
          "netsuiteItemId": {
            "type": "string",
            "description": "NetSuite item internalId (required to push)."
          }
        },
        "required": [
          "quantity",
          "amount"
        ]
      },
      "CreateNsSalesOrderDto": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string"
          },
          "clientName": {
            "type": "string"
          },
          "netsuiteClientId": {
            "type": "string"
          },
          "estimateId": {
            "type": "string",
            "description": "Originating NsEstimate.id, when raised from an accepted EL."
          },
          "spkNumber": {
            "type": "string",
            "description": "Client's own contract/SPK reference number."
          },
          "orderDate": {
            "type": "string"
          },
          "startDate": {
            "type": "string"
          },
          "endDate": {
            "type": "string"
          },
          "currency": {
            "type": "string"
          },
          "memo": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "DRAFT",
              "PENDING_APPROVAL",
              "APPROVED",
              "REJECTED",
              "CLOSED"
            ]
          },
          "lines": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SalesOrderLineDto"
            }
          },
          "pushNow": {
            "type": "boolean"
          }
        },
        "required": [
          "title",
          "clientName",
          "lines"
        ]
      },
      "CreateFromEstimateDto": {
        "type": "object",
        "properties": {
          "estimateId": {
            "type": "string"
          },
          "spkNumber": {
            "type": "string"
          },
          "orderDate": {
            "type": "string"
          },
          "pushNow": {
            "type": "boolean"
          }
        },
        "required": [
          "estimateId"
        ]
      },
      "UpdateNsSalesOrderDto": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string"
          },
          "clientName": {
            "type": "string"
          },
          "netsuiteClientId": {
            "type": "string"
          },
          "estimateId": {
            "type": "string"
          },
          "spkNumber": {
            "type": "string"
          },
          "orderDate": {
            "type": "string"
          },
          "startDate": {
            "type": "string"
          },
          "endDate": {
            "type": "string"
          },
          "currency": {
            "type": "string"
          },
          "memo": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "DRAFT",
              "PENDING_APPROVAL",
              "APPROVED",
              "REJECTED",
              "CLOSED"
            ]
          },
          "lines": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SalesOrderLineDto"
            }
          }
        }
      },
      "SurveyResponseDto": {
        "type": "object",
        "properties": {
          "category": {
            "type": "string",
            "enum": [
              "communication",
              "quality",
              "timeliness",
              "value",
              "relationship"
            ]
          },
          "rating": {
            "type": "number",
            "minimum": 1,
            "maximum": 5
          },
          "comments": {
            "type": "string"
          }
        },
        "required": [
          "category",
          "rating"
        ]
      },
      "NetsuiteLaunchLogDto": {
        "type": "object",
        "properties": {
          "launchType": {
            "type": "string",
            "enum": [
              "netsuite_sso_launch",
              "employee_center_launch",
              "hrms_launch"
            ]
          },
          "targetUrl": {
            "type": "string"
          }
        },
        "required": [
          "launchType"
        ]
      },
      "DivisionDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "code": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "entityCode": {
            "type": "string"
          },
          "entityName": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "code",
          "name"
        ]
      },
      "BudgetProjectOptionDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "source": {
            "type": "string"
          },
          "divisionCode": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "hasBudget": {
            "type": "boolean"
          }
        },
        "required": [
          "id",
          "name",
          "source"
        ]
      },
      "BudgetRevisionItemDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "budgetPlanId": {
            "type": "string"
          },
          "revisionType": {
            "type": "string"
          },
          "targetField": {
            "type": "string"
          },
          "currentValue": {
            "type": "number"
          },
          "proposedValue": {
            "type": "number"
          },
          "revisedPayload": {
            "type": "object"
          },
          "reason": {
            "type": "string"
          },
          "isUrgent": {
            "type": "boolean"
          },
          "status": {
            "type": "string"
          },
          "requesterId": {
            "type": "string"
          },
          "requesterName": {
            "type": "string"
          },
          "reviewerId": {
            "type": "string"
          },
          "reviewerName": {
            "type": "string"
          },
          "reviewNotes": {
            "type": "string"
          },
          "reviewedAt": {
            "type": "string"
          },
          "createdAt": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "budgetPlanId",
          "revisionType",
          "reason",
          "isUrgent",
          "status",
          "requesterId",
          "requesterName",
          "createdAt"
        ]
      },
      "ReviewRevisionDto": {
        "type": "object",
        "properties": {
          "reviewNotes": {
            "type": "string"
          }
        }
      },
      "BudgetActualsDto": {
        "type": "object",
        "properties": {
          "feeRevenueActual": {
            "type": "number"
          },
          "confirmFeeActual": {
            "type": "number"
          },
          "collectionActual": {
            "type": "number"
          },
          "manpowerActual": {
            "type": "number"
          },
          "opexActual": {
            "type": "number"
          },
          "totalRevenueActual": {
            "type": "number"
          },
          "totalCostActual": {
            "type": "number"
          }
        },
        "required": [
          "feeRevenueActual",
          "confirmFeeActual",
          "collectionActual",
          "manpowerActual",
          "opexActual",
          "totalRevenueActual",
          "totalCostActual"
        ]
      },
      "BudgetListItemDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "budgetType": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "fiscalYear": {
            "type": "string"
          },
          "fiscalPeriodId": {
            "type": "string"
          },
          "divisionId": {
            "type": "string"
          },
          "divisionCode": {
            "type": "string"
          },
          "divisionName": {
            "type": "string"
          },
          "entityCode": {
            "type": "string"
          },
          "rsmidProjectId": {
            "type": "string"
          },
          "projectName": {
            "type": "string"
          },
          "feeRevenueTarget": {
            "type": "number"
          },
          "confirmFeeTarget": {
            "type": "number"
          },
          "collectionTarget": {
            "type": "number"
          },
          "manpowerBudget": {
            "type": "number"
          },
          "opexBudget": {
            "type": "number"
          },
          "totalRevenueTarget": {
            "type": "number"
          },
          "totalCostBudget": {
            "type": "number"
          },
          "actuals": {
            "$ref": "#/components/schemas/BudgetActualsDto"
          },
          "revenueUtilizationPct": {
            "type": "number"
          },
          "costUtilizationPct": {
            "type": "number"
          },
          "healthStatus": {
            "type": "string"
          },
          "currency": {
            "type": "string"
          },
          "approvedById": {
            "type": "string"
          },
          "approvedAt": {
            "type": "string"
          },
          "notes": {
            "type": "string"
          },
          "createdAt": {
            "type": "string"
          },
          "updatedAt": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "budgetType",
          "status",
          "fiscalYear",
          "fiscalPeriodId",
          "divisionId",
          "divisionCode",
          "divisionName",
          "feeRevenueTarget",
          "confirmFeeTarget",
          "collectionTarget",
          "manpowerBudget",
          "opexBudget",
          "totalRevenueTarget",
          "totalCostBudget",
          "actuals",
          "revenueUtilizationPct",
          "costUtilizationPct",
          "healthStatus",
          "currency",
          "createdAt",
          "updatedAt"
        ]
      },
      "CreateBudgetDto": {
        "type": "object",
        "properties": {
          "budgetType": {
            "type": "string",
            "enum": [
              "ORG_BUDGET",
              "PROJECT_BUDGET"
            ]
          },
          "divisionId": {
            "type": "string"
          },
          "fiscalPeriodId": {
            "type": "string"
          },
          "entityId": {
            "type": "string"
          },
          "rsmidProjectId": {
            "type": "string"
          },
          "feeRevenueTarget": {
            "type": "number"
          },
          "confirmFeeTarget": {
            "type": "number"
          },
          "collectionTarget": {
            "type": "number"
          },
          "manpowerBudget": {
            "type": "number"
          },
          "opexBudget": {
            "type": "number"
          },
          "notes": {
            "type": "string"
          }
        },
        "required": [
          "budgetType",
          "divisionId",
          "fiscalPeriodId",
          "feeRevenueTarget",
          "confirmFeeTarget",
          "collectionTarget"
        ]
      },
      "MonthlyTargetDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "budgetPlanId": {
            "type": "string"
          },
          "month": {
            "type": "number"
          },
          "monthLabel": {
            "type": "string"
          },
          "feeRevenueTarget": {
            "type": "number"
          },
          "confirmFeeTarget": {
            "type": "number"
          },
          "collectionTarget": {
            "type": "number"
          },
          "manpowerBudget": {
            "type": "number"
          },
          "opexBudget": {
            "type": "number"
          },
          "feeRevenueActual": {
            "type": "number"
          },
          "confirmFeeActual": {
            "type": "number"
          },
          "collectionActual": {
            "type": "number"
          },
          "manpowerActual": {
            "type": "number"
          },
          "opexActual": {
            "type": "number"
          },
          "revenueVariance": {
            "type": "number"
          },
          "costVariance": {
            "type": "number"
          },
          "periodStatus": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "budgetPlanId",
          "month",
          "monthLabel",
          "feeRevenueTarget",
          "confirmFeeTarget",
          "collectionTarget",
          "manpowerBudget",
          "opexBudget",
          "feeRevenueActual",
          "confirmFeeActual",
          "collectionActual",
          "manpowerActual",
          "opexActual",
          "revenueVariance",
          "costVariance",
          "periodStatus"
        ]
      },
      "BudgetDetailDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "budgetType": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "fiscalYear": {
            "type": "string"
          },
          "fiscalPeriodId": {
            "type": "string"
          },
          "divisionId": {
            "type": "string"
          },
          "divisionCode": {
            "type": "string"
          },
          "divisionName": {
            "type": "string"
          },
          "entityCode": {
            "type": "string"
          },
          "rsmidProjectId": {
            "type": "string"
          },
          "projectName": {
            "type": "string"
          },
          "feeRevenueTarget": {
            "type": "number"
          },
          "confirmFeeTarget": {
            "type": "number"
          },
          "collectionTarget": {
            "type": "number"
          },
          "manpowerBudget": {
            "type": "number"
          },
          "opexBudget": {
            "type": "number"
          },
          "totalRevenueTarget": {
            "type": "number"
          },
          "totalCostBudget": {
            "type": "number"
          },
          "actuals": {
            "$ref": "#/components/schemas/BudgetActualsDto"
          },
          "revenueUtilizationPct": {
            "type": "number"
          },
          "costUtilizationPct": {
            "type": "number"
          },
          "healthStatus": {
            "type": "string"
          },
          "currency": {
            "type": "string"
          },
          "approvedById": {
            "type": "string"
          },
          "approvedAt": {
            "type": "string"
          },
          "notes": {
            "type": "string"
          },
          "createdAt": {
            "type": "string"
          },
          "updatedAt": {
            "type": "string"
          },
          "monthlyTargets": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MonthlyTargetDto"
            }
          },
          "revisions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BudgetRevisionItemDto"
            }
          }
        },
        "required": [
          "id",
          "budgetType",
          "status",
          "fiscalYear",
          "fiscalPeriodId",
          "divisionId",
          "divisionCode",
          "divisionName",
          "feeRevenueTarget",
          "confirmFeeTarget",
          "collectionTarget",
          "manpowerBudget",
          "opexBudget",
          "totalRevenueTarget",
          "totalCostBudget",
          "actuals",
          "revenueUtilizationPct",
          "costUtilizationPct",
          "healthStatus",
          "currency",
          "createdAt",
          "updatedAt",
          "monthlyTargets",
          "revisions"
        ]
      },
      "UpdateBudgetDto": {
        "type": "object",
        "properties": {
          "feeRevenueTarget": {
            "type": "number"
          },
          "confirmFeeTarget": {
            "type": "number"
          },
          "collectionTarget": {
            "type": "number"
          },
          "manpowerBudget": {
            "type": "number"
          },
          "opexBudget": {
            "type": "number"
          },
          "status": {
            "type": "string",
            "enum": [
              "DRAFT",
              "ACTIVE",
              "LOCKED",
              "ARCHIVED"
            ]
          },
          "notes": {
            "type": "string"
          }
        }
      },
      "MonthlyTargetInputDto": {
        "type": "object",
        "properties": {
          "month": {
            "type": "number",
            "minimum": 1,
            "maximum": 12
          },
          "feeRevenueTarget": {
            "type": "number"
          },
          "confirmFeeTarget": {
            "type": "number"
          },
          "collectionTarget": {
            "type": "number"
          },
          "manpowerBudget": {
            "type": "number"
          },
          "opexBudget": {
            "type": "number"
          }
        },
        "required": [
          "month",
          "feeRevenueTarget",
          "confirmFeeTarget",
          "collectionTarget"
        ]
      },
      "UpdateMonthlyTargetsDto": {
        "type": "object",
        "properties": {
          "targets": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MonthlyTargetInputDto"
            }
          }
        },
        "required": [
          "targets"
        ]
      },
      "SubmitRevisionDto": {
        "type": "object",
        "properties": {
          "targetField": {
            "type": "string"
          },
          "currentValue": {
            "type": "number"
          },
          "proposedValue": {
            "type": "number"
          },
          "revisedPayload": {
            "type": "object"
          },
          "reason": {
            "type": "string"
          },
          "isUrgent": {
            "type": "boolean"
          }
        },
        "required": [
          "reason"
        ]
      },
      "PushBudgetDto": {
        "type": "object",
        "properties": {
          "subsidiaryId": {
            "type": "string"
          },
          "yearId": {
            "type": "string"
          },
          "departmentId": {
            "type": "string"
          }
        },
        "required": [
          "subsidiaryId",
          "yearId"
        ]
      },
      "BudgetHoursActivityDto": {
        "type": "object",
        "properties": {
          "key": {
            "type": "string"
          },
          "seq": {
            "type": "number"
          },
          "title": {
            "type": "string"
          },
          "hours": {
            "type": "object",
            "description": "memberKey → budgeted hours"
          }
        },
        "required": [
          "key",
          "seq",
          "title",
          "hours"
        ]
      },
      "BudgetHoursGroupDto": {
        "type": "object",
        "properties": {
          "key": {
            "type": "string"
          },
          "seq": {
            "type": "number"
          },
          "title": {
            "type": "string"
          },
          "activities": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BudgetHoursActivityDto"
            }
          }
        },
        "required": [
          "key",
          "seq",
          "title",
          "activities"
        ]
      },
      "CreateBudgetTemplateDto": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "lob": {
            "type": "string",
            "description": "Audit | Non-Audit"
          },
          "description": {
            "type": "string"
          },
          "groups": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BudgetHoursGroupDto"
            }
          }
        },
        "required": [
          "name",
          "lob",
          "groups"
        ]
      },
      "UpdateBudgetTemplateDto": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "lob": {
            "type": "string",
            "description": "Audit | Non-Audit"
          },
          "description": {
            "type": "string"
          },
          "groups": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BudgetHoursGroupDto"
            }
          },
          "active": {
            "type": "boolean",
            "description": "false hides the template from the catalog (non-built-in only)"
          }
        }
      },
      "CloneBudgetTemplateDto": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          }
        }
      },
      "BudgetHoursMemberDto": {
        "type": "object",
        "properties": {
          "key": {
            "type": "string"
          },
          "name": {
            "type": "string",
            "description": "Optional person name. Review 2026-08-11: budget hours are budgeted by POSITION (level + role) — names are no longer collected or displayed; kept for legacy plans."
          },
          "resourceId": {
            "type": "string",
            "description": "Master Employee id assigned to this position"
          },
          "resourceName": {
            "type": "string",
            "description": "Master Employee display name (snapshot)"
          },
          "level": {
            "type": "string",
            "description": "Rate-card level code, e.g. P / SM3 / AM1"
          },
          "role": {
            "type": "string",
            "description": "Engagement role, e.g. Engagement Partner / Member"
          },
          "rate": {
            "type": "number",
            "description": "Hourly charge-out rate in USD"
          }
        },
        "required": [
          "key",
          "level",
          "role",
          "rate"
        ]
      },
      "CreateBudgetHoursPlanDto": {
        "type": "object",
        "properties": {
          "clientName": {
            "type": "string"
          },
          "lob": {
            "type": "string",
            "description": "Line of business: Audit | Non-Audit"
          },
          "division": {
            "type": "string",
            "deprecated": true,
            "description": "Legacy alias of lob"
          },
          "templateKey": {
            "type": "string",
            "description": "Catalog template to seed phases/activities from (see GET /budget-hours/template)"
          },
          "assignment": {
            "type": "string",
            "description": "Type of assignment / engagement"
          },
          "periodStart": {
            "type": "string",
            "description": "ISO date"
          },
          "periodEnd": {
            "type": "string",
            "description": "ISO date"
          },
          "proposedFee": {
            "type": "number",
            "description": "Proposed fee in IDR"
          },
          "exchangeRate": {
            "type": "number",
            "description": "USD→IDR conversion for the rate card"
          },
          "notes": {
            "type": "string"
          },
          "members": {
            "description": "Defaults to empty",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BudgetHoursMemberDto"
            }
          },
          "groups": {
            "description": "Defaults to the firm activity template with zero hours",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BudgetHoursGroupDto"
            }
          }
        },
        "required": [
          "clientName"
        ]
      },
      "UpdateBudgetHoursPlanDto": {
        "type": "object",
        "properties": {
          "clientName": {
            "type": "string"
          },
          "lob": {
            "type": "string",
            "description": "Line of business: Audit | Non-Audit"
          },
          "division": {
            "type": "string",
            "deprecated": true,
            "description": "Legacy alias of lob"
          },
          "assignment": {
            "type": "string"
          },
          "periodStart": {
            "type": "string"
          },
          "periodEnd": {
            "type": "string"
          },
          "proposedFee": {
            "type": "number"
          },
          "exchangeRate": {
            "type": "number"
          },
          "notes": {
            "type": "string"
          },
          "members": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BudgetHoursMemberDto"
            }
          },
          "groups": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BudgetHoursGroupDto"
            }
          },
          "projectStructure": {
            "type": "object",
            "description": "\"Form Project Structure\" document (workbook sheet 3): EQR, deduction items, terms of payment, shared works, subcontracting vendors, RSM PIC, notes."
          }
        }
      },
      "ReviewBudgetHoursPlanDto": {
        "type": "object",
        "properties": {
          "note": {
            "type": "string"
          }
        }
      },
      "ExpenseEmployeeDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "displayName": {
            "type": "string"
          },
          "email": {
            "type": "string"
          },
          "divisionCode": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "displayName",
          "email"
        ]
      },
      "ExpenseListItemDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "totalAmount": {
            "type": "number"
          },
          "currency": {
            "type": "string"
          },
          "employee": {
            "$ref": "#/components/schemas/ExpenseEmployeeDto"
          },
          "rsmidProjectId": {
            "type": "string"
          },
          "rsmidProjectName": {
            "type": "string"
          },
          "apergusuiteProjectId": {
            "type": "string"
          },
          "itemCount": {
            "type": "number"
          },
          "attachmentCount": {
            "type": "number"
          },
          "submittedAt": {
            "type": "string"
          },
          "managerApprovedAt": {
            "type": "string"
          },
          "financeApprovedAt": {
            "type": "string"
          },
          "paidAt": {
            "type": "string"
          },
          "rejectionReason": {
            "type": "string"
          },
          "createdAt": {
            "type": "string"
          },
          "updatedAt": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "title",
          "status",
          "totalAmount",
          "currency",
          "employee",
          "itemCount",
          "attachmentCount",
          "createdAt",
          "updatedAt"
        ]
      },
      "ExpenseItemInputDto": {
        "type": "object",
        "properties": {
          "category": {
            "type": "string",
            "enum": [
              "TRAVEL",
              "MEAL",
              "ACCOMMODATION",
              "TRANSPORTATION",
              "ENTERTAINMENT",
              "COMMUNICATION",
              "OFFICE_SUPPLIES",
              "OTHER"
            ]
          },
          "expenseDate": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "amount": {
            "type": "number"
          },
          "currency": {
            "type": "string"
          },
          "notes": {
            "type": "string"
          }
        },
        "required": [
          "category",
          "expenseDate",
          "description",
          "amount"
        ]
      },
      "CreateExpenseDto": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string"
          },
          "rsmidProjectId": {
            "type": "string"
          },
          "apergusuiteProjectId": {
            "type": "string"
          },
          "currency": {
            "type": "string"
          },
          "notes": {
            "type": "string"
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExpenseItemInputDto"
            }
          }
        },
        "required": [
          "title"
        ]
      },
      "ExpenseItemDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "expenseReportId": {
            "type": "string"
          },
          "category": {
            "type": "string"
          },
          "expenseDate": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "amount": {
            "type": "number"
          },
          "currency": {
            "type": "string"
          },
          "notes": {
            "type": "string"
          },
          "createdAt": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "expenseReportId",
          "category",
          "expenseDate",
          "description",
          "amount",
          "currency",
          "createdAt"
        ]
      },
      "ExpenseAttachmentDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "expenseReportId": {
            "type": "string"
          },
          "expenseItemId": {
            "type": "string"
          },
          "fileName": {
            "type": "string"
          },
          "mimeType": {
            "type": "string"
          },
          "fileSize": {
            "type": "number"
          },
          "publicUrl": {
            "type": "string"
          },
          "uploadedById": {
            "type": "string"
          },
          "uploadedByName": {
            "type": "string"
          },
          "createdAt": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "expenseReportId",
          "fileName",
          "mimeType",
          "fileSize",
          "uploadedById",
          "uploadedByName",
          "createdAt"
        ]
      },
      "ExpenseDetailDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "totalAmount": {
            "type": "number"
          },
          "currency": {
            "type": "string"
          },
          "employee": {
            "$ref": "#/components/schemas/ExpenseEmployeeDto"
          },
          "rsmidProjectId": {
            "type": "string"
          },
          "rsmidProjectName": {
            "type": "string"
          },
          "apergusuiteProjectId": {
            "type": "string"
          },
          "itemCount": {
            "type": "number"
          },
          "attachmentCount": {
            "type": "number"
          },
          "submittedAt": {
            "type": "string"
          },
          "managerApprovedAt": {
            "type": "string"
          },
          "financeApprovedAt": {
            "type": "string"
          },
          "paidAt": {
            "type": "string"
          },
          "rejectionReason": {
            "type": "string"
          },
          "createdAt": {
            "type": "string"
          },
          "updatedAt": {
            "type": "string"
          },
          "notes": {
            "type": "string"
          },
          "managerApproverName": {
            "type": "string"
          },
          "financeApproverName": {
            "type": "string"
          },
          "paidByName": {
            "type": "string"
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExpenseItemDto"
            }
          },
          "attachments": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExpenseAttachmentDto"
            }
          }
        },
        "required": [
          "id",
          "title",
          "status",
          "totalAmount",
          "currency",
          "employee",
          "itemCount",
          "attachmentCount",
          "createdAt",
          "updatedAt",
          "items",
          "attachments"
        ]
      },
      "UpdateExpenseDto": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string"
          },
          "rsmidProjectId": {
            "type": "string"
          },
          "apergusuiteProjectId": {
            "type": "string"
          },
          "currency": {
            "type": "string"
          },
          "notes": {
            "type": "string"
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExpenseItemInputDto"
            }
          }
        }
      },
      "ReviewExpenseDto": {
        "type": "object",
        "properties": {
          "notes": {
            "type": "string"
          }
        }
      },
      "RejectExpenseDto": {
        "type": "object",
        "properties": {
          "reason": {
            "type": "string"
          },
          "notes": {
            "type": "string"
          }
        },
        "required": [
          "reason"
        ]
      },
      "CreateApprovalProcessDto": {
        "type": "object",
        "properties": {}
      },
      "DecisionDto": {
        "type": "object",
        "properties": {}
      },
      "CommentDto": {
        "type": "object",
        "properties": {}
      },
      "AttachmentDto": {
        "type": "object",
        "properties": {}
      },
      "CreateAnnouncementDto": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string"
          },
          "body": {
            "type": "string"
          },
          "category": {
            "type": "string",
            "default": "general"
          },
          "pinned": {
            "type": "boolean",
            "default": false
          },
          "isPublished": {
            "type": "boolean",
            "default": true
          }
        },
        "required": [
          "title",
          "body"
        ]
      },
      "CreateEventDto": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "location": {
            "type": "string"
          },
          "startsAt": {
            "type": "string",
            "description": "ISO 8601 start"
          },
          "endsAt": {
            "type": "string",
            "description": "ISO 8601 end"
          },
          "allDay": {
            "type": "boolean",
            "default": false
          },
          "category": {
            "type": "string",
            "default": "general"
          }
        },
        "required": [
          "title",
          "startsAt"
        ]
      },
      "CreateHrDocumentDto": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "category": {
            "type": "string",
            "default": "general"
          },
          "fileUrl": {
            "type": "string",
            "description": "URL/path to the document"
          },
          "version": {
            "type": "string"
          },
          "isPublished": {
            "type": "boolean",
            "default": true
          }
        },
        "required": [
          "title",
          "fileUrl"
        ]
      },
      "CreateKnowledgeArticleDto": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string",
            "minLength": 2
          },
          "category": {
            "type": "string",
            "minLength": 2
          },
          "body": {
            "type": "string"
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "title",
          "category",
          "body"
        ]
      },
      "UpdateKnowledgeArticleDto": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string",
            "minLength": 2
          },
          "category": {
            "type": "string",
            "minLength": 2
          },
          "body": {
            "type": "string"
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "CreateGalleryAlbumDto": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string",
            "minLength": 2
          },
          "description": {
            "type": "string"
          },
          "eventDate": {
            "type": "string",
            "description": "YYYY-MM-DD"
          }
        },
        "required": [
          "title"
        ]
      },
      "UpdateGalleryAlbumDto": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string",
            "minLength": 2
          },
          "description": {
            "type": "string"
          },
          "eventDate": {
            "type": "string",
            "description": "YYYY-MM-DD"
          },
          "coverImageId": {
            "type": "string",
            "description": "GalleryImage id to use as the album cover."
          }
        }
      },
      "AddGalleryImageDto": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "description": "Image URL, http(s) only (no file upload in this milestone)."
          },
          "caption": {
            "type": "string"
          },
          "sortOrder": {
            "type": "number",
            "description": "Position in the album; appended last when omitted."
          }
        },
        "required": [
          "url"
        ]
      },
      "Object": {
        "type": "object",
        "properties": {}
      },
      "CreateMeetingRoomDto": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 2
          },
          "location": {
            "type": "string"
          },
          "capacity": {
            "type": "number",
            "minimum": 1
          },
          "amenities": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "name",
          "capacity"
        ]
      },
      "UpdateMeetingRoomDto": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 2
          },
          "location": {
            "type": "string"
          },
          "capacity": {
            "type": "number",
            "minimum": 1
          },
          "amenities": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "active": {
            "type": "boolean"
          }
        }
      },
      "CreateReservationDto": {
        "type": "object",
        "properties": {
          "roomId": {
            "type": "string"
          },
          "title": {
            "type": "string",
            "minLength": 2
          },
          "description": {
            "type": "string"
          },
          "startsAt": {
            "type": "string",
            "description": "ISO datetime"
          },
          "endsAt": {
            "type": "string",
            "description": "ISO datetime"
          },
          "attendeeCount": {
            "type": "number",
            "minimum": 1
          }
        },
        "required": [
          "roomId",
          "title",
          "startsAt",
          "endsAt"
        ]
      },
      "SetFlagDto": {
        "type": "object",
        "properties": {}
      },
      "RunSnapshotDto": {
        "type": "object",
        "properties": {}
      },
      "RecordClockingDto": {
        "type": "object",
        "properties": {
          "clockCode": {
            "type": "string",
            "description": "Clock device / location code"
          }
        }
      },
      "SubmitLeaveRequestDto": {
        "type": "object",
        "properties": {
          "leaveCode": {
            "type": "string",
            "description": "Tenant-defined BIPO leave code, e.g. 'SGANL'"
          },
          "dateStart": {
            "type": "string",
            "example": "2026-09-21"
          },
          "dateEnd": {
            "type": "string",
            "example": "2026-09-23"
          },
          "halfStart": {
            "type": "string",
            "enum": [
              "AM",
              "PM"
            ],
            "description": "For day-unit (UnitType D) leave codes"
          },
          "halfEnd": {
            "type": "string",
            "enum": [
              "AM",
              "PM"
            ]
          },
          "timeStart": {
            "type": "string",
            "example": "09:00",
            "description": "For hour-unit (UnitType H) leave codes"
          },
          "timeEnd": {
            "type": "string",
            "example": "17:30"
          },
          "leaveAmount": {
            "type": "number",
            "minimum": 0
          },
          "remarks": {
            "type": "string",
            "maxLength": 500
          }
        },
        "required": [
          "leaveCode",
          "dateStart",
          "dateEnd"
        ]
      },
      "CancelLeaveRequestDto": {
        "type": "object",
        "properties": {
          "leaveCode": {
            "type": "string"
          },
          "dateStart": {
            "type": "string",
            "example": "2026-09-21"
          },
          "dateEnd": {
            "type": "string",
            "example": "2026-09-23"
          },
          "halfStart": {
            "type": "string",
            "enum": [
              "AM",
              "PM"
            ]
          },
          "halfEnd": {
            "type": "string",
            "enum": [
              "AM",
              "PM"
            ]
          },
          "leaveAmount": {
            "type": "number",
            "minimum": 0
          }
        },
        "required": [
          "leaveCode",
          "dateStart",
          "dateEnd"
        ]
      }
    }
  },
  "x-role-permissions": {
    "admin": [
      "*"
    ],
    "partner": [
      "timesheet:*",
      "expense:*",
      "budget:*",
      "opportunity:*",
      "estimate:*",
      "sales_order:*",
      "project:*",
      "survey:*",
      "report:*",
      "finance:read",
      "hr:read",
      "approval:*",
      "workplace:read",
      "knowledge:read",
      "gallery:read",
      "reservation:read",
      "reservation:create",
      "integration:read",
      "crm:*"
    ],
    "manager": [
      "timesheet:read",
      "timesheet:create",
      "timesheet:submit",
      "expense:read",
      "expense:create",
      "expense:submit",
      "opportunity:read",
      "opportunity:create",
      "opportunity:update",
      "estimate:read",
      "estimate:create",
      "estimate:update",
      "project:read",
      "survey:read",
      "survey:respond",
      "approval:read",
      "workplace:read",
      "knowledge:read",
      "gallery:read",
      "reservation:read",
      "reservation:create",
      "report:read",
      "timesheet:approve",
      "expense:approve",
      "budget:read",
      "project:create",
      "project:update",
      "project:manage",
      "opportunity:push",
      "estimate:push",
      "sales_order:read",
      "sales_order:create",
      "approval:decide",
      "integration:read",
      "crm:read",
      "crm:manage",
      "crm:lead:create",
      "crm:lead:update",
      "crm:lead:notify",
      "crm:lead:assign",
      "crm:lead:define",
      "crm:lead:qualify",
      "crm:lead:convert",
      "crm:opportunity:create",
      "crm:opportunity:update",
      "crm:opportunity:stage",
      "crm:checklist:update",
      "crm:checklist:submit",
      "crm:structure:update",
      "crm:structure:read-internal-rate",
      "crm:proposal:create",
      "crm:proposal:update",
      "crm:proposal:submit",
      "crm:proposal:send",
      "crm:el:create",
      "crm:el:update",
      "crm:el:review",
      "crm:el:send",
      "crm:el:amend",
      "crm:assignment:create",
      "crm:assignment:update",
      "crm:customer:sync",
      "crm:kam:create",
      "crm:kam:update",
      "crm:kam:submit",
      "crm:project-template:manage",
      "crm:employee:read",
      "crm:client-survey:create",
      "crm:client-survey:update",
      "crm:client-survey:send",
      "crm:client-survey:complete",
      "crm:iir:create",
      "crm:iir:update",
      "crm:iir:submit",
      "crm:release:create",
      "crm:release:update",
      "crm:release:review",
      "crm:release:coordinate",
      "crm:release:deliver",
      "crm:borrowing:request",
      "crm:borrowing:approve",
      "crm:wps:prepare",
      "crm:wps:approve",
      "crm:spb-vendor:create",
      "crm:spb-vendor:update",
      "crm:spb-vendor:submit",
      "crm:spb-vendor:approve",
      "crm:ta-settlement:create",
      "crm:ta-settlement:update",
      "crm:ta-settlement:submit",
      "crm:ta-settlement:approve",
      "crm:cc-settlement:create",
      "crm:cc-settlement:update",
      "crm:cc-settlement:submit",
      "crm:clearance:manage",
      "crm:clearance:decide",
      "crm:clearance:revise",
      "crm:cfbc:read",
      "crm:cfbc:update"
    ],
    "staff": [
      "timesheet:read",
      "timesheet:create",
      "timesheet:submit",
      "expense:read",
      "expense:create",
      "expense:submit",
      "opportunity:read",
      "opportunity:create",
      "opportunity:update",
      "estimate:read",
      "estimate:create",
      "estimate:update",
      "project:read",
      "survey:read",
      "survey:respond",
      "approval:read",
      "workplace:read",
      "knowledge:read",
      "gallery:read",
      "reservation:read",
      "reservation:create",
      "report:read",
      "crm:read",
      "crm:lead:create",
      "crm:lead:update",
      "crm:checklist:update",
      "crm:borrowing:request",
      "crm:wps:prepare",
      "crm:spb-vendor:create",
      "crm:spb-vendor:update",
      "crm:spb-vendor:submit",
      "crm:ta-settlement:create",
      "crm:ta-settlement:update",
      "crm:ta-settlement:submit",
      "crm:cc-settlement:create",
      "crm:cc-settlement:update",
      "crm:cc-settlement:submit",
      "crm:clearance:decide",
      "crm:clearance:revise"
    ],
    "hr_admin": [
      "timesheet:read",
      "timesheet:create",
      "timesheet:submit",
      "expense:read",
      "expense:create",
      "expense:submit",
      "opportunity:read",
      "opportunity:create",
      "opportunity:update",
      "estimate:read",
      "estimate:create",
      "estimate:update",
      "project:read",
      "survey:read",
      "survey:respond",
      "approval:read",
      "workplace:read",
      "knowledge:read",
      "gallery:read",
      "reservation:read",
      "reservation:create",
      "report:read",
      "hr:read",
      "hr:manage",
      "workplace:manage",
      "knowledge:manage",
      "gallery:manage",
      "reservation:manage",
      "approval:decide"
    ],
    "finance_admin": [
      "timesheet:read",
      "timesheet:create",
      "timesheet:submit",
      "expense:read",
      "expense:create",
      "expense:submit",
      "opportunity:read",
      "opportunity:create",
      "opportunity:update",
      "estimate:read",
      "estimate:create",
      "estimate:update",
      "project:read",
      "survey:read",
      "survey:respond",
      "approval:read",
      "workplace:read",
      "knowledge:read",
      "gallery:read",
      "reservation:read",
      "reservation:create",
      "report:read",
      "budget:create",
      "budget:submit",
      "budget:approve",
      "expense:approve",
      "expense:pay",
      "sales_order:read",
      "sales_order:create",
      "sales_order:push",
      "opportunity:push",
      "estimate:push",
      "report:read",
      "report:read_firmwide",
      "approval:decide",
      "integration:read",
      "integration:manage",
      "finance:read",
      "crm:read",
      "crm:manage",
      "crm:customer:sync",
      "crm:assignment:read-swa",
      "crm:structure:read-internal-rate",
      "crm:release:finance",
      "crm:ta-settlement:finance",
      "crm:mccer:read-all",
      "crm:mccer:manage",
      "crm:mccer:approve",
      "crm:cc-settlement:create",
      "crm:cc-settlement:update",
      "crm:cc-settlement:submit",
      "crm:cc-settlement:complete",
      "crm:cfbc:read",
      "crm:cfbc:update"
    ]
  }
}