diff --git a/apps/transmittal/openapi.yaml b/apps/transmittal/openapi.yaml new file mode 100644 index 0000000..58f175e --- /dev/null +++ b/apps/transmittal/openapi.yaml @@ -0,0 +1,1292 @@ +openapi: 3.0.3 + +info: + title: Transmittal Service API + version: "1.0.0" + description: | + REST API сервиса **transmittal-api** (`pdm/transmittal-api`) — управление + трансмитталами (передачей документации на согласование), их шаблонами, + шагами/действиями маршрута согласования и генерацией актов. + + Сервис написан на Python (**FastAPI**). Приложение собирается фабрикой + `get_application` в `src/transmittal_service/app/http_server.py`. Роутинг + состоит из двух групп: + + - публичный API — префикс `/api/v1` (`controller/http/api/v1/api.py`); + - внутренний API — префикс `/internal/v1` (`controller/http/internal/v1/api.py`), + предназначен для вызовов внутри кластера (через ingress не публикуется). + + Интерактивная документация Swagger доступна по `/api/docs`, схема — + по `/api/openapi.json` (с учётом `root_path`). + + ### Аутентификация + Публичные эндпоинты (кроме `/api/v1/healthcheck`) требуют аутентификации. + Токен передаётся заголовком `Authorization: Bearer ` (FastAPI + `HTTPBearer`). Поддерживаются два режима (`controller/http/security.py`): + + 1. **Zitadel** — если передан дополнительный заголовок `identity` + (`Identity `), полезная нагрузка берётся из этого токена + (`urn:zitadel:iam:user:metadata`). Валидность проверяется на уровне + Istio, подпись сервисом не проверяется. + 2. **sarex-backend** — если заголовка `identity` нет, подпись основного + токена проверяется публичным RSA-ключом + (`TRANSMITTAL_SERVICE_AUTH__PUBLIC_KEY`, алгоритм `RS512`). + + Внутренние эндпоинты (`/internal/v1/*`) аутентификации на уровне приложения + не требуют — ограничение доступа обеспечивается сетевым слоем. + + ### Пагинация + Списочные ответы используют «bookmark»-пагинацию. Ответ оборачивается в + `Page` — `{ result, next, prev }`, где `next`/`prev` — курсоры (bookmark). + Размер страницы задаётся параметром `limit` (в теле или query, зависит от + эндпоинта), продолжение — параметром `bookmark`. Часть ответов оборачивается + в более простой `Result` — `{ result }` (без курсоров). + + ### Обработка ошибок + Ошибки возвращаются в формате **RFC 7807** (`application/problem+json`), + схема `ApiError` — `{ type, title, status, detail, instance }` + (`controller/http/middleware.py`, класс `ExceptionHandler`). Идентификатор + запроса возвращается в заголовке `X-Request-Id`, длительность обработки — + в `Server-Timing`. + + ### Замечания (расхождения кода) + - Ошибка «ресурс не найден» (`NotFound`) маппится на статус **`410 Gone`**, + а не на привычный `404 Not Found`. + - При отсутствии/некорректности `Authorization` FastAPI `HTTPBearer` + возвращает `403`, тогда как ошибки разбора токена в middleware дают `401`. + - Ошибки валидации тела/параметров запроса (Pydantic) отдаются FastAPI в + стандартном формате `422` (не `application/problem+json`). + - Конфликт имени шаблона (`TemplateNameIsNotUnique`) возвращает `409`. + + contact: + name: transmittal-api + url: https://gitlab/pdm/transmittal-api + +servers: + - url: https://api.sarex.io/transmittals + description: Production (ingress, root_path=/transmittals) + - url: https://stage-api.sarex.io/transmittals + description: Stage (ingress, root_path=/transmittals) + - url: http://transmittal-service.transmittal-api-stage.svc.cluster.local + description: Внутрикластерный адрес (ClusterIP, порт 80 → 8000). Единственный способ достучаться до /internal/v1 + - url: http://localhost:8001 + description: Локальный запуск (Uvicorn, порт по умолчанию 8001) + +tags: + - name: infra + description: Служебные эндпоинты (проверка доступности) + - name: transmittals + description: Трансмитталы — создание, просмотр, согласование, поиск, акты + - name: transmittal_templates + description: Шаблоны трансмитталов + - name: steps + description: Шаги маршрута согласования + - name: internal + description: Внутренние эндпоинты (только внутри кластера) + +security: + - bearerAuth: [] + +paths: + # ========================================================================== + # Infra + # ========================================================================== + /api/v1/healthcheck: + get: + tags: [infra] + summary: Проверка доступности зависимостей + description: | + Параллельно проверяет доступность всех внешних зависимостей (БД, S3, + и HTTP-сервисов) и возвращает агрегированный статус `healthy` или + `partially_healthy`. Аутентификация не требуется. + operationId: healthcheck + security: [] + responses: + '200': + description: Статус доступности зависимостей + content: + application/json: + schema: + $ref: '#/components/schemas/HealthCheckResponse' + + # ========================================================================== + # Steps + # ========================================================================== + /api/v1/steps: + get: + tags: [steps] + summary: Список шагов + operationId: getSteps + parameters: + - name: limit + in: query + required: false + schema: + type: integer + minimum: 1 + maximum: 32767 + default: 100 + - name: bookmark + in: query + required: false + schema: + type: string + nullable: true + responses: + '200': + description: Страница шагов + content: + application/json: + schema: + $ref: '#/components/schemas/Page_StepReadResponseDto' + '401': { $ref: '#/components/responses/Unauthorized' } + '403': { $ref: '#/components/responses/Forbidden' } + '422': { $ref: '#/components/responses/ValidationError' } + + /api/v1/steps/{id}: + get: + tags: [steps] + summary: Шаг по id + operationId: getStep + parameters: + - name: id + in: path + required: true + schema: + type: integer + minimum: 0 + maximum: 32767 + responses: + '200': + description: Шаг + content: + application/json: + schema: + $ref: '#/components/schemas/StepReadResponseDto' + '401': { $ref: '#/components/responses/Unauthorized' } + '403': { $ref: '#/components/responses/Forbidden' } + '410': { $ref: '#/components/responses/Gone' } + '422': { $ref: '#/components/responses/ValidationError' } + + # ========================================================================== + # Transmittals + # ========================================================================== + /api/v1/transmittals: + post: + tags: [transmittals] + summary: Список трансмитталов ресурса + description: Пагинированный список трансмитталов по `resource_id` со сводной статистикой по статусам. + operationId: listTransmittals + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/TransmittalListRequestDto' + responses: + '200': + description: Страница трансмитталов и статистика по статусам + content: + application/json: + schema: + $ref: '#/components/schemas/TransmittalListResponseDto' + '401': { $ref: '#/components/responses/Unauthorized' } + '403': { $ref: '#/components/responses/Forbidden' } + '422': { $ref: '#/components/responses/ValidationError' } + + /api/v1/transmittals/create: + post: + tags: [transmittals] + summary: Создать трансмиттал + description: | + Создаёт трансмиттал. Требуется хотя бы один документ (`documents_to_approve`) + и хотя бы один получатель (`receivers`). + operationId: createTransmittal + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/TransmittalCreateRequestDto' + responses: + '200': + description: Идентификатор созданного трансмиттала + content: + application/json: + schema: + $ref: '#/components/schemas/TransmittalCreateResponseDto' + '400': { $ref: '#/components/responses/BadRequest' } + '401': { $ref: '#/components/responses/Unauthorized' } + '403': { $ref: '#/components/responses/Forbidden' } + '422': { $ref: '#/components/responses/ValidationError' } + + /api/v1/transmittals/global_statuses: + post: + tags: [transmittals] + summary: Глобальные статусы по набору ресурсов + description: Возвращает статистику статусов трансмитталов по списку `resource_ids`. + operationId: getGlobalStatuses + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/TransmittalGetGlobalStatusesRequestDto' + responses: + '200': + description: Статистика статусов по ресурсам + content: + application/json: + schema: + $ref: '#/components/schemas/TransmittalGetGlobalStatusesResponseDto' + '401': { $ref: '#/components/responses/Unauthorized' } + '403': { $ref: '#/components/responses/Forbidden' } + '422': { $ref: '#/components/responses/ValidationError' } + + /api/v1/transmittals/status: + get: + tags: [transmittals] + summary: Справочник статусов + operationId: listStatuses + responses: + '200': + description: Список типов статусов + content: + application/json: + schema: + $ref: '#/components/schemas/StatusesListResponseDto' + '401': { $ref: '#/components/responses/Unauthorized' } + '403': { $ref: '#/components/responses/Forbidden' } + + /api/v1/transmittals/count: + get: + tags: [transmittals] + summary: Число открытых трансмитталов пользователя + description: Количество незакрытых трансмитталов, требующих действия текущего пользователя. + operationId: myOpenTransmittalsCount + responses: + '200': + description: Количество открытых трансмитталов + content: + application/json: + schema: + $ref: '#/components/schemas/TransmittalOpenCountResponseDto' + '401': { $ref: '#/components/responses/Unauthorized' } + '403': { $ref: '#/components/responses/Forbidden' } + + /api/v1/transmittals/search: + post: + tags: [transmittals] + summary: Поиск трансмитталов в ресурсе + description: Поиск и фильтрация трансмитталов в рамках одного ресурса (`resource_id`). + operationId: searchTransmittals + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/TransmittalSearchFilterRequestDto' + responses: + '200': + description: Страница трансмитталов и статистика по статусам + content: + application/json: + schema: + $ref: '#/components/schemas/TransmittalListResponseDto' + '401': { $ref: '#/components/responses/Unauthorized' } + '403': { $ref: '#/components/responses/Forbidden' } + '422': { $ref: '#/components/responses/ValidationError' } + + /api/v1/transmittals/search/resources: + post: + tags: [transmittals] + summary: Поиск по нескольким ресурсам + description: Фильтрация трансмитталов по списку ресурсов (`resource_ids`); возвращает статистику статусов по ресурсам. + operationId: searchTransmittalsResources + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/TransmittalSearchFilterDto' + responses: + '200': + description: Статистика статусов по ресурсам + content: + application/json: + schema: + $ref: '#/components/schemas/TransmittalGetGlobalStatusesResponseDto' + '401': { $ref: '#/components/responses/Unauthorized' } + '403': { $ref: '#/components/responses/Forbidden' } + '422': { $ref: '#/components/responses/ValidationError' } + + /api/v1/transmittals/{transmittal_id}: + parameters: + - $ref: '#/components/parameters/TransmittalId' + get: + tags: [transmittals] + summary: Трансмиттал по id + operationId: getTransmittal + responses: + '200': + description: Трансмиттал с историей действий и документами + content: + application/json: + schema: + $ref: '#/components/schemas/TransmittalReadResponseDto' + '401': { $ref: '#/components/responses/Unauthorized' } + '403': { $ref: '#/components/responses/Forbidden' } + '410': { $ref: '#/components/responses/Gone' } + '422': { $ref: '#/components/responses/ValidationError' } + delete: + tags: [transmittals] + summary: Удалить трансмиттал + operationId: deleteTransmittal + responses: + '204': { description: Удалено } + '400': { $ref: '#/components/responses/BadRequest' } + '401': { $ref: '#/components/responses/Unauthorized' } + '403': { $ref: '#/components/responses/Forbidden' } + '410': { $ref: '#/components/responses/Gone' } + '422': { $ref: '#/components/responses/ValidationError' } + + /api/v1/transmittals/{transmittal_id}/approve: + parameters: + - $ref: '#/components/parameters/TransmittalId' + put: + tags: [transmittals] + summary: Принять трансмиттал + description: Фиксирует действие «принять» текущего пользователя на текущем шаге (с опциональным комментарием). По завершении может инициировать генерацию акта. + operationId: approveTransmittal + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UserActionCreateRequest' + responses: + '200': + description: Созданное действие пользователя + content: + application/json: + schema: + $ref: '#/components/schemas/UserActionCreateResponseDto' + '400': { $ref: '#/components/responses/BadRequest' } + '401': { $ref: '#/components/responses/Unauthorized' } + '403': { $ref: '#/components/responses/Forbidden' } + '410': { $ref: '#/components/responses/Gone' } + '422': { $ref: '#/components/responses/ValidationError' } + + /api/v1/transmittals/{transmittal_id}/decline: + parameters: + - $ref: '#/components/parameters/TransmittalId' + put: + tags: [transmittals] + summary: Отклонить трансмиттал + description: Фиксирует действие «отклонить» текущего пользователя на текущем шаге (с опциональным комментарием). + operationId: declineTransmittal + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UserActionCreateRequest' + responses: + '200': + description: Созданное действие пользователя + content: + application/json: + schema: + $ref: '#/components/schemas/UserActionCreateResponseDto' + '400': { $ref: '#/components/responses/BadRequest' } + '401': { $ref: '#/components/responses/Unauthorized' } + '403': { $ref: '#/components/responses/Forbidden' } + '410': { $ref: '#/components/responses/Gone' } + '422': { $ref: '#/components/responses/ValidationError' } + + /api/v1/transmittals/{transmittal_id}/act_available: + parameters: + - $ref: '#/components/parameters/TransmittalId' + get: + tags: [transmittals] + summary: Доступность акта + description: Признак готовности акта для скачивания по данному трансмитталу. + operationId: actAvailable + responses: + '200': + description: Готовность акта + content: + application/json: + schema: + $ref: '#/components/schemas/TransmittalGetActReadinessResponse' + '401': { $ref: '#/components/responses/Unauthorized' } + '403': { $ref: '#/components/responses/Forbidden' } + '410': { $ref: '#/components/responses/Gone' } + '422': { $ref: '#/components/responses/ValidationError' } + + /api/v1/transmittals/{transmittal_id}/download_act: + parameters: + - $ref: '#/components/parameters/TransmittalId' + get: + tags: [transmittals] + summary: Скачать акт + description: Возвращает presigned-URL для скачивания акта из S3. + operationId: downloadAct + responses: + '200': + description: Presigned-URL акта + content: + application/json: + schema: + $ref: '#/components/schemas/TransmittalDownloadActResponse' + '400': { $ref: '#/components/responses/BadRequest' } + '401': { $ref: '#/components/responses/Unauthorized' } + '403': { $ref: '#/components/responses/Forbidden' } + '410': { $ref: '#/components/responses/Gone' } + '422': { $ref: '#/components/responses/ValidationError' } + + /api/v1/transmittals/{transmittal_id}/link_review: + parameters: + - $ref: '#/components/parameters/TransmittalId' + post: + tags: [transmittals] + summary: Привязать review к трансмитталу + operationId: linkReview + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/TransmittalLinkReviewRequest' + responses: + '204': { description: Review привязан } + '400': { $ref: '#/components/responses/BadRequest' } + '401': { $ref: '#/components/responses/Unauthorized' } + '403': { $ref: '#/components/responses/Forbidden' } + '410': { $ref: '#/components/responses/Gone' } + '422': { $ref: '#/components/responses/ValidationError' } + + # ========================================================================== + # Transmittal templates + # ========================================================================== + /api/v1/transmittal_templates: + post: + tags: [transmittal_templates] + summary: Список шаблонов трансмитталов + description: Пагинированный список шаблонов с фильтрами. Тело запроса опционально (при отсутствии применяются значения по умолчанию). + operationId: listTransmittalTemplates + requestBody: + required: false + content: + application/json: + schema: + $ref: '#/components/schemas/TransmittalTemplateListFiltersRequest' + responses: + '200': + description: Страница шаблонов + content: + application/json: + schema: + $ref: '#/components/schemas/Page_TransmittalTemplateListResponse' + '401': { $ref: '#/components/responses/Unauthorized' } + '403': { $ref: '#/components/responses/Forbidden' } + '422': { $ref: '#/components/responses/ValidationError' } + + /api/v1/transmittal_templates/create: + post: + tags: [transmittal_templates] + summary: Создать шаблон + operationId: createTransmittalTemplate + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/TransmittalTemplateCreateRequestDto' + responses: + '200': + description: Идентификатор созданного шаблона + content: + application/json: + schema: + $ref: '#/components/schemas/TransmittalTemplateCreateResponseDto' + '400': { $ref: '#/components/responses/BadRequest' } + '401': { $ref: '#/components/responses/Unauthorized' } + '403': { $ref: '#/components/responses/Forbidden' } + '409': { $ref: '#/components/responses/Conflict' } + '422': { $ref: '#/components/responses/ValidationError' } + + /api/v1/transmittal_templates/select: + get: + tags: [transmittal_templates] + summary: Шаблоны для выбора + description: Плоский список шаблонов (id/имя/автор), доступных для выбора в рамках ресурса. + operationId: listTransmittalTemplatesForSelect + parameters: + - name: resource + in: query + required: true + schema: + type: string + format: uuid + - name: template_id + in: query + required: false + description: Один или несколько id шаблонов для фильтрации + schema: + type: array + items: + type: string + format: uuid + nullable: true + responses: + '200': + description: Список шаблонов для выбора + content: + application/json: + schema: + $ref: '#/components/schemas/Result_TransmittalTemplateSelectListResponse' + '401': { $ref: '#/components/responses/Unauthorized' } + '403': { $ref: '#/components/responses/Forbidden' } + '422': { $ref: '#/components/responses/ValidationError' } + + /api/v1/transmittal_templates/{transmittal_template_id}: + parameters: + - $ref: '#/components/parameters/TransmittalTemplateId' + get: + tags: [transmittal_templates] + summary: Шаблон по id + operationId: getTransmittalTemplate + parameters: + - name: resource + in: query + required: true + schema: + type: string + format: uuid + responses: + '200': + description: Шаблон трансмиттала + content: + application/json: + schema: + $ref: '#/components/schemas/TransmittalTemplateGetSingleResponse' + '401': { $ref: '#/components/responses/Unauthorized' } + '403': { $ref: '#/components/responses/Forbidden' } + '410': { $ref: '#/components/responses/Gone' } + '422': { $ref: '#/components/responses/ValidationError' } + patch: + tags: [transmittal_templates] + summary: Обновить шаблон + operationId: updateTransmittalTemplate + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/TransmittalTemplateUpdateRequest' + responses: + '204': { description: Обновлено } + '400': { $ref: '#/components/responses/BadRequest' } + '401': { $ref: '#/components/responses/Unauthorized' } + '403': { $ref: '#/components/responses/Forbidden' } + '409': { $ref: '#/components/responses/Conflict' } + '410': { $ref: '#/components/responses/Gone' } + '422': { $ref: '#/components/responses/ValidationError' } + delete: + tags: [transmittal_templates] + summary: Удалить шаблон + operationId: deleteTransmittalTemplate + responses: + '204': { description: Удалено } + '401': { $ref: '#/components/responses/Unauthorized' } + '403': { $ref: '#/components/responses/Forbidden' } + '410': { $ref: '#/components/responses/Gone' } + '422': { $ref: '#/components/responses/ValidationError' } + + # ========================================================================== + # Internal (cluster-only) + # ========================================================================== + /internal/v1/transmittals: + post: + tags: [internal] + summary: Список трансмитталов (внутренний) + description: Внутренний список трансмитталов по фильтрам. Без аутентификации на уровне приложения; доступен только внутри кластера. + operationId: internalGetTransmittals + security: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/TransmittalListInternalRequest' + responses: + '200': + description: Список трансмитталов + content: + application/json: + schema: + $ref: '#/components/schemas/Result_TransmittalReadListResponseDto' + '422': { $ref: '#/components/responses/ValidationError' } + + /internal/v1/transmittals/by_bundle_ids: + post: + tags: [internal] + summary: Трансмитталы по bundle id (внутренний) + description: Возвращает трансмитталы, связанные с переданными `bundle_ids`. Без аутентификации на уровне приложения; доступен только внутри кластера. + operationId: internalGetTransmittalsByBundleIds + security: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/TransmittalGetByBundleIdsRequest' + responses: + '200': + description: Трансмитталы по bundle id + content: + application/json: + schema: + $ref: '#/components/schemas/Result_TransmittalGetByBundleIdsResponse' + '422': { $ref: '#/components/responses/ValidationError' } + +components: + securitySchemes: + bearerAuth: + type: http + scheme: bearer + bearerFormat: JWT + description: | + JWT в заголовке `Authorization: Bearer `. Проверяется публичным + ключом (`RS512`) в режиме sarex-backend либо принимается как есть в + режиме Zitadel (см. заголовок `identity`). + identityToken: + type: apiKey + in: header + name: identity + description: | + Опциональный заголовок `identity` (`Identity `) для режима Zitadel. + При его наличии полезная нагрузка берётся из этого токена, а подпись + основного токена сервисом не проверяется. + + parameters: + TransmittalId: + name: transmittal_id + in: path + required: true + schema: + type: string + format: uuid + TransmittalTemplateId: + name: transmittal_template_id + in: path + required: true + schema: + type: string + format: uuid + + responses: + BadRequest: + description: Некорректный запрос / бизнес-правило нарушено + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ApiError' + Unauthorized: + description: Токен не предоставлен или невалиден + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ApiError' + Forbidden: + description: Недостаточно прав + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ApiError' + Conflict: + description: Конфликт (например, имя шаблона уже занято) + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ApiError' + Gone: + description: Запрошенный ресурс не существует (маппинг `NotFound` → `410`) + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ApiError' + ValidationError: + description: Ошибка валидации тела/параметров запроса (FastAPI/Pydantic) + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPValidationError' + + schemas: + # --- Общие ------------------------------------------------------------ + ApiError: + type: object + description: Ошибка в формате RFC 7807 (application/problem+json) + properties: + type: { type: string } + title: { type: string } + status: { type: integer } + detail: { type: string } + instance: + type: string + description: URL запроса, вызвавшего ошибку + required: [type, title, status, detail, instance] + + HTTPValidationError: + type: object + properties: + detail: + type: array + items: + $ref: '#/components/schemas/ValidationErrorItem' + + ValidationErrorItem: + type: object + properties: + loc: + type: array + items: + anyOf: + - type: string + - type: integer + msg: { type: string } + type: { type: string } + required: [loc, msg, type] + + ReceiversRequestDto: + type: object + description: Получатели трансмиттала (запрос). Хотя бы один из наборов должен быть непуст. + properties: + users: + type: array + items: { type: integer, minimum: 0, maximum: 9223372036854775807 } + uniqueItems: true + departments: + type: array + items: { type: integer } + uniqueItems: true + roles: + type: array + items: { type: integer } + uniqueItems: true + + ReceiversReadResponseDto: + type: object + properties: + users: { type: array, items: { type: integer } } + departments: { type: array, items: { type: integer } } + roles: { type: array, items: { type: integer } } + required: [users, departments, roles] + + ReceiversFilterDto: + type: object + properties: + users: { type: array, items: { type: integer } } + departments: { type: array, items: { type: integer } } + roles: { type: array, items: { type: integer } } + + DocumentVersionRequestDto: + type: object + properties: + document_id: { type: integer, minimum: 0, maximum: 2147483647 } + bundle_id: { type: string, format: uuid } + required: [document_id, bundle_id] + + DocumentVersionReadResponse: + type: object + properties: + document_id: { type: integer } + bundle_id: { type: string, format: uuid } + required: [document_id, bundle_id] + + StatusReadResponseDto: + type: object + properties: + id: { type: integer } + name: { type: string } + slug: { type: string } + required: [id, name, slug] + + StatusSimpleDto: + type: object + properties: + slug: { type: string } + name: { type: string } + required: [slug, name] + + StatusesListResponseDto: + type: object + properties: + statuses: + type: array + items: { $ref: '#/components/schemas/StatusSimpleDto' } + required: [statuses] + + StepReadResponseDto: + type: object + properties: + id: { type: integer } + name: { type: string } + slug: { type: string } + required: [id, name, slug] + + ActionReadResponse: + type: object + properties: + id: { type: integer } + step_id: { type: integer } + name: { type: string } + slug: { type: string } + required: [id, step_id, name, slug] + + ReviewLightResponse: + type: object + properties: + id: { type: integer } + name: { type: string } + required: [id, name] + + UserActionCreateRequest: + type: object + properties: + comment: { type: string, nullable: true } + + UserActionCreateResponseDto: + type: object + properties: + id: { type: string, format: uuid } + transmittal_id: { type: string, format: uuid } + action_id: { type: integer } + comment: { type: string, nullable: true } + required: [id, transmittal_id, action_id, comment] + + UserActionReadResponse: + type: object + properties: + id: { type: string, format: uuid } + user_id: { type: integer } + transmittal_id: { type: string, format: uuid } + action_id: { type: integer } + comment: { type: string, nullable: true } + action: { $ref: '#/components/schemas/ActionReadResponse' } + created_at: { type: string, format: date-time } + required: [id, user_id, transmittal_id, action_id, comment, action, created_at] + + # --- Wrappers --------------------------------------------------------- + Page_StepReadResponseDto: + type: object + properties: + result: + type: array + items: { $ref: '#/components/schemas/StepReadResponseDto' } + next: { type: string } + prev: { type: string } + required: [result, next, prev] + + Page_TransmittalReadListResponseDto: + type: object + properties: + result: + type: array + items: { $ref: '#/components/schemas/TransmittalReadListResponseDto' } + next: { type: string } + prev: { type: string } + required: [result, next, prev] + + Page_TransmittalTemplateListResponse: + type: object + properties: + result: + type: array + items: { $ref: '#/components/schemas/TransmittalTemplateListResponse' } + next: { type: string } + prev: { type: string } + required: [result, next, prev] + + Result_TransmittalTemplateSelectListResponse: + type: object + properties: + result: + type: array + items: { $ref: '#/components/schemas/TransmittalTemplateSelectListResponse' } + required: [result] + + Result_TransmittalReadListResponseDto: + type: object + properties: + result: + type: array + items: { $ref: '#/components/schemas/TransmittalReadListResponseDto' } + required: [result] + + Result_TransmittalGetByBundleIdsResponse: + type: object + properties: + result: + type: array + items: { $ref: '#/components/schemas/TransmittalGetByBundleIdsResponse' } + required: [result] + + # --- Transmittals ----------------------------------------------------- + TransmittalCreateRequestDto: + type: object + properties: + name: { type: string, minLength: 1, maxLength: 255 } + company_id: { type: integer, minimum: 0, maximum: 18446744073709551615 } + additional_information: { type: string, nullable: true } + resource_id: { type: string, format: uuid } + auto_send: { type: boolean } + documents_to_approve: + type: array + description: Не менее одного документа + minItems: 1 + uniqueItems: true + items: { $ref: '#/components/schemas/DocumentVersionRequestDto' } + receivers: { $ref: '#/components/schemas/ReceiversRequestDto' } + deadline_at: + type: string + format: date-time + nullable: true + description: Дедлайн в будущем + required: [name, company_id, additional_information, resource_id, auto_send, documents_to_approve, receivers, deadline_at] + + TransmittalCreateResponseDto: + type: object + properties: + id: { type: string, format: uuid } + required: [id] + + TransmittalListRequestDto: + type: object + properties: + limit: { type: integer, default: 1000 } + bookmark: { type: string, nullable: true } + resource_id: { type: string, format: uuid } + required: [resource_id] + + TransmittalReadListResponseDto: + type: object + properties: + id: { type: string, format: uuid } + resource_id: { type: string, format: uuid } + created_by: { type: integer } + current_step_id: { type: integer } + "no": { type: integer } + name: { type: string } + additional_information: { type: string, nullable: true } + steps: { type: array, items: { type: integer } } + auto_send: { type: boolean } + receivers: { $ref: '#/components/schemas/ReceiversReadResponseDto' } + deadline_at: { type: string, format: date-time, nullable: true } + status: { $ref: '#/components/schemas/StatusReadResponseDto' } + step: { $ref: '#/components/schemas/StepReadResponseDto' } + sent_at: { type: string, format: date-time, nullable: true } + received_at: { type: string, format: date-time, nullable: true } + act_available: { type: boolean } + reviews: + type: array + nullable: true + items: { $ref: '#/components/schemas/ReviewLightResponse' } + required: [id, resource_id, created_by, current_step_id, "no", name, additional_information, steps, auto_send, receivers, deadline_at, status, step, sent_at, received_at, act_available] + + TransmittalReadResponseDto: + type: object + properties: + id: { type: string, format: uuid } + resource_id: { type: string, format: uuid } + created_by: { type: integer } + current_step_id: { type: integer } + "no": { type: integer } + name: { type: string } + additional_information: { type: string, nullable: true } + steps: { type: array, items: { type: integer } } + auto_send: { type: boolean } + receivers: { $ref: '#/components/schemas/ReceiversReadResponseDto' } + deadline_at: { type: string, format: date-time, nullable: true } + sent_at: { type: string, format: date-time, nullable: true } + received_at: { type: string, format: date-time, nullable: true } + act_available: { type: boolean } + status: { $ref: '#/components/schemas/StatusReadResponseDto' } + step: { $ref: '#/components/schemas/StepReadResponseDto' } + documents_to_approve: + type: array + items: { $ref: '#/components/schemas/DocumentVersionReadResponse' } + history: + type: array + items: { $ref: '#/components/schemas/UserActionReadResponse' } + required: [id, resource_id, created_by, current_step_id, "no", name, additional_information, steps, auto_send, receivers, deadline_at, sent_at, received_at, act_available, status, step, documents_to_approve, history] + + TransmittalStatusesStatResponseDto: + type: object + properties: + status: { $ref: '#/components/schemas/StatusReadResponseDto' } + count: { type: integer } + required: [status, count] + + TransmittalListResponseDto: + type: object + properties: + page: { $ref: '#/components/schemas/Page_TransmittalReadListResponseDto' } + statuses_stat: + type: array + items: { $ref: '#/components/schemas/TransmittalStatusesStatResponseDto' } + required: [page, statuses_stat] + + TransmittalGlobalStatusesStatResponseDto: + type: object + properties: + resource_id: { type: string, format: uuid } + statuses: + type: array + items: { $ref: '#/components/schemas/TransmittalStatusesStatResponseDto' } + required: [resource_id, statuses] + + TransmittalGetGlobalStatusesRequestDto: + type: object + properties: + resource_ids: + type: array + items: { type: string, format: uuid } + required: [resource_ids] + + TransmittalGetGlobalStatusesResponseDto: + type: object + properties: + result: + type: array + items: { $ref: '#/components/schemas/TransmittalGlobalStatusesStatResponseDto' } + required: [result] + + TransmittalDownloadActResponse: + type: object + properties: + presigned_url: { type: string } + required: [presigned_url] + + TransmittalGetActReadinessResponse: + type: object + properties: + act_available: { type: boolean } + required: [act_available] + + TransmittalOpenCountResponseDto: + type: object + properties: + count: { type: integer } + required: [count] + + TransmittalLinkReviewRequest: + type: object + properties: + review_id: { type: integer } + required: [review_id] + + TransmittalSearchFilterRequestDto: + type: object + description: Фильтр поиска в рамках одного ресурса + properties: + q: { type: string, nullable: true } + resource_id: { type: string, format: uuid } + status_slug: { type: array, items: { type: string } } + created_by: { type: array, items: { type: integer } } + receivers: + nullable: true + allOf: [{ $ref: '#/components/schemas/ReceiversFilterDto' }] + created_from: { type: string, format: date-time, nullable: true } + created_to: { type: string, format: date-time, nullable: true } + received_from: { type: string, format: date-time, nullable: true } + received_to: { type: string, format: date-time, nullable: true } + deadline_at: { type: string, format: date-time, nullable: true } + unlimited: { type: boolean, nullable: true, default: false } + auto_send: { type: array, items: { type: boolean } } + limit: { type: integer, nullable: true, default: 100 } + bookmark: { type: string, nullable: true } + required: [resource_id] + + TransmittalSearchFilterDto: + type: object + description: Фильтр поиска по нескольким ресурсам + properties: + q: { type: string, nullable: true } + resource_ids: { type: array, items: { type: string, format: uuid } } + status_slug: { type: array, items: { type: string } } + created_by: { type: array, items: { type: integer } } + receivers: + nullable: true + allOf: [{ $ref: '#/components/schemas/ReceiversFilterDto' }] + created_from: { type: string, format: date-time, nullable: true } + created_to: { type: string, format: date-time, nullable: true } + received_from: { type: string, format: date-time, nullable: true } + received_to: { type: string, format: date-time, nullable: true } + deadline_at: { type: string, format: date-time, nullable: true } + unlimited: { type: boolean, nullable: true, default: false } + auto_send: { type: array, items: { type: boolean } } + limit: { type: integer, nullable: true, default: 100 } + bookmark: { type: string, nullable: true } + + # --- Internal --------------------------------------------------------- + TransmittalListInternalRequest: + type: object + properties: + limit: { type: integer, default: 100 } + transmittal_ids: { type: array, items: { type: string, format: uuid } } + company_id: { type: integer, nullable: true } + resource_id: { type: string, format: uuid, nullable: true } + + TransmittalGetByBundleIdsRequest: + type: object + properties: + bundle_ids: { type: array, items: { type: string, format: uuid } } + required: [bundle_ids] + + TransmittalGetByBundleIdsReadDto: + type: object + properties: + id: { type: string, format: uuid } + name: { type: string } + status: { $ref: '#/components/schemas/StatusReadResponseDto' } + created_at: { type: string, format: date-time } + required: [id, name, status, created_at] + + TransmittalGetByBundleIdsResponse: + type: object + properties: + bundle_id: { type: string, format: uuid } + transmittals: + type: array + items: { $ref: '#/components/schemas/TransmittalGetByBundleIdsReadDto' } + required: [bundle_id, transmittals] + + # --- Templates -------------------------------------------------------- + TransmittalTemplateListFiltersRequest: + type: object + properties: + include_wo_resources: { type: boolean, default: true } + resources: { type: array, nullable: true, items: { type: string, format: uuid } } + companies: { type: array, nullable: true, items: { type: integer } } + q: { type: string, nullable: true } + bookmark: { type: string, nullable: true } + limit: { type: integer, default: 20 } + + TransmittalTemplateListResponse: + type: object + properties: + id: { type: string, format: uuid } + name: { type: string } + is_active: { type: boolean } + resource_id: { type: string, format: uuid, nullable: true } + allowed_initiators: + nullable: true + allOf: [{ $ref: '#/components/schemas/ReceiversReadResponseDto' }] + created_by: { type: integer } + created_at: { type: string, format: date-time } + has_deadline_interval: { type: boolean } + deadline_interval: { type: integer, minimum: 1, nullable: true, description: Интервал дедлайна в днях } + auto_send: { type: boolean, nullable: true } + has_additional_information: { type: boolean } + additional_information: { type: string, nullable: true } + receivers: + nullable: true + allOf: [{ $ref: '#/components/schemas/ReceiversReadResponseDto' }] + invalid_receivers: + nullable: true + allOf: [{ $ref: '#/components/schemas/ReceiversReadResponseDto' }] + required: [id, name, is_active, resource_id, allowed_initiators, created_by, created_at, has_deadline_interval, deadline_interval, auto_send, has_additional_information, additional_information, receivers, invalid_receivers] + + TransmittalTemplateCreateRequestDto: + type: object + properties: + name: { type: string, minLength: 1, maxLength: 255 } + resource_id: { type: string, format: uuid, nullable: true } + allowed_initiators: + nullable: true + allOf: [{ $ref: '#/components/schemas/ReceiversRequestDto' }] + company_id: { type: integer } + has_deadline_interval: { type: boolean } + deadline_interval: { type: integer, minimum: 1, nullable: true, description: Интервал дедлайна в днях } + auto_send: { type: boolean, nullable: true } + has_additional_information: { type: boolean } + additional_information: { type: string, nullable: true } + receivers: + nullable: true + allOf: [{ $ref: '#/components/schemas/ReceiversRequestDto' }] + required: [name, resource_id, allowed_initiators, company_id, has_deadline_interval, deadline_interval, auto_send, has_additional_information, additional_information, receivers] + + TransmittalTemplateCreateResponseDto: + type: object + properties: + id: { type: string, format: uuid } + required: [id] + + TransmittalTemplateSelectListResponse: + type: object + properties: + id: { type: string, format: uuid } + name: { type: string } + created_by: { type: integer } + required: [id, name, created_by] + + TransmittalTemplateGetSingleResponse: + type: object + properties: + name: { type: string } + created_by: { type: integer } + has_deadline_interval: { type: boolean } + deadline_interval: { type: integer, minimum: 1, nullable: true, description: Интервал дедлайна в днях } + auto_send: { type: boolean, nullable: true } + has_additional_information: { type: boolean } + additional_information: { type: string, nullable: true } + receivers: + nullable: true + allOf: [{ $ref: '#/components/schemas/ReceiversReadResponseDto' }] + invalid_receivers: + nullable: true + allOf: [{ $ref: '#/components/schemas/ReceiversReadResponseDto' }] + required: [name, created_by, has_deadline_interval, deadline_interval, auto_send, has_additional_information, additional_information, receivers, invalid_receivers] + + TransmittalTemplateUpdateRequest: + type: object + description: Частичное обновление. Все поля опциональны. + properties: + name: { type: string, minLength: 1, maxLength: 255, nullable: true } + is_active: { type: boolean, nullable: true } + resource_id: { type: string, format: uuid, nullable: true } + allowed_initiators: + nullable: true + allOf: [{ $ref: '#/components/schemas/ReceiversRequestDto' }] + has_deadline_interval: { type: boolean, nullable: true } + deadline_interval: { type: integer, minimum: 1, nullable: true, description: Интервал дедлайна в днях } + auto_send: { type: boolean, nullable: true } + has_additional_information: { type: boolean, nullable: true } + additional_information: { type: string, nullable: true } + receivers: + nullable: true + allOf: [{ $ref: '#/components/schemas/ReceiversRequestDto' }] + + # --- Infra ------------------------------------------------------------ + ResourceStatus: + type: object + properties: + name: { type: string } + available: { type: boolean } + required: [name, available] + + HealthCheckResponse: + type: object + properties: + availability: + type: array + items: { $ref: '#/components/schemas/ResourceStatus' } + status: + type: string + enum: [healthy, partially_healthy] + description: Вычисляемое поле — `healthy`, если доступны все зависимости + readOnly: true + required: [availability, status]