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 <jwt>`
    (`src/flow/middleware.py`, `TokenUserMiddleware`). Поддерживаются два режима:

    1. **Zitadel** — если передан заголовок `identity` (`Bearer <jwt>`),
       полезная нагрузка берётся из этого токена
       (`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 <jwt>` для режима 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 }
