openapi: 3.0.3 info: title: Flows Service API version: "1.0.0" description: | REST API сервиса **flows-backend** (`proc/flows-backend`) — управление процессами согласования документации: маршрутами (`flows`), их шагами (`steps`) и статусами (`statuses`), запусками согласования (`reviews`), документами в согласовании (`documents`), действиями пользователей (`user-actions`) и очередью задач согласующих (`tasks`). Сервис написан на Python (**FastAPI**). Приложение собирается фабрикой `get_app` в `src/flow/main.py`. Роутинг состоит из двух зеркальных групп (`src/flow/routers/__init__.py`): - публичный API — префикс `/api/v1` (`API_PREFIX`); - внутренний API — префикс `/internal/v1` (`API_INTERNAL_PREFIX`), предназначен для вызовов внутри кластера. Набор роутеров идентичен публичному, но внутренний префикс исключён из проверки аутентификации. Оба префикса могут дополнительно предваряться `PROXY_PATH_PREFIX` (в кластере — `/flows`). Админ-панель (`sqladmin`) смонтирована по `/api/admin/`. Интерактивная документация Swagger доступна по `/docs`, схема — по `/openapi.json` (с учётом `root_path`). Ниже описан публичный API (`/api/v1`). Внутренний API (`/internal/v1/*`) имеет те же пути и тела, но не требует аутентификации на уровне приложения. ### Аутентификация Публичные эндпоинты требуют заголовок `Authorization: Bearer ` (`src/flow/middleware.py`, `TokenUserMiddleware`). Поддерживаются два режима: 1. **Zitadel** — если передан заголовок `identity` (`Bearer `), полезная нагрузка берётся из этого токена (`urn:zitadel:iam:user:metadata`). 2. **sarex-backend** — если заголовка `identity` нет, данные берутся из основного токена (подпись проверяется публичным RSA-ключом `JWT_PUBLIC_KEY`). Проверка отключается флагом `JWT_AUTH_ENABLE=False` (тогда все запросы идут от дефолтного администратора). Пути `/docs/`, `/openapi.json/` и весь `/internal/*` из проверки исключены. ### Авторизация (права) Доступ к группам проверяется в `PermissionManager` (`src/flow/dependencies.py`) по правам пользователя (`src/flow/utils/permissions.py`): напр. `flows`/`steps`/ `statuses` требуют `base.can_view_flow`/`base.can_add_flow`/… , `reviews`/ `documents` — `base.can_view_review`/`base.can_add_review`/… Пользователь с признаком администратора проверки прав пропускает. ### Пагинация Списочные эндпоинты используют limit/offset (`LimitOffsetParams`, по умолчанию `limit=1000`, `offset=0`). Часть «тяжёлых» списков (`reviews`, подсчёты) возвращается как готовый JSON (`Response(media_type=application/json)`), поэтому их тело в схеме описано обобщённо. ### Обработка ошибок Ошибки бизнес-логики возвращаются как `{"detail": "..."}` с соответствующим статусом (`400`/`403`/`404`). Ошибки валидации тела/query (Pydantic) отдаются FastAPI в стандартном формате `422`. servers: - url: https://api.sarex.io/flows/api/v1 description: production - url: https://api.preprod.sarex.io/flows/api/v1 description: preprod - url: https://stage-api.sarex.io/flows/api/v1 description: stage security: - bearerAuth: [] tags: - name: flows description: Маршруты согласования - name: reviews description: Запуски согласования - name: steps description: Шаги маршрута - name: statuses description: Статусы согласования - name: documents description: Документы в согласовании - name: user_actions description: Действия пользователей - name: tasks description: Очередь задач согласующих paths: /flows/: get: tags: [flows] summary: Список маршрутов description: Фильтры передаются query-параметрами (`MainFilters`). При `full=true` возвращаются вложенные шаги/статусы. parameters: - { $ref: '#/components/parameters/Limit' } - { $ref: '#/components/parameters/Offset' } - { in: query, name: full, schema: { type: boolean, default: false } } - { in: query, name: resource_id, schema: { type: string }, description: "CSV UUID ресурсов" } - { in: query, name: company_id, schema: { type: string } } - { in: query, name: is_active, schema: { type: boolean } } - { in: query, name: flow_type, schema: { $ref: '#/components/schemas/FlowType' } } - { in: query, name: name, schema: { type: string } } responses: '200': description: OK content: application/json: schema: type: array items: oneOf: [ { $ref: '#/components/schemas/Flow' }, { $ref: '#/components/schemas/FullFlow' } ] post: tags: [flows] summary: Создать маршрут parameters: - { in: query, name: full, schema: { type: boolean, default: false } } requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/FlowCreate' } responses: '201': description: Создан content: application/json: schema: oneOf: [ { $ref: '#/components/schemas/Flow' }, { $ref: '#/components/schemas/FullFlow' } ] '400': { $ref: '#/components/responses/BadRequest' } /flows/filter/: post: tags: [flows] summary: Список маршрутов по фильтру (тело) parameters: - { $ref: '#/components/parameters/Limit' } - { $ref: '#/components/parameters/Offset' } requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/MainFilterPostRequest' } responses: '200': description: OK content: application/json: schema: { type: array, items: { $ref: '#/components/schemas/Flow' } } /flows/light/: get: tags: [flows] summary: Облегчённый список маршрутов (id, name) parameters: - { $ref: '#/components/parameters/Limit' } - { $ref: '#/components/parameters/Offset' } responses: '200': description: OK content: application/json: schema: { type: array, items: { $ref: '#/components/schemas/FlowLight' } } /flows/count_flows_by_resource/: get: tags: [flows] summary: Количество маршрутов, сгруппированное по resource_id responses: '200': { $ref: '#/components/responses/JsonObject' } post: tags: [flows] summary: То же по фильтру (тело) requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/MainFilterPostRequest' } responses: '200': { $ref: '#/components/responses/JsonObject' } /flows/bulk-update/: patch: tags: [flows] summary: Массовое обновление маршрутов (watchers/approvers) requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/FlowBulkUpdate' } responses: '200': description: OK content: application/json: schema: { type: array, items: { $ref: '#/components/schemas/Flow' } } '404': { $ref: '#/components/responses/NotFound' } /flows/{instance_id}/: parameters: - { $ref: '#/components/parameters/InstanceId' } get: tags: [flows] summary: Маршрут по id parameters: - { in: query, name: full, schema: { type: boolean, default: false } } responses: '200': description: OK content: application/json: schema: oneOf: [ { $ref: '#/components/schemas/Flow' }, { $ref: '#/components/schemas/FullFlow' } ] '404': { $ref: '#/components/responses/NotFound' } put: tags: [flows] summary: Обновить маршрут parameters: - { in: query, name: full, schema: { type: boolean, default: false } } requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/FlowCreate' } responses: '200': description: OK content: application/json: schema: oneOf: [ { $ref: '#/components/schemas/Flow' }, { $ref: '#/components/schemas/FullFlow' } ] '400': { $ref: '#/components/responses/BadRequest' } '404': { $ref: '#/components/responses/NotFound' } delete: tags: [flows] summary: Удалить маршрут responses: '200': description: OK content: application/json: schema: { $ref: '#/components/schemas/Flow' } '404': { $ref: '#/components/responses/NotFound' } /flows/{instance_id}/copy/: parameters: - { $ref: '#/components/parameters/InstanceId' } post: tags: [flows] summary: Копировать маршрут parameters: - { in: query, name: full, schema: { type: boolean, default: false } } requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/CopyFlow' } responses: '201': description: Создан content: application/json: schema: oneOf: [ { $ref: '#/components/schemas/Flow' }, { $ref: '#/components/schemas/FullFlow' } ] '400': { $ref: '#/components/responses/BadRequest' } '404': { $ref: '#/components/responses/NotFound' } /reviews/: get: tags: [reviews] summary: Список review (готовый JSON) parameters: - { $ref: '#/components/parameters/Limit' } - { $ref: '#/components/parameters/Offset' } - { in: query, name: join_user_actions, schema: { type: boolean, default: false } } - { in: query, name: resource_id, schema: { type: string } } - { in: query, name: flow_id, schema: { type: string } } - { in: query, name: company_id, schema: { type: string } } - { in: query, name: status, schema: { type: string }, description: "CSV значений StateReview" } responses: '200': { $ref: '#/components/responses/JsonArray' } post: tags: [reviews] summary: Создать review requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/BaseReview' } responses: '201': description: Создан content: application/json: schema: { $ref: '#/components/schemas/Review' } '400': { $ref: '#/components/responses/BadRequest' } /reviews/filter/: post: tags: [reviews] summary: Список review по фильтру (тело) parameters: - { $ref: '#/components/parameters/Limit' } - { $ref: '#/components/parameters/Offset' } - { in: query, name: join_user_actions, schema: { type: boolean, default: false } } requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/MainFilterPostRequest' } responses: '200': { $ref: '#/components/responses/JsonArray' } /reviews/light/: get: tags: [reviews] summary: Облегчённый список review parameters: - { $ref: '#/components/parameters/Limit' } - { $ref: '#/components/parameters/Offset' } - { in: query, name: resource_id, schema: { type: string } } - { in: query, name: flow_id, schema: { type: string } } - { in: query, name: company_id, schema: { type: string } } responses: '200': { $ref: '#/components/responses/JsonArray' } /reviews/tasks-count/: get: tags: [reviews] summary: Количество задач текущего пользователя responses: '200': description: OK content: application/json: schema: { $ref: '#/components/schemas/TasksCount' } /reviews/count_by_resource_id/: get: tags: [reviews] summary: Количество review по resource_id responses: '200': { $ref: '#/components/responses/JsonObject' } post: tags: [reviews] summary: То же по фильтру (тело) requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/MainFilterPostRequest' } responses: '200': { $ref: '#/components/responses/JsonObject' } /reviews/count_by_reviewer_id/: get: tags: [reviews] summary: Количество review по reviewer_id responses: '200': { $ref: '#/components/responses/JsonObject' } post: tags: [reviews] summary: То же по фильтру (тело) requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/MainFilterPostRequest' } responses: '200': { $ref: '#/components/responses/JsonObject' } /reviews/bulk-reviewers-update/: patch: tags: [reviews] summary: Массовое обновление согласующих в review requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/BulkReviewersUpdate' } responses: '200': description: OK content: application/json: schema: { type: array, items: { $ref: '#/components/schemas/Review' } } /reviews/{instance_id}/: parameters: - { $ref: '#/components/parameters/InstanceId' } get: tags: [reviews] summary: Review по id responses: '200': { $ref: '#/components/responses/ReviewOk' } '404': { $ref: '#/components/responses/NotFound' } put: tags: [reviews] summary: Обновить review requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/UpdateBaseReview' } responses: '200': { $ref: '#/components/responses/ReviewOk' } '404': { $ref: '#/components/responses/NotFound' } patch: tags: [reviews] summary: Частичное обновление review requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/PatchBaseReview' } responses: '200': { $ref: '#/components/responses/ReviewOk' } '404': { $ref: '#/components/responses/NotFound' } delete: tags: [reviews] summary: Удалить review responses: '200': { $ref: '#/components/responses/ReviewOk' } '404': { $ref: '#/components/responses/NotFound' } /reviews/{instance_id}/restart/: parameters: - { $ref: '#/components/parameters/InstanceId' } post: tags: [reviews] summary: Пересчитать динамические поля review responses: '200': { $ref: '#/components/responses/ReviewOk' } '404': { $ref: '#/components/responses/NotFound' } /reviews/{instance_id}/documents/: parameters: - { $ref: '#/components/parameters/InstanceId' } get: tags: [reviews] summary: Документы review parameters: - { $ref: '#/components/parameters/Limit' } - { $ref: '#/components/parameters/Offset' } - { in: query, name: document_type, schema: { type: string }, description: "CSV" } - { in: query, name: bundle_id, schema: { type: string }, description: "CSV" } responses: '200': description: OK content: application/json: schema: { type: array, items: { $ref: '#/components/schemas/Document' } } '404': { $ref: '#/components/responses/NotFound' } put: tags: [reviews] summary: Обновить статус документов review requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/BaseUpdateDocument' } responses: '200': description: OK content: application/json: schema: { type: array, items: { $ref: '#/components/schemas/Document' } } '400': { $ref: '#/components/responses/BadRequest' } '404': { $ref: '#/components/responses/NotFound' } /reviews/{instance_id}/start/: parameters: - { $ref: '#/components/parameters/InstanceId' } patch: tags: [reviews] summary: Начать проверку (таймтрекинг) responses: '200': { $ref: '#/components/responses/JsonObject' } '400': { $ref: '#/components/responses/BadRequest' } '404': { $ref: '#/components/responses/NotFound' } /reviews/{instance_id}/approve/: parameters: - { $ref: '#/components/parameters/InstanceId' } patch: tags: [reviews] summary: Пройти review согласующим (принять/отклонить/подписать/аннулировать) requestBody: required: false content: application/json: schema: { $ref: '#/components/schemas/StatusForReview' } responses: '200': { $ref: '#/components/responses/ReviewOk' } '400': { $ref: '#/components/responses/BadRequest' } '404': { $ref: '#/components/responses/NotFound' } /reviews/{instance_id}/update-bundles/: parameters: - { $ref: '#/components/parameters/InstanceId' } patch: tags: [reviews] summary: Обновить bundle_id подписанных документов requestBody: required: true content: application/json: schema: { type: object, additionalProperties: true } responses: '200': description: OK content: application/json: schema: { type: array, items: { $ref: '#/components/schemas/Document' } } '404': { $ref: '#/components/responses/NotFound' } /reviews/{instance_id}/update-documents/: parameters: - { $ref: '#/components/parameters/InstanceId' } patch: tags: [reviews] summary: Обновить copied-id документов requestBody: required: true content: application/json: schema: { type: array, items: { $ref: '#/components/schemas/UpdateDocumentCopiedIds' } } responses: '200': description: OK content: application/json: schema: { type: array, items: { $ref: '#/components/schemas/Document' } } '404': { $ref: '#/components/responses/NotFound' } /reviews/{instance_id}/change_reviewers/: parameters: - { $ref: '#/components/parameters/InstanceId' } patch: tags: [reviews] summary: Заменить согласующих на текущем шаге requestBody: required: true content: application/json: schema: { type: array, items: { $ref: '#/components/schemas/PatchCurrentReviewers' } } responses: '200': { $ref: '#/components/responses/ReviewOk' } '404': { $ref: '#/components/responses/NotFound' } /reviews/{instance_id}/change-min-reviewers/: parameters: - { $ref: '#/components/parameters/InstanceId' } patch: tags: [reviews] summary: Изменить минимальное число согласующих на шаге requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/PatchMinReviewersOnStep' } responses: '200': { $ref: '#/components/responses/ReviewOk' } '404': { $ref: '#/components/responses/NotFound' } /reviews/{instance_id}/set-step/{step_id}/: parameters: - { $ref: '#/components/parameters/InstanceId' } - { in: path, name: step_id, required: true, schema: { type: integer } } patch: tags: [reviews] summary: Принудительно установить шаг review (только админ) responses: '200': { $ref: '#/components/responses/ReviewOk' } '400': { $ref: '#/components/responses/BadRequest' } '403': { $ref: '#/components/responses/Forbidden' } '404': { $ref: '#/components/responses/NotFound' } /reviews/{instance_id}/switch_to_next_step/: parameters: - { $ref: '#/components/parameters/InstanceId' } patch: tags: [reviews] summary: Перевести review на следующий шаг (только superuser) responses: '200': { $ref: '#/components/responses/ReviewOk' } '403': { $ref: '#/components/responses/Forbidden' } '404': { $ref: '#/components/responses/NotFound' } /reviews/{instance_id}/time-tracking/: parameters: - { $ref: '#/components/parameters/InstanceId' } get: tags: [reviews] summary: Таймтрекинг review responses: '200': { $ref: '#/components/responses/JsonObject' } '404': { $ref: '#/components/responses/NotFound' } /reviews/{review_id}/checklist-results/: parameters: - { in: path, name: review_id, required: true, schema: { type: integer } } patch: tags: [reviews] summary: Обновить результаты чек-листов review requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/ChecklistResultsUpdateRequest' } responses: '200': { $ref: '#/components/responses/ReviewOk' } '400': { $ref: '#/components/responses/BadRequest' } '404': { $ref: '#/components/responses/NotFound' } /reviews/{review_id}/transmittal-created/: parameters: - { in: path, name: review_id, required: true, schema: { type: integer } } post: tags: [reviews] summary: Зафиксировать созданную по review передачу (transmittal) requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/TransmittalForReviewCreated' } responses: '200': { $ref: '#/components/responses/ReviewOk' } '400': { $ref: '#/components/responses/BadRequest' } '404': { $ref: '#/components/responses/NotFound' } /steps/: get: tags: [steps] summary: Список шагов parameters: - { $ref: '#/components/parameters/Limit' } - { $ref: '#/components/parameters/Offset' } - { in: query, name: full, schema: { type: boolean, default: false } } responses: '200': description: OK content: application/json: schema: { type: array, items: { $ref: '#/components/schemas/Step' } } post: tags: [steps] summary: Создать шаг requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/BaseStep' } responses: '201': description: Создан content: application/json: schema: { $ref: '#/components/schemas/Step' } '400': { $ref: '#/components/responses/BadRequest' } /steps/{instance_id}/: parameters: - { $ref: '#/components/parameters/InstanceId' } get: tags: [steps] summary: Шаг по id parameters: - { in: query, name: full, schema: { type: boolean, default: false } } responses: '200': description: OK content: application/json: schema: { $ref: '#/components/schemas/Step' } '404': { $ref: '#/components/responses/NotFound' } put: tags: [steps] summary: Обновить шаг requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/BaseStep' } responses: '200': description: OK content: application/json: schema: { $ref: '#/components/schemas/Step' } '404': { $ref: '#/components/responses/NotFound' } delete: tags: [steps] summary: Удалить шаг responses: '200': description: OK content: application/json: schema: { $ref: '#/components/schemas/Step' } '404': { $ref: '#/components/responses/NotFound' } /steps/{instance_id}/active_reviews/: parameters: - { $ref: '#/components/parameters/InstanceId' } get: tags: [steps] summary: Активные review на шаге responses: '200': description: OK content: application/json: schema: { type: array, items: { $ref: '#/components/schemas/ActiveReview' } } '404': { $ref: '#/components/responses/NotFound' } /steps/{instance_id}/update_reviewers/: parameters: - { $ref: '#/components/parameters/InstanceId' } patch: tags: [steps] summary: Обновить согласующих шага requestBody: required: true content: application/json: schema: { type: array, items: { $ref: '#/components/schemas/UpdatedReviewer' } } responses: '200': description: OK content: application/json: schema: { $ref: '#/components/schemas/Step' } '404': { $ref: '#/components/responses/NotFound' } /steps/{instance_id}/get_reviewers/: parameters: - { $ref: '#/components/parameters/InstanceId' } get: tags: [steps] summary: Допустимые согласующие для шага parameters: - { in: query, name: review_id, schema: { type: integer } } responses: '200': description: OK content: application/json: schema: { type: array, items: { type: object, additionalProperties: true } } '404': { $ref: '#/components/responses/NotFound' } /statuses/: get: tags: [statuses] summary: Список статусов parameters: - { $ref: '#/components/parameters/Limit' } - { $ref: '#/components/parameters/Offset' } responses: '200': description: OK content: application/json: schema: { type: array, items: { $ref: '#/components/schemas/Status' } } post: tags: [statuses] summary: Создать статус requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/BaseStatus' } responses: '201': description: Создан content: application/json: schema: { $ref: '#/components/schemas/Status' } '400': { $ref: '#/components/responses/BadRequest' } /statuses/{instance_id}/: parameters: - { $ref: '#/components/parameters/InstanceId' } get: tags: [statuses] summary: Статус по id responses: '200': description: OK content: application/json: schema: { $ref: '#/components/schemas/Status' } '404': { $ref: '#/components/responses/NotFound' } put: tags: [statuses] summary: Обновить статус requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/BaseStatus' } responses: '200': description: OK content: application/json: schema: { $ref: '#/components/schemas/Status' } '404': { $ref: '#/components/responses/NotFound' } delete: tags: [statuses] summary: Удалить статус responses: '200': description: OK content: application/json: schema: { $ref: '#/components/schemas/Status' } '404': { $ref: '#/components/responses/NotFound' } /documents/: get: tags: [documents] summary: Список документов description: При `full=true` возвращаются расширенные записи (`ExtendDocument`) с данными review. parameters: - { $ref: '#/components/parameters/Limit' } - { $ref: '#/components/parameters/Offset' } - { in: query, name: full, schema: { type: boolean, default: false } } - { in: query, name: flow_ids, schema: { type: string }, description: "CSV" } - { in: query, name: review_ids, schema: { type: string }, description: "CSV" } - { in: query, name: document_ids, schema: { type: string }, description: "CSV" } - { in: query, name: bundle_ids, schema: { type: string }, description: "CSV" } - { in: query, name: review_status, schema: { type: string }, description: "CSV" } - { in: query, name: document_types, schema: { type: string }, description: "CSV" } - { in: query, name: document_copied_ids, schema: { type: string }, description: "CSV" } - { in: query, name: bundle_copied_ids, schema: { type: string }, description: "CSV" } responses: '200': description: OK content: application/json: schema: type: array items: oneOf: [ { $ref: '#/components/schemas/Document' }, { $ref: '#/components/schemas/ExtendDocument' } ] post: tags: [documents] summary: Создать документы (пакетно) requestBody: required: true content: application/json: schema: { type: array, items: { $ref: '#/components/schemas/BaseDocument' } } responses: '201': description: Создано content: application/json: schema: { type: array, items: { $ref: '#/components/schemas/Document' } } '400': { $ref: '#/components/responses/BadRequest' } /documents/filter/: post: tags: [documents] summary: Список документов по фильтру (тело) parameters: - { $ref: '#/components/parameters/Limit' } - { $ref: '#/components/parameters/Offset' } requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/DocumentFilterRequest' } responses: '200': description: OK content: application/json: schema: type: array items: oneOf: [ { $ref: '#/components/schemas/Document' }, { $ref: '#/components/schemas/ExtendDocument' } ] /documents/change-copy-paths/: patch: tags: [documents] summary: Изменить пути копирования документов requestBody: required: true content: application/json: schema: { type: array, items: { $ref: '#/components/schemas/ChangeDocumentCopyPath' } } responses: '200': description: OK content: application/json: schema: { type: array, items: { $ref: '#/components/schemas/Document' } } /documents/set-status/: patch: tags: [documents] summary: Установить статус документам parameters: - { in: query, name: document_ids, required: true, schema: { type: string }, description: "CSV id документов" } requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/SetDocumentStatusUpdate' } responses: '200': description: OK content: application/json: schema: { type: array, items: { $ref: '#/components/schemas/SetDocumentStatusRead' } } '400': { $ref: '#/components/responses/BadRequest' } /documents/{instance_id}/: parameters: - { $ref: '#/components/parameters/InstanceId' } get: tags: [documents] summary: Документ по id responses: '200': { $ref: '#/components/responses/DocumentOk' } '404': { $ref: '#/components/responses/NotFound' } put: tags: [documents] summary: Обновить статус документа requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/BaseUpdateDocument' } responses: '200': { $ref: '#/components/responses/DocumentOk' } '400': { $ref: '#/components/responses/BadRequest' } '404': { $ref: '#/components/responses/NotFound' } patch: tags: [documents] summary: Установить bundle_id и статус документа requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/SetDocumentBundleIdAndStatus' } responses: '200': { $ref: '#/components/responses/DocumentOk' } '404': { $ref: '#/components/responses/NotFound' } delete: tags: [documents] summary: Удалить документ responses: '200': { $ref: '#/components/responses/DocumentOk' } '404': { $ref: '#/components/responses/NotFound' } /user-actions/: post: tags: [user_actions] summary: Создать действие пользователя requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/BaseUserAction' } responses: '201': description: Создано content: application/json: schema: { type: object, properties: { detail: { type: string } } } '400': { $ref: '#/components/responses/BadRequest' } /user-actions/bulk-delete-user-action/: delete: tags: [user_actions] summary: Массовое удаление действий requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/ListUserActionsForDelete' } responses: '200': description: OK content: application/json: schema: type: object properties: deleted_ids: { type: array, items: { type: integer } } /user-actions/{instance_id}/: parameters: - { $ref: '#/components/parameters/InstanceId' } patch: tags: [user_actions] summary: Обновить key/value действия requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/UpdateUserAction' } responses: '200': description: OK content: application/json: schema: { $ref: '#/components/schemas/UserActions' } '404': { $ref: '#/components/responses/NotFound' } /tasks/: get: tags: [tasks] summary: Список задач очереди parameters: - { $ref: '#/components/parameters/Limit' } - { $ref: '#/components/parameters/Offset' } - { in: query, name: review_id, schema: { type: integer } } - { in: query, name: reviewer_id, schema: { type: integer } } - { in: query, name: is_active, schema: { type: boolean } } - { in: query, name: resource_id, schema: { type: string } } responses: '200': description: OK content: application/json: schema: { type: array, items: { $ref: '#/components/schemas/Task' } } /tasks/reviewers-max-end-dates/: get: tags: [tasks] summary: Максимальные даты окончания задач по согласующим parameters: - { in: query, name: reviewers_ids, required: true, schema: { type: string }, description: "CSV id" } - { in: query, name: duration, required: true, schema: { type: integer } } responses: '200': description: OK content: application/json: schema: { type: array, items: { $ref: '#/components/schemas/ReviewerMaxTaskEndDate' } } /tasks/{instance_id}/change-priority/: parameters: - { $ref: '#/components/parameters/InstanceId' } patch: tags: [tasks] summary: Изменить приоритет задачи requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/TaskUpdatePriority' } responses: '200': description: OK content: application/json: schema: { $ref: '#/components/schemas/Task' } '404': { $ref: '#/components/responses/NotFound' } /tasks/{instance_id}/change-duration/: parameters: - { $ref: '#/components/parameters/InstanceId' } patch: tags: [tasks] summary: Изменить длительность задачи requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/TaskUpdateDuration' } responses: '200': description: OK content: application/json: schema: { $ref: '#/components/schemas/Task' } '404': { $ref: '#/components/responses/NotFound' } components: securitySchemes: bearerAuth: type: http scheme: bearer bearerFormat: JWT description: "Основной токен. Дополнительно может передаваться заголовок `identity: Bearer ` для режима Zitadel." parameters: Limit: in: query name: limit schema: { type: integer, minimum: 0, default: 1000 } Offset: in: query name: offset schema: { type: integer, minimum: 0, default: 0 } InstanceId: in: path name: instance_id required: true schema: { type: integer } responses: BadRequest: description: Ошибка запроса content: application/json: schema: { $ref: '#/components/schemas/ApiError' } Forbidden: description: Недостаточно прав content: application/json: schema: { $ref: '#/components/schemas/ApiError' } NotFound: description: Не найдено content: application/json: schema: { $ref: '#/components/schemas/ApiError' } JsonObject: description: Готовый JSON-объект (структура зависит от группировки) content: application/json: schema: { type: object, additionalProperties: true } JsonArray: description: Готовый JSON-массив review (сериализуется на стороне сервиса) content: application/json: schema: { type: array, items: { type: object, additionalProperties: true } } ReviewOk: description: OK content: application/json: schema: { $ref: '#/components/schemas/Review' } DocumentOk: description: OK content: application/json: schema: { $ref: '#/components/schemas/Document' } schemas: ApiError: type: object properties: detail: oneOf: [ { type: string }, { type: array, items: { type: object } } ] FlowType: type: string enum: [document, schedule] Action: type: string enum: ['Отсутствует', 'Скопировать в папку'] StatusKey: type: string enum: ['Согласовано', 'Не согласовано'] StateReview: type: string nullable: true enum: ['Начато', 'Открыто', 'Копирование документов', 'На подписании', 'Закрыто', 'Завершено', 'Аннулировано'] StepType: type: string enum: ['Инициализирующий', 'Обычный', 'Финальный'] ChangeCopyPathRole: type: string enum: ['Инициатор при запуске', 'Утверждающий при завершении', 'Инициатор и утверждающий', 'Возможность отсутствует'] AcceptanceByReviewer: type: string enum: [accepted, rejected, partly_rejected] TimeTrackingMode: type: string enum: [auto, manual] CompletionNotificationsMode: type: string enum: [disabled, positive_documents_only, all_documents] TransmittalDocumentsStatus: type: string enum: [accepted_only, all_documents] TransmittalDocumentsType: type: string enum: [copy_only, original_only] BulkListUpdateType: type: string enum: [replace, add] FlowActonAdditions: type: object properties: enable_stamp: { type: boolean } enable_signature: { type: boolean } enable_qr_code: { type: boolean } enable_base_plan: { type: boolean } required: [enable_stamp, enable_signature, enable_qr_code, enable_base_plan] StepChecklistAssignment: type: object properties: required: { type: boolean } reviewers: { type: array, items: { type: string, format: uuid } } required: [required, reviewers] BaseFlow: type: object properties: name: { type: string } description: { type: string, nullable: true } action: { $ref: '#/components/schemas/Action' } additions: { $ref: '#/components/schemas/FlowActonAdditions' } meta_data: { type: object, additionalProperties: true } folder_dst: { type: string, nullable: true } folder_dst_relative: { type: string, nullable: true } folder_label: { type: string, nullable: true } is_active: { type: boolean, nullable: true } count_of_step: { type: integer, minimum: 0 } launchers: { type: array, items: { type: string, format: uuid } } approvers: { type: array, nullable: true, items: { type: string, format: uuid } } company_id: { type: integer } creator_id: { type: integer, nullable: true } changer_id: { type: integer, nullable: true } signatories: { type: array, nullable: true, items: { type: integer } } resource_id: { type: string, format: uuid, nullable: true } steps: { type: array, items: { type: integer } } statuses: { type: array, items: { type: integer } } change_copy_path_role: { $ref: '#/components/schemas/ChangeCopyPathRole' } final_step_duration: { type: integer, nullable: true } step_watchers: { type: object, additionalProperties: { type: array, items: { type: string } } } flow_type: { $ref: '#/components/schemas/FlowType' } finish_step_after_review: { type: boolean } skip_empty_steps: { type: boolean } time_tracking_mode: { $ref: '#/components/schemas/TimeTrackingMode' } attributes: { type: object, additionalProperties: true } unset_attributes: { type: array, items: { type: integer } } completion_notifications_mode: { $ref: '#/components/schemas/CompletionNotificationsMode' } completion_notifications_receivers: { type: array, items: { type: string, format: uuid } } checklists: { type: object, additionalProperties: { $ref: '#/components/schemas/StepChecklistAssignment' } } transmittal_after_review: { type: boolean } transmittal_templates: { type: array, items: { type: string, format: uuid } } transmittal_doc_statuses: { $ref: '#/components/schemas/TransmittalDocumentsStatus' } transmittal_doc_types: { $ref: '#/components/schemas/TransmittalDocumentsType' } folder_dst_ai_assist_enabled: { type: boolean } required: [name, additions, meta_data, count_of_step, launchers, company_id] FlowCreate: allOf: - { $ref: '#/components/schemas/BaseFlow' } Flow: allOf: - { $ref: '#/components/schemas/BaseFlow' } - type: object properties: id: { type: integer } created_at: { type: string, format: date-time } updated_at: { type: string, format: date-time } required: [id, created_at, updated_at] FullFlow: allOf: - { $ref: '#/components/schemas/BaseFlow' } - type: object properties: id: { type: integer } created_at: { type: string, format: date-time } updated_at: { type: string, format: date-time } steps: { type: array, items: { $ref: '#/components/schemas/FullStep' } } statuses: { type: array, items: { $ref: '#/components/schemas/Status' } } required: [id, created_at, updated_at] FlowLight: type: object properties: id: { type: integer } name: { type: string } required: [id, name] CopyFlow: type: object properties: name: { type: string } required: [name] BulkUpdateListUUID4FieldAction: type: object properties: action: { $ref: '#/components/schemas/BulkListUpdateType' } value: { type: array, items: { type: string, format: uuid } } required: [action, value] FlowBulkUpdate: type: object properties: ids: { type: array, items: { type: integer } } watchers: { $ref: '#/components/schemas/BulkUpdateListUUID4FieldAction' } approvers: { $ref: '#/components/schemas/BulkUpdateListUUID4FieldAction' } required: [ids] BaseStep: type: object properties: name: { type: string } duration: { type: integer, minimum: 0 } min_reviewers: { type: integer, nullable: true } all_verify: { type: boolean, default: true } force_close: { type: boolean, default: false } force_close_block_status: { type: boolean, default: false } setup_next_step: { type: boolean, default: false } task_queue: { type: boolean, default: false } set_status: { type: boolean, default: false } blocking_step: { type: boolean, default: false } flow_id: { type: integer } reviewers: { type: array, items: { type: string, format: uuid } } blocking_reviewers: { type: array, items: { type: string, format: uuid } } only_final_step_blocking: { type: boolean, default: false } min_reviewers_by_sa: { type: object, additionalProperties: { type: integer } } close_on_rejection: { type: boolean, default: false } complete_on_rejection: { type: boolean, default: false } complete_on_rejection_reviewers: { type: array, items: { type: string, format: uuid } } checklists: { type: object, additionalProperties: { $ref: '#/components/schemas/StepChecklistAssignment' } } force_next_step_reviewers: { type: array, items: { type: string, format: uuid } } pinned_reviewers: { type: array, items: { type: string, format: uuid } } required: [name, duration, flow_id] Step: allOf: - { $ref: '#/components/schemas/BaseStep' } - type: object properties: id: { type: integer } type: { $ref: '#/components/schemas/StepType' } required: [id, type] FullStep: allOf: - { $ref: '#/components/schemas/Step' } ActiveReview: type: object properties: id: { type: integer } name: { type: string } required: [id, name] UpdatedReviewer: type: object properties: new_account: { type: string, format: uuid } old_account: { type: string, format: uuid, nullable: true } skip: { type: boolean } required: [new_account, skip] BaseStatus: type: object properties: key: { $ref: '#/components/schemas/StatusKey' } value: { type: string } flow_id: { type: integer } required: [value, flow_id] Status: allOf: - { $ref: '#/components/schemas/BaseStatus' } - type: object properties: id: { type: integer } required: [id] StatusForDocument: type: object properties: key: { type: string } value: { type: string } id: { type: integer, nullable: true } is_copied: { type: boolean, nullable: true } bundles_history: { type: array, nullable: true, items: {} } required: [key, value] BaseDocument: type: object properties: document_id: { type: integer, minimum: 1 } bundle_id: { type: string, format: uuid } review_id: { type: integer, minimum: 1 } instance_id: { type: integer, nullable: true } document_type: { type: string, nullable: true } document_copied_id: { type: integer, nullable: true } bundle_copied_id: { type: string, format: uuid, nullable: true } required: [document_id, bundle_id, review_id] Document: allOf: - { $ref: '#/components/schemas/BaseDocument' } - type: object properties: id: { type: integer } status: { $ref: '#/components/schemas/StatusForDocument' } copy_to_folder_dst: { type: string, nullable: true } copy_to_folder_label: { type: string, nullable: true } is_accepted: { type: boolean, nullable: true } statuses: { type: array, nullable: true, items: { $ref: '#/components/schemas/SetDocumentStatusRead' } } acceptance_by_reviewer: { $ref: '#/components/schemas/AcceptanceByReviewer' } required: [id] ExtendDocument: allOf: - { $ref: '#/components/schemas/Document' } - type: object properties: review: { type: string } review_status: { type: string } review_completed_at: { type: string, format: date-time, nullable: true } flow_id: { type: integer } review_comments: { type: array, items: { $ref: '#/components/schemas/ReviewComment' } } required: [review, review_status, flow_id] BaseUpdateDocument: type: object properties: status: { $ref: '#/components/schemas/StatusForDocument' } required: [status] ChangeDocumentCopyPath: type: object properties: id: { type: integer } copy_to_folder_dst: { type: string } copy_to_folder_label: { type: string } required: [id, copy_to_folder_dst, copy_to_folder_label] SetDocumentStatusUpdate: type: object properties: status_id: { type: integer } required: [status_id] SetDocumentStatusRead: type: object properties: id: { type: integer } document_id: { type: integer } user_id: { type: integer } step: { $ref: '#/components/schemas/Step' } status: { $ref: '#/components/schemas/Status' } required: [id, document_id, user_id, step, status] SetDocumentBundleIdAndStatus: type: object properties: bundle_id: { type: string, format: uuid, nullable: true } status: { $ref: '#/components/schemas/StatusForDocument' } UpdateDocumentCopiedIds: type: object properties: document_id: { type: integer } document_copied_id: { type: integer } bundle_copied_id: { type: string, format: uuid } required: [document_id, document_copied_id, bundle_copied_id] DocumentFilterRequest: type: object properties: flow_ids: { type: string, nullable: true } review_ids: { type: string, nullable: true } document_ids: { type: string, nullable: true } bundle_ids: { type: string, nullable: true } review_status: { type: string, nullable: true } document_types: { type: string, nullable: true } document_copied_ids: { type: string, nullable: true } bundle_copied_ids: { type: string, nullable: true } full: { type: boolean, default: false } ReviewComment: type: object properties: comment: { type: string } creator_id: { type: integer } created_at: { type: string, format: date-time } step_name: { type: string } required: [comment, creator_id, created_at, step_name] BaseCreateDocument: type: object properties: document_id: { type: integer, minimum: 1 } bundle_id: { type: string, format: uuid, nullable: true } is_folder: { type: boolean, default: false } instance_id: { type: integer, nullable: true } required: [document_id] BaseReview: type: object properties: name: { type: string } flow_id: { type: integer, minimum: 1 } folder_dst: { type: string, nullable: true } folder_label: { type: string, nullable: true } resource_id: { type: string, format: uuid, nullable: true } meta_data: { type: object, additionalProperties: true } documents: { type: array, items: { $ref: '#/components/schemas/BaseCreateDocument' } } comment: { type: string, nullable: true } attributes: { type: object, additionalProperties: true } checklist_results: { type: object, additionalProperties: { type: array, items: { type: integer } } } required: [name, flow_id] UpdateBaseReview: type: object properties: name: { type: string } flow_id: { type: integer, minimum: 1 } folder_dst: { type: string, nullable: true } folder_label: { type: string, nullable: true } resource_id: { type: string, format: uuid, nullable: true } meta_data: { type: object, additionalProperties: true } attributes: { type: object, additionalProperties: true } required: [name, flow_id] PatchBaseReview: type: object properties: name: { type: string, nullable: true } resource_id: { type: string, format: uuid, nullable: true } status: { $ref: '#/components/schemas/StateReview' } attributes: { type: object, nullable: true, additionalProperties: true } PatchCurrentReviewers: type: object properties: was: { type: string, format: uuid, nullable: true } became: { type: string, format: uuid, nullable: true } required: { type: boolean, default: false } force_next_step_reviewer: { type: boolean, default: false } PatchMinReviewersOnStep: type: object properties: new_value: { type: integer, minimum: 1 } required: [new_value] StatusForReview: type: object properties: status: { $ref: '#/components/schemas/StateReview' } comment: { type: string, nullable: true } duration: { type: integer, minimum: 0, nullable: true } reviewers: { type: array, nullable: true, items: { $ref: '#/components/schemas/PatchCurrentReviewers' } } ReviewersUpdate: type: object properties: id: { type: integer } current_reviewers: { type: array, nullable: true, items: { type: string, format: uuid } } completed_reviewers: { type: array, nullable: true, items: { type: string, format: uuid } } current_signatories: { type: array, nullable: true, items: { type: integer } } completed_signatories: { type: array, nullable: true, items: { type: integer } } required: [id] BulkReviewersUpdate: type: object properties: reviews: { type: array, items: { $ref: '#/components/schemas/ReviewersUpdate' } } required: [reviews] Review: type: object description: | Итоговая структура review. Сериализатор дополнительно раскладывает user_actions по шагам flow и добавляет агрегаты `count_of_document`, `Согласовано`, `Не согласовано`. properties: id: { type: integer } name: { type: string } status: { $ref: '#/components/schemas/StateReview' } folder_dst: { type: string, nullable: true } folder_label: { type: string, nullable: true } current_step: { type: integer, nullable: true } current_step_id: { type: integer } current_step_expired_at: { type: string, format: date-time, nullable: true } created_at: { type: string, format: date-time } updated_at: { type: string, format: date-time } expired_at: { type: string, format: date-time, nullable: true } completed_at: { type: string, format: date-time, nullable: true } min_reviewers: { type: integer, nullable: true } creator_id: { type: integer } resource_id: { type: string, format: uuid, nullable: true } current_reviewers: { type: array, items: { type: string, format: uuid } } completed_reviewers: { type: array, items: { type: string, format: uuid } } current_signatories: { type: array, nullable: true, items: { type: integer } } completed_signatories: { type: array, nullable: true, items: { type: integer } } flow: { $ref: '#/components/schemas/FullFlow' } meta_data: { type: object, additionalProperties: true } current_reviewers_by_sa: { type: object, additionalProperties: { type: integer } } current_reviewers_changes: { type: array, items: { type: string, format: uuid } } time_tracking_mode: { $ref: '#/components/schemas/TimeTrackingMode' } attributes: { type: object, additionalProperties: true } checklist_results: { type: object, additionalProperties: { type: array, items: { type: integer } } } force_next_step_reviewers: { type: array, items: { type: string, format: uuid } } transmittal_ids: { type: array, items: { type: string, format: uuid } } count_of_document: { type: integer } required: [id, name, status, current_step_id, created_at, updated_at, creator_id] ChecklistResultsUpdateRequest: type: object properties: step_id: { type: integer } add_results: { type: array, items: { type: integer } } remove_results: { type: array, items: { type: integer } } required: [step_id] TransmittalForReviewCreated: type: object properties: review_id: { type: integer } transmittal_id: { type: string, format: uuid } required: [review_id, transmittal_id] BaseUserAction: type: object properties: action: { type: string } key: { type: string, nullable: true } value: { type: string } creator_id: { type: integer } step_id: { type: integer } document_id: { type: integer, nullable: true } review_id: { type: integer } required: [action, value, creator_id, step_id, review_id] UserActions: type: object properties: id: { type: integer } action: { type: string } key: { type: string, nullable: true } value: { type: string } creator_id: { type: integer } created_at: { type: string, format: date-time } step_id: { type: integer } document_id: { type: integer, nullable: true } review_id: { type: integer } required: [id, action, value, creator_id, created_at, step_id, review_id] ListUserActionsForDelete: type: object properties: ids: { type: array, items: { type: integer } } required: [ids] UpdateUserAction: type: object properties: key: { type: string, nullable: true } value: { type: string, nullable: true } Task: type: object properties: id: { type: integer } priority: { type: integer, minimum: 1, nullable: true } start_date: { type: string, format: date-time } duration: { type: integer, minimum: 0 } end_date: { type: string, format: date-time } review: { type: object, additionalProperties: true } reviewer_id: { type: integer } is_active: { type: boolean } step_id: { type: integer, nullable: true } review_expired_at: { type: string, format: date-time, nullable: true } current_reviewers: { type: array, items: { type: integer } } completed_reviewers: { type: array, items: { type: integer } } required: [id, start_date, duration, end_date, reviewer_id, is_active] TaskUpdatePriority: type: object properties: priority: { type: integer, minimum: 1 } required: [priority] TaskUpdateDuration: type: object properties: duration: { type: integer, minimum: 1 } required: [duration] TasksCount: type: object properties: tasks_count: { type: integer } required: [tasks_count] ReviewerMaxTaskEndDate: type: object properties: reviewer_id: { type: integer } max_task_end_date: { type: string, format: date-time } required: [reviewer_id, max_task_end_date] MainFilterPostRequest: type: object description: Схема фильтрации для POST /flows/filter/, /reviews/filter/ и подсчётов. properties: created_at_after: { type: string, nullable: true } created_at_before: { type: string, nullable: true } updated_at_after: { type: string, nullable: true } updated_at_before: { type: string, nullable: true } expired_before: { type: string, nullable: true } current_step_expired_before: { type: string, nullable: true } resource_id: { type: array, nullable: true, items: { type: string } } flow_id: { type: array, nullable: true, items: { type: string } } launcher_id: { type: array, nullable: true, items: { type: string } } reviewers_ids: { type: array, nullable: true, items: { type: string } } reviewers_sa: { type: array, nullable: true, items: { type: string } } launcher_sa: { type: array, nullable: true, items: { type: string } } action: { type: array, nullable: true, items: { type: string } } approvers: { type: array, nullable: true, items: { type: string } } step_watchers: { type: array, nullable: true, items: { type: string } } reviewers: { type: array, nullable: true, items: { type: string } } current_reviewers: { type: array, nullable: true, items: { type: string } } completed_reviewers: { type: array, nullable: true, items: { type: string } } signatories: { type: array, nullable: true, items: { type: string } } current_signatories: { type: array, nullable: true, items: { type: string } } completed_signatories: { type: array, nullable: true, items: { type: string } } company_id: { type: string, nullable: true } creator_id: { type: string, nullable: true } changer_id: { type: string, nullable: true } name: { type: string, nullable: true } status: { type: array, nullable: true, items: { $ref: '#/components/schemas/StateReview' } } flow_type: { $ref: '#/components/schemas/FlowType' } full: { type: boolean, default: false } is_active: { type: boolean, nullable: true } enable_stamp: { type: boolean, nullable: true } enable_signature: { type: boolean, nullable: true } enable_qr_code: { type: boolean, nullable: true } has_reviewers: { type: boolean, nullable: true } attributes: { type: object, nullable: true, additionalProperties: true }