openapi: 3.0.3

info:
  title: Inspections
  version: "0.1.0"
  description: |
    REST API сервиса **inspections-backend** (`proc/inspections-backend`) — управление
    событиями/инспекциями (создание, редактирование, поиск, экспорт), их типами,
    статусами, атрибутами, историей изменений и расчётом доступности исполнителей.

    Сервис написан на Python (**FastAPI** + **Piccolo ORM**, PostgreSQL). Приложение
    собирается фабрикой `create_app` в `src/app/http.py`. Помимо HTTP-приложения есть
    отдельный процесс-консьюмер Kafka (`src/app/kafka.py`), который слушает события
    EAV-ассетов; в OpenAPI он не отражён.

    Роутинг: корневой роутер имеет префикс `/api` (`controller/http/api/router.py`),
    вложенный — `/v1` (`controller/http/api/v1/router.py`), ресурс инспекций —
    `/inspections` (`controller/http/api/v1/inspection.py`). За реверс-прокси
    добавляется `root_path` (в k8s — `/inspections`, задаётся `HTTP_APP_ROOT_PATH`).

    Схема OpenAPI отдаётся по `/openapi.json/`, документация ReDoc — по `/docs/`
    (Swagger UI отключён). Админ-панель Piccolo монтируется по `/admin/`
    (если `HTTP_APP_ADMIN_ENABLE=true`).

    ### Аутентификация
    Если включён middleware (`JWT_AUTH_ENABLE=true`), все запросы, кроме `/docs/`,
    `/openapi.json/`, `/admin/`, `/internal/`, требуют заголовок
    `Authorization: Bearer <jwt>`. Поддерживается дополнительный заголовок
    `identity` (метаданные Zitadel): при его наличии полезная нагрузка берётся из
    него, иначе — из `Authorization`. Токен декодируется **без проверки подписи**
    (`verify_signature=False`) — доверие обеспечивается сетевым слоем/ingress.
    При отсутствии заголовка `Authorization` middleware возвращает `401`.

    При `JWT_AUTH_ENABLE=false` middleware не подключается и используется
    захардкоженный дефолтный пользователь (`entity/context.py`) — режим локальной
    разработки.

    ### Права доступа
    Операции защищены правами (`InspectionPermission`): `core.can_view_inspections`,
    `core.can_create_inspection`, `core.can_edit_inspection`,
    `core.can_delete_inspection`, `core.can_view_all_inspections`. Права берутся из
    токена; при их отсутствии проверяются права по сервисным аккаунтам на конкретный
    тип события. Недостаток прав — ответ `403`.

    ### Пагинация
    Списочные ответы используют limit/offset-пагинацию и оборачиваются в `Page`
    (`{ count, result }`). Сортировка задаётся параметрами `order_by` и `ascending`.

    ### Обработка ошибок
    Доменные ошибки маппятся на HTTP-статусы (`controller/http/errors.py`) и
    возвращаются как `{ "detail": "<текст>" }`. Ошибки валидации тела/параметров
    (Pydantic) отдаются FastAPI в стандартном формате `422`.

servers:
  - url: https://api.sarex.io/inspections
    description: production
  - url: https://api.preprod.sarex.io/inspections
    description: preprod
  - url: https://stage-api.sarex.io/inspections
    description: stage
  - url: http://localhost:8000
    description: local (без root_path)

tags:
  - name: Inspections
    description: События / инспекции
  - name: Mobile app
    description: Версии мобильного приложения

security:
  - bearerAuth: []

paths:
  /api/v1/mobile-app/version/:
    get:
      tags: [Mobile app]
      summary: Версии мобильного приложения
      operationId: get_mobile_app_version
      responses:
        "200":
          description: Текущая, рекомендуемая и минимально требуемая версии
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MobileAppVersion"
        "401":
          $ref: "#/components/responses/Unauthorized"

  /api/v1/inspections/:
    get:
      tags: [Inspections]
      summary: Список событий
      description: Требует право `core.can_view_inspections`.
      operationId: get_all
      parameters:
        - $ref: "#/components/parameters/OrderByInspection"
        - $ref: "#/components/parameters/Ascending"
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Offset"
        - $ref: "#/components/parameters/Search"
        - $ref: "#/components/parameters/CompanyIdList"
        - $ref: "#/components/parameters/ResourceIdList"
        - $ref: "#/components/parameters/TypeIdList"
        - $ref: "#/components/parameters/StatusIdList"
      responses:
        "200":
          description: Страница событий
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PageInspectionRead"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
    post:
      tags: [Inspections]
      summary: Создать событие
      description: Требует право `core.can_create_inspection`.
      operationId: create
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/InspectionCreate"
      responses:
        "201":
          description: Созданное событие
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InspectionRead"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "422":
          $ref: "#/components/responses/ValidationError"

  /api/v1/inspections/filter/:
    post:
      tags: [Inspections]
      summary: Список событий (фильтры в теле)
      description: |
        Аналог `GET /api/v1/inspections/` с передачей фильтров и пагинации в теле
        запроса. Требует право `core.can_view_inspections`.
      operationId: post_filter_all
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/InspectionFiltersWithPagination"
      responses:
        "200":
          description: Страница событий
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PageInspectionRead"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "422":
          $ref: "#/components/responses/ValidationError"

  /api/v1/inspections/light/:
    get:
      tags: [Inspections]
      summary: Список событий (облегчённое представление)
      description: Требует право `core.can_view_inspections`.
      operationId: get_all_light
      parameters:
        - $ref: "#/components/parameters/OrderByInspection"
        - $ref: "#/components/parameters/Ascending"
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Offset"
        - $ref: "#/components/parameters/Search"
        - $ref: "#/components/parameters/CompanyIdList"
        - $ref: "#/components/parameters/ResourceIdList"
      responses:
        "200":
          description: Страница облегчённых событий
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PageInspectionLightRead"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"

  /api/v1/inspections/export/:
    get:
      tags: [Inspections]
      summary: Экспорт событий (xlsx)
      description: |
        Возвращает файл экспорта. Поддерживается единственный тип — `xlsx`.
        Требует право `core.can_view_inspections`.
      operationId: export
      parameters:
        - name: type
          in: query
          required: false
          schema:
            $ref: "#/components/schemas/InspectionExportType"
        - $ref: "#/components/parameters/Search"
        - $ref: "#/components/parameters/CompanyIdList"
        - $ref: "#/components/parameters/ResourceIdList"
        - $ref: "#/components/parameters/TypeIdList"
        - $ref: "#/components/parameters/StatusIdList"
      responses:
        "200":
          description: Файл экспорта
          headers:
            Content-Disposition:
              schema:
                type: string
              example: attachment; filename=inspections_export.xlsx
          content:
            application/vnd.openxmlformats-officedocument.spreadsheetml.sheet:
              schema:
                type: string
                format: binary
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"

  /api/v1/inspections/types/:
    get:
      tags: [Inspections]
      summary: Типы событий компании
      description: Требует право `core.can_view_inspections`.
      operationId: get_types
      parameters:
        - name: company_id
          in: query
          required: true
          description: ID компании
          schema:
            type: integer
            example: 1
      responses:
        "200":
          description: Список типов событий (полное представление)
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/InspectionTypeFullRead"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"

  /api/v1/inspections/status-count/:
    get:
      tags: [Inspections]
      summary: Счётчики по статусам (фильтры в query)
      description: Требует право `core.can_view_inspections`.
      operationId: get_status_count
      parameters:
        - $ref: "#/components/parameters/Search"
        - $ref: "#/components/parameters/CompanyIdList"
        - $ref: "#/components/parameters/ResourceIdList"
        - $ref: "#/components/parameters/TypeIdList"
        - $ref: "#/components/parameters/StatusIdList"
      responses:
        "200":
          description: Счётчики по статусам, сгруппированные по ресурсам
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/InspectionStatusCountRead"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
    post:
      tags: [Inspections]
      summary: Счётчики по статусам (фильтры в теле)
      description: Требует право `core.can_view_inspections`.
      operationId: post_filter_status_count
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/InspectionFilters"
      responses:
        "200":
          description: Счётчики по статусам, сгруппированные по ресурсам
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/InspectionStatusCountRead"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "422":
          $ref: "#/components/responses/ValidationError"

  /api/v1/inspections/unavailable-dates/:
    post:
      tags: [Inspections]
      summary: Недоступные даты для исполнителей
      description: Требует право `core.can_view_inspections`.
      operationId: get_unavailable_dates
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/InspectionUnavailableDatesRequest"
      responses:
        "200":
          description: Список отрезков недоступных дат
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/InspectionUnavailableDatesRead"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "422":
          $ref: "#/components/responses/ValidationError"

  /api/v1/inspections/unavailable-users/:
    post:
      tags: [Inspections]
      summary: Недоступные исполнители на интервал
      description: Требует право `core.can_view_inspections`.
      operationId: get_unavailable_users
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/InspectionUnavailableUsersRequest"
      responses:
        "200":
          description: Список недоступных пользователей
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InspectionUnavailableUsersRead"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "422":
          $ref: "#/components/responses/ValidationError"

  /api/v1/inspections/change-history/:
    get:
      tags: [Inspections]
      summary: История изменений событий
      description: Требует право `core.can_view_inspections`.
      operationId: get_change_history
      parameters:
        - $ref: "#/components/parameters/OrderByChangeHistory"
        - $ref: "#/components/parameters/Ascending"
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Offset"
        - name: created_at_after
          in: query
          schema: { type: string, format: date-time, nullable: true }
        - name: created_at_before
          in: query
          schema: { type: string, format: date-time, nullable: true }
        - name: updated_at_after
          in: query
          schema: { type: string, format: date-time, nullable: true }
        - name: updated_at_before
          in: query
          schema: { type: string, format: date-time, nullable: true }
        - name: inspection_public_id
          in: query
          schema:
            type: array
            nullable: true
            items: { type: string, format: uuid }
        - $ref: "#/components/parameters/CompanyIdList"
        - $ref: "#/components/parameters/ResourceIdList"
      responses:
        "200":
          description: Страница записей истории изменений
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PageInspectionChangeRecordRead"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"

  /api/v1/inspections/filter-options/:
    post:
      tags: [Inspections]
      summary: Доступные значения фильтров
      description: |
        Возвращает возможные значения для перечисленных полей фильтра.
        Требует право `core.can_view_inspections`.
      operationId: get_filter_options
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/InspectionFilterOptionsRequest"
      responses:
        "200":
          description: Маппинг «поле фильтра → список значений»
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InspectionFilterOptionsRead"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "422":
          $ref: "#/components/responses/ValidationError"

  /api/v1/inspections/aggregate/{field_name}/{aggregate_func}/:
    get:
      tags: [Inspections]
      summary: Агрегация по полю события
      description: Требует право `core.can_view_inspections`.
      operationId: aggregate
      parameters:
        - name: field_name
          in: path
          required: true
          schema:
            $ref: "#/components/schemas/InspectionAggregateField"
        - name: aggregate_func
          in: path
          required: true
          schema:
            $ref: "#/components/schemas/AggregateFunc"
        - $ref: "#/components/parameters/Search"
        - $ref: "#/components/parameters/CompanyIdList"
        - $ref: "#/components/parameters/ResourceIdList"
      responses:
        "200":
          description: Результат агрегации
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AggregateResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"

  /api/v1/inspections/{instance_id}/:
    get:
      tags: [Inspections]
      summary: Событие по id
      description: Требует право `core.can_view_inspections`.
      operationId: retrieve
      parameters:
        - $ref: "#/components/parameters/InstanceId"
      responses:
        "200":
          description: Событие
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InspectionRead"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
    patch:
      tags: [Inspections]
      summary: Частичное обновление события
      description: Требует право `core.can_edit_inspection`.
      operationId: partial_update
      parameters:
        - $ref: "#/components/parameters/InstanceId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/InspectionPartialUpdate"
      responses:
        "200":
          description: Обновлённое событие
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InspectionRead"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "422":
          $ref: "#/components/responses/ValidationError"
    delete:
      tags: [Inspections]
      summary: Удалить событие
      description: Требует право `core.can_delete_inspection`.
      operationId: delete
      parameters:
        - $ref: "#/components/parameters/InstanceId"
      responses:
        "204":
          description: Удалено
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: |
        JWT в заголовке `Authorization: Bearer <jwt>`. Опционально —
        дополнительный заголовок `identity: Bearer <jwt>` (метаданные Zitadel).
        Подпись токена не проверяется.

  parameters:
    InstanceId:
      name: instance_id
      in: path
      required: true
      description: Публичный ID (UUID) события
      schema:
        type: string
        format: uuid
    Limit:
      name: limit
      in: query
      description: Максимальное количество объектов
      schema: { type: integer, default: 100 }
    Offset:
      name: offset
      in: query
      description: Количество пропущенных объектов
      schema: { type: integer, default: 0 }
    Ascending:
      name: ascending
      in: query
      description: Сортировка по возрастанию
      schema: { type: boolean, default: true }
    OrderByInspection:
      name: order_by
      in: query
      description: Поле сортировки
      schema:
        type: string
        enum: [id, inspection_dt]
        default: id
    OrderByChangeHistory:
      name: order_by
      in: query
      description: Поле сортировки
      schema:
        type: string
        enum: [id, created_at]
        default: id
    Search:
      name: search
      in: query
      description: Поиск по названию, локации и описанию
      schema: { type: string, nullable: true }
    CompanyIdList:
      name: company_id
      in: query
      description: ID компании
      schema:
        type: array
        nullable: true
        items: { type: integer }
    ResourceIdList:
      name: resource_id
      in: query
      description: ID ресурса (проекта)
      schema:
        type: array
        nullable: true
        items: { type: string, format: uuid }
    TypeIdList:
      name: type_id
      in: query
      description: ID типа
      schema:
        type: array
        nullable: true
        items: { type: integer }
    StatusIdList:
      name: status_id
      in: query
      description: ID статуса
      schema:
        type: array
        nullable: true
        items: { type: integer }

  responses:
    BadRequest:
      description: Некорректный запрос
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/HTTPError"
          example: { detail: "Bad Request" }
    Unauthorized:
      description: Не аутентифицирован
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/HTTPError"
          example: { detail: "Authorization header is required" }
    Forbidden:
      description: Недостаточно прав
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/HTTPError"
          example: { detail: "Forbidden" }
    NotFound:
      description: Ресурс не найден
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/HTTPError"
          example: { detail: "Not Found" }
    ValidationError:
      description: Ошибка валидации (Pydantic / FastAPI)
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/HTTPValidationError"

  schemas:
    HTTPError:
      type: object
      properties:
        detail:
          type: string
          description: Описание ошибки
      required: [detail]

    HTTPValidationError:
      type: object
      properties:
        detail:
          type: array
          items:
            type: object
            properties:
              loc:
                type: array
                items:
                  anyOf:
                    - { type: string }
                    - { type: integer }
              msg: { type: string }
              type: { type: string }

    MobileAppVersion:
      type: object
      properties:
        current_version: { type: string, example: "1.0.0" }
        recommended_version: { type: string, example: "1.0.0" }
        required_version: { type: string, example: "1.0.0" }
      required: [current_version, recommended_version, required_version]

    AttributeValueType:
      description: Значение атрибута (одиночное)
      nullable: true
      anyOf:
        - { type: boolean }
        - { type: integer }
        - { type: number }
        - { type: string }
        - { type: array, items: { type: integer } }

    AttributeMultivalueType:
      description: Мультизначение атрибута
      type: array
      items:
        nullable: true
        anyOf:
          - { type: boolean }
          - { type: integer }
          - { type: number }
          - { type: string }

    InspectionExportType:
      type: string
      enum: [xlsx]
      default: xlsx

    InspectionAggregateField:
      type: string
      enum: [created_at, inspection_dt]

    AggregateFunc:
      type: string
      enum: [minmax]

    AggregateResponse:
      type: object
      properties:
        min:
          nullable: true
          description: Минимальное значение
          example: 0
        max:
          nullable: true
          description: Максимальное значение
          example: 1

    AllowedToSetRole:
      type: string
      enum: [author, responsible_user, author_or_responsible_user]

    SetRole:
      type: string
      enum: [author, responsible_user]

    InspectionFilters:
      type: object
      description: Фильтры выборки событий
      properties:
        search: { type: string, nullable: true, description: Поиск по названию, локации и описанию }
        created_at_after: { type: string, format: date-time, nullable: true }
        created_at_before: { type: string, format: date-time, nullable: true }
        updated_at_after: { type: string, format: date-time, nullable: true }
        updated_at_before: { type: string, format: date-time, nullable: true }
        public_id:
          type: array
          nullable: true
          items: { type: string, format: uuid }
        company_id:
          type: array
          nullable: true
          items: { type: integer, minimum: 1 }
        resource_id:
          type: array
          nullable: true
          items: { type: string, format: uuid }
        premise_id:
          type: array
          nullable: true
          items: { type: string, format: uuid }
        author_id:
          type: array
          nullable: true
          items: { type: integer, minimum: 1 }
        inspection_dt_after: { type: string, format: date-time, nullable: true }
        inspection_dt_before: { type: string, format: date-time, nullable: true }
        inspection_dt_end_after: { type: string, format: date-time, nullable: true }
        inspection_dt_end_before: { type: string, format: date-time, nullable: true }
        responsible_user_id:
          type: array
          nullable: true
          items: { type: integer, minimum: 1 }
        type_id:
          type: array
          nullable: true
          items: { type: integer }
        status_id:
          type: array
          nullable: true
          items: { type: integer }
        attributes:
          type: object
          nullable: true
          description: >-
            Маппинг id атрибута → значение. Может передаваться JSON-строкой.
          additionalProperties:
            oneOf:
              - $ref: "#/components/schemas/AttributeValueType"
              - $ref: "#/components/schemas/AttributeMultivalueType"

    InspectionFiltersWithPagination:
      allOf:
        - $ref: "#/components/schemas/InspectionFilters"
        - type: object
          properties:
            order_by:
              type: string
              enum: [id, inspection_dt]
              default: id
            ascending: { type: boolean, default: true }
            limit: { type: integer, default: 100 }
            offset: { type: integer, default: 0 }

    InspectionExportFilters:
      allOf:
        - $ref: "#/components/schemas/InspectionFilters"
        - type: object
          properties:
            type:
              $ref: "#/components/schemas/InspectionExportType"

    InspectionFilterOptionsRequest:
      type: object
      properties:
        filters:
          $ref: "#/components/schemas/InspectionFilters"
        fields:
          type: array
          description: Список полей, для которых нужно получить значения
          items: { type: string }
          example: [author_id, company_id]
      required: [fields]

    InspectionFilterOptionsRead:
      type: object
      properties:
        options:
          type: object
          description: Маппинг «поле фильтра» → «список значений»
          additionalProperties: true
          example: { company_id: [1, 2, 3, null] }
      required: [options]

    AssetAttributeCreate:
      type: object
      properties:
        attribute_id: { type: integer, example: 1 }
        asset_id: { type: string, example: "1" }
        order: { type: integer, example: 1 }
      required: [attribute_id, asset_id, order]

    AssetAttributeRead:
      allOf:
        - $ref: "#/components/schemas/AssetAttributeCreate"
        - type: object
          properties:
            value:
              $ref: "#/components/schemas/AttributeValueType"
          required: [value]

    AttributeRead:
      type: object
      properties:
        id: { type: integer, example: 1 }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
        attribute_id: { type: integer, example: 1 }
        attribute_name: { type: string, example: "Название атрибута" }
        asset_id: { type: string, nullable: true, example: "1" }
        asset_name: { type: string, nullable: true, example: "Название ассета" }
        root_asset_id: { type: string, nullable: true, example: "1" }
        required: { type: boolean, example: false }
        resource_id: { type: string, format: uuid, nullable: true }
      required: [id, created_at, updated_at, attribute_id, attribute_name, required]

    AttachmentsChange:
      type: object
      description: Состояние вложений до и после изменения
      properties:
        before:
          type: array
          items: { type: string }
        after:
          type: array
          items: { type: string }
      example: { before: ["report.pdf"], after: ["report.pdf", "photo.jpg"] }

    StatusSettingRead:
      type: object
      properties:
        id: { type: integer, example: 1 }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
        inspection_status_id: { type: integer, example: 1 }
        set_service_account_id: { type: string, format: uuid, nullable: true }
        set_role:
          nullable: true
          allOf: [{ $ref: "#/components/schemas/SetRole" }]
        comment_required: { type: boolean, example: false }
      required: [id, created_at, updated_at, inspection_status_id, comment_required]

    StatusRead:
      type: object
      properties:
        id: { type: integer, example: 1 }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
        inspection_status_model_id: { type: integer, example: 1 }
        name: { type: string, example: "Название статуса" }
        color: { type: string, example: "#000000" }
        is_final: { type: boolean, example: false }
        allowed_to_set:
          nullable: true
          deprecated: true
          allOf: [{ $ref: "#/components/schemas/AllowedToSetRole" }]
        comment_required: { type: boolean, deprecated: true, example: false }
        set_status_ids:
          type: array
          items: { type: integer }
          example: [1, 2, 3]
        settings:
          type: array
          items: { $ref: "#/components/schemas/StatusSettingRead" }
      required: [id, created_at, updated_at, inspection_status_model_id, name, color, is_final, comment_required]

    StatusWithCountRead:
      allOf:
        - $ref: "#/components/schemas/StatusRead"
        - type: object
          properties:
            count: { type: integer, example: 1 }
          required: [count]

    StatusModelRead:
      type: object
      properties:
        id: { type: integer, example: 1 }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
        inspection_type_id: { type: integer, example: 1 }
        resource_id: { type: string, format: uuid, nullable: true }
        statuses:
          type: array
          items: { $ref: "#/components/schemas/StatusRead" }
      required: [id, created_at, updated_at, inspection_type_id]

    InspectionTypePermissionsRead:
      type: object
      properties:
        id: { type: integer, example: 1 }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
        inspection_type_id: { type: integer, example: 1 }
        service_account_id: { type: string, format: uuid }
        permissions:
          type: array
          items: { $ref: "#/components/schemas/InspectionPermission" }
      required: [id, created_at, updated_at, inspection_type_id, service_account_id]

    InspectionPermission:
      type: string
      enum:
        - core.can_create_inspection
        - core.can_view_inspections
        - core.can_edit_inspection
        - core.can_delete_inspection
        - core.can_view_all_inspections

    InspectionTypeLightRead:
      type: object
      properties:
        id: { type: integer, example: 1 }
        issue_types:
          type: array
          items: { type: integer }
          example: [1, 2]
      required: [id]

    InspectionTypeRead:
      type: object
      properties:
        id: { type: integer, example: 1 }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
        company_id: { type: integer, example: 1 }
        name: { type: string, example: "Название типа события" }
        event_duration:
          type: integer
          description: Длительность события в минутах
          example: 30
        columns_order:
          type: array
          items: { type: string }
          example: [name, description, "attribute[1]"]
        issue_types:
          type: array
          items: { type: integer }
          example: [1, 2]
      required: [id, created_at, updated_at, company_id, name, event_duration]

    InspectionTypeFullRead:
      allOf:
        - $ref: "#/components/schemas/InspectionTypeRead"
        - type: object
          properties:
            permissions:
              type: array
              items: { $ref: "#/components/schemas/InspectionTypePermissionsRead" }
            status_models:
              type: array
              items: { $ref: "#/components/schemas/StatusModelRead" }
            attributes:
              type: array
              items: { $ref: "#/components/schemas/AttributeRead" }
            inspection_dt_overlap_allowed: { type: boolean, example: false }
            inspection_dt_current_day_allowed: { type: boolean, example: false }
          required: [inspection_dt_overlap_allowed, inspection_dt_current_day_allowed]

    InspectionCreate:
      type: object
      properties:
        company_id: { type: integer, minimum: 1, example: 1 }
        resource_id: { type: string, format: uuid }
        name: { type: string, minLength: 1, maxLength: 255, example: "Название события" }
        inspection_dt:
          type: string
          format: date-time
          description: Дата/время проведения (обнуляются секунды; не в прошлом)
        inspection_dt_end: { type: string, format: date-time, nullable: true }
        responsible_users:
          type: array
          minItems: 1
          items: { type: integer, minimum: 1 }
          example: [1, 2]
        location: { type: string, nullable: true }
        description: { type: string, nullable: true }
        type_id: { type: integer, minimum: 1, example: 1 }
        status_id: { type: integer, nullable: true, example: 1 }
        attributes:
          type: object
          additionalProperties:
            $ref: "#/components/schemas/AttributeMultivalueType"
          example: { "1": [null], "2": [true, false], "3": [1, 2] }
        asset_attributes:
          type: array
          items: { $ref: "#/components/schemas/AssetAttributeCreate" }
        premise_id: { type: string, format: uuid, nullable: true }
      required: [company_id, resource_id, name, inspection_dt, responsible_users, type_id]

    InspectionPartialUpdate:
      type: object
      description: Все поля опциональны
      properties:
        name: { type: string, minLength: 1, maxLength: 255, nullable: true }
        inspection_dt: { type: string, format: date-time, nullable: true }
        inspection_dt_end: { type: string, format: date-time, nullable: true }
        responsible_users:
          type: array
          minItems: 1
          nullable: true
          items: { type: integer, minimum: 1 }
        location: { type: string, nullable: true }
        description: { type: string, nullable: true }
        type_id: { type: integer, nullable: true }
        status_id: { type: integer, nullable: true }
        attributes:
          type: object
          nullable: true
          additionalProperties:
            $ref: "#/components/schemas/AttributeMultivalueType"
        asset_attributes:
          type: array
          nullable: true
          items: { $ref: "#/components/schemas/AssetAttributeCreate" }
        status_comment: { type: string, nullable: true }
        attachments:
          nullable: true
          allOf: [{ $ref: "#/components/schemas/AttachmentsChange" }]
        premise_id: { type: string, format: uuid, nullable: true }

    InspectionLightRead:
      type: object
      properties:
        public_id: { type: string, format: uuid }
        name: { type: string, example: "Название события" }
        type:
          $ref: "#/components/schemas/InspectionTypeLightRead"
      required: [public_id, name, type]

    InspectionRead:
      type: object
      properties:
        id: { type: integer, example: 1 }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
        public_id: { type: string, format: uuid }
        company_id: { type: integer, example: 1 }
        resource_id: { type: string, format: uuid }
        author_id: { type: integer, example: 1 }
        name: { type: string, example: "Название события" }
        inspection_dt: { type: string, format: date-time }
        inspection_dt_end: { type: string, format: date-time }
        responsible_users:
          type: array
          items: { type: integer }
          example: [1, 2]
        location: { type: string, nullable: true }
        description: { type: string, nullable: true }
        type:
          $ref: "#/components/schemas/InspectionTypeRead"
        status:
          $ref: "#/components/schemas/StatusRead"
        attributes:
          type: object
          additionalProperties:
            $ref: "#/components/schemas/AttributeMultivalueType"
        asset_attributes:
          type: array
          items: { $ref: "#/components/schemas/AssetAttributeRead" }
        premise_id: { type: string, format: uuid, nullable: true }
      required:
        - id
        - created_at
        - updated_at
        - public_id
        - company_id
        - resource_id
        - author_id
        - name
        - inspection_dt
        - inspection_dt_end
        - responsible_users
        - type
        - status
        - attributes
        - premise_id

    InspectionStatusCountRead:
      type: object
      properties:
        resource_id: { type: string, format: uuid }
        statuses:
          type: array
          items: { $ref: "#/components/schemas/StatusWithCountRead" }
      required: [resource_id, statuses]

    InspectionUnavailableDatesRequest:
      type: object
      properties:
        company_id: { type: integer, minimum: 1, nullable: true, example: 1 }
        responsible_users:
          type: array
          minItems: 1
          items: { type: integer, minimum: 1 }
          example: [1, 2]
        excluded_inspection_ids:
          type: array
          items: { type: integer }
          example: [1, 2]
      required: [responsible_users]

    InspectionUnavailableDatesRead:
      type: object
      properties:
        unavailable_from: { type: string, format: date-time }
        unavailable_to: { type: string, format: date-time }
      required: [unavailable_from, unavailable_to]

    InspectionUnavailableUsersRequest:
      type: object
      properties:
        company_id: { type: integer, minimum: 1, nullable: true, example: 1 }
        inspection_dt: { type: string, format: date-time }
        inspection_dt_end: { type: string, format: date-time, nullable: true }
        users:
          type: array
          minItems: 1
          items: { type: integer, minimum: 1 }
          example: [1, 2]
      required: [inspection_dt, users]

    InspectionUnavailableUsersRead:
      type: object
      properties:
        unavailable_users:
          type: array
          items: { type: integer }
          example: [1, 2]
      required: [unavailable_users]

    InspectionChangeRecordRead:
      type: object
      properties:
        id: { type: integer, example: 1 }
        created_at: { type: string, format: date-time }
        inspection_id: { type: integer, example: 1 }
        created_by: { type: integer, example: 1023 }
        field_name: { type: string, example: "name" }
        attribute_name: { type: string, nullable: true, example: "Локация" }
        was:
          oneOf:
            - $ref: "#/components/schemas/AttributeValueType"
            - $ref: "#/components/schemas/AttributeMultivalueType"
        became:
          oneOf:
            - $ref: "#/components/schemas/AttributeValueType"
            - $ref: "#/components/schemas/AttributeMultivalueType"
        was_text: { type: string, nullable: true }
        became_text: { type: string, nullable: true }
      required: [id, created_at, inspection_id, created_by, field_name, attribute_name, was, became, was_text, became_text]

    PageInspectionRead:
      type: object
      properties:
        count: { type: integer, example: 1 }
        result:
          type: array
          items: { $ref: "#/components/schemas/InspectionRead" }
      required: [count, result]

    PageInspectionLightRead:
      type: object
      properties:
        count: { type: integer, example: 1 }
        result:
          type: array
          items: { $ref: "#/components/schemas/InspectionLightRead" }
      required: [count, result]

    PageInspectionChangeRecordRead:
      type: object
      properties:
        count: { type: integer, example: 1 }
        result:
          type: array
          items: { $ref: "#/components/schemas/InspectionChangeRecordRead" }
      required: [count, result]
