openapi: 3.1.0
info:
  title: Fulcore OMS — External API
  version: 1.1.0
  summary: Внешний продуктовый API платформы фулфилмента Fulcore
  description: |
    # Введение

    Ресурс-ориентированный API `/v1` для интеграции с платформой фулфилмента Fulcore:
    каталог и цены, заказы, исполнение и доставка, остатки, возвраты, платежи и счета,
    исходящие вебхуки.

    Имена ресурсов — принятые англоязычные термины OMS/fulfillment. Денежные суммы —
    целое число в минорных единицах валюты (например, копейки) + код валюты ISO-4217.

    ## Соглашения
    - **Идемпотентность:** заголовок `Idempotency-Key` на создании и командах с внешним
      эффектом. Область действия ключа — аккаунт + метод + путь; TTL — 24 часа. Повтор с
      тем же ключом и телом возвращает исходный результат (тот же код и тело), операция
      не выполняется повторно; тот же ключ с иным телом → `409`. Если исходный запрос
      завершился ошибкой (`5xx`/сетевой сбой), повтор с тем же ключом безопасен.
    - **Оптимистичная блокировка:** `If-Match: <version>` на `PATCH /orders/*` (у заказа
      есть поле `version`). Прочие `PATCH` версионирования не требуют. Команды смены
      состояния (`confirm`, `submit`, `cancel`) проверяются по текущему состоянию заказа
      и возвращают `409` при недопустимом переходе.
    - **Статусы и жизненный цикл:** статусы документов вычисляются платформой —
      произвольная установка статуса извне не предусмотрена. Документ, собранный в
      статусе `DRAFT`, переводится в первый рабочий статус командой `activate`
      (напр. `POST /purchase-orders/{id}/activate`); дальнейшие переходы — результат
      бизнес-событий и команд.
    - **Пагинация:** курсорная по умолчанию (`?cursor=&limit=` → `meta.nextCursor`;
      `null` — следующей страницы нет). Инкрементальная синхронизация — `?updatedSince=`
      на списковых ресурсах (записи, изменённые после момента).
    - **Формы URL:** команда над ресурсом — `POST /{resource}/{id}/{action}`
      (`/orders/{id}/activate`); RPC-глагол над коллекцией/доменом — `POST /{resource}:{verb}`
      (`/pricing:quote`, `/skus:batchUpsert`).
    - **Ошибки:** бизнес-валидации — `4xx`, не `5xx`. Конверт ответа `{ data, error }`.
    - **Аутентификация и лимиты:** любой эндпоинт может вернуть `401` (нет/истёк токен),
      `403` (недостаточно прав), `429` (лимит запросов, заголовок `Retry-After`) или `5xx`
      (временный сбой) — со стандартным конвертом ошибки `{ data, error }`.
    - **Вебхуки:** `POST` на ваш URL с конвертом события. Подпись: заголовок
      `X-Fulcore-Signature: sha256=<hex>` — HMAC-SHA256 по строке `<timestamp>.<тело>`
      с ключом `secret`; `X-Fulcore-Timestamp` — unix-секунды (допуск ±5 минут, защита от
      replay). Повторная доставка при ответе не `2xx`; дедупликация по `id` события.
  contact:
    name: Fulcore
    url: https://fulcore.ru
servers:
  - url: https://api.fulcore.ru/v1
    description: Продакшн
  - url: https://sandbox.api.fulcore.ru/v1
    description: Песочница

security:
  - bearerAuth: []

tags:
  - name: Продажи
    description: Заказы, подтверждение, отмена, возвраты
  - name: Фулфилмент
    description: Исполнение заказов — отгрузки, отправления, передача перевозчику
  - name: Остатки
    description: Остатки, резервы, корректировки, перемещения, комплектация
  - name: Закупки
    description: Закупки у поставщиков, приёмка
  - name: Каталог
    description: Товары, SKU, категории
  - name: Цены
    description: Прайс-листы, промоакции, расчёт цены
  - name: Доставка
    description: Расчёт стоимости доставки и выбор пунктов выдачи
  - name: Финансы
    description: Платежи, наложенный платёж, баланс, счета
  - name: Справочники
    description: Склады, юрлица, контрагенты — справочники для ссылочных полей
  - name: Платформа
    description: Профиль, конфигурация, отчёты, исходящие вебхуки

paths:
  /orders:
    get:
      tags: [Продажи]
      operationId: listOrders
      summary: Список заказов
      parameters:
        - $ref: '#/components/parameters/cursor'
        - $ref: '#/components/parameters/limit'
        - $ref: '#/components/parameters/updatedSince'
        - name: status
          in: query
          description: Фильтр по статусу заказа
          schema: { $ref: '#/components/schemas/OrderStatus' }
        - name: q
          in: query
          description: Поиск (номер, получатель, телефон, трек-номер)
          schema: { type: string }
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Страница заказов
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: '#/components/schemas/Order' }
                  meta: { $ref: '#/components/schemas/PageMeta' }
                  error: { type: 'null' }
    post:
      tags: [Продажи]
      operationId: createOrder
      summary: Создать заказ
      parameters:
        - $ref: '#/components/parameters/idempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/OrderCreate' }
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '201':
          description: Заказ создан
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/Order' }
                  error: { type: 'null' }
        '409': { $ref: '#/components/responses/Conflict' }
        '422': { $ref: '#/components/responses/Unprocessable' }

  /orders/{id}:
    parameters:
      - $ref: '#/components/parameters/idPath'
    get:
      tags: [Продажи]
      operationId: getOrder
      summary: Получить заказ
      parameters:
        - name: include
          in: query
          description: 'Включить связанные данные: `lines`, `statusHistory` (через запятую)'
          schema: { type: string, examples: ['lines,statusHistory'] }
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Заказ
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/Order' }
                  error: { type: 'null' }
        '404': { $ref: '#/components/responses/NotFound' }
    patch:
      tags: [Продажи]
      operationId: updateOrder
      summary: Редактировать заказ
      description: Набор разрешённых для изменения полей зависит от текущего статуса заказа.
      parameters:
        - $ref: '#/components/parameters/ifMatch'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/OrderPatch' }
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Обновлённый заказ
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/Order' }
                  error: { type: 'null' }
        '404': { $ref: '#/components/responses/NotFound' }
        '409': { $ref: '#/components/responses/Conflict' }

  /orders/{id}/cancel:
    post:
      tags: [Продажи]
      operationId: cancelOrder
      summary: Отменить заказ
      description: |
        Отменяет заказ. В ответе возвращаются предупреждения; блокирующие
        предупреждения (`blocking: true`) означают, что отмена не выполнена.
      parameters:
        - $ref: '#/components/parameters/idPath'
        - $ref: '#/components/parameters/idempotencyKey'
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                reason: { type: string, description: Причина отмены }
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Результат отмены
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      canceled: { type: boolean, description: 'false — отмена заблокирована (см. warnings)' }
                      status: { $ref: '#/components/schemas/OrderStatus' }
                      warnings:
                        type: array
                        items: { $ref: '#/components/schemas/Warning' }
                  error: { type: 'null' }
        '404': { $ref: '#/components/responses/NotFound' }
        '409': { $ref: '#/components/responses/Conflict' }

  /orders/{id}/activate:
    post:
      tags: [Продажи]
      operationId: activateOrder
      summary: Активировать заказ
      description: Переводит заказ из `DRAFT` в первый рабочий статус (`NEW`). До активации заказ можно дособирать/редактировать.
      parameters:
        - $ref: '#/components/parameters/idPath'
        - $ref: '#/components/parameters/idempotencyKey'
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Заказ активирован
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/Order' }
                  error: { type: 'null' }
        '404': { $ref: '#/components/responses/NotFound' }
        '409': { $ref: '#/components/responses/Conflict' }
        '422': { $ref: '#/components/responses/Unprocessable' }

  /orders/{id}/confirm:
    post:
      tags: [Продажи]
      operationId: confirmOrder
      summary: Подтвердить заказ
      description: Подтверждение состава и контактных данных заказа.
      parameters:
        - $ref: '#/components/parameters/idPath'
        - $ref: '#/components/parameters/idempotencyKey'
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Заказ подтверждён
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/Order' }
                  error: { type: 'null' }
        '404': { $ref: '#/components/responses/NotFound' }
        '409': { $ref: '#/components/responses/Conflict' }

  /orders/{id}/submit:
    post:
      tags: [Продажи]
      operationId: submitOrder
      summary: Отправить заказ на фулфилмент-обработку (принудительно)
      description: |
        Принудительно передаёт заказ на фулфилмент-обработку (`READY_TO_PACK`), если он
        не ушёл в исполнение автоматически — застрял на этапе оформления (например,
        `CONFIRM_HOLD` или `AWAITING_PAYMENT` при постоплате). В штатном потоке переход в
        исполнение происходит автоматически после подтверждения и оплаты; этот вызов —
        ручной обход для таких случаев. При нарушении бизнес-правил — `422` со списком `issues`.
      parameters:
        - $ref: '#/components/parameters/idPath'
        - $ref: '#/components/parameters/idempotencyKey'
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Заказ передан в исполнение
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/Order' }
                  error: { type: 'null' }
        '404': { $ref: '#/components/responses/NotFound' }
        '409': { $ref: '#/components/responses/Conflict' }
        '422': { $ref: '#/components/responses/Unprocessable' }

  /orders/{id}/refresh-reservation:
    post:
      tags: [Продажи]
      operationId: refreshOrderReservation
      summary: Перерезервировать позиции заказа
      description: |
        Повторная попытка резервирования при нехватке остатка (`INSUFFICIENT_RESERVE`) —
        например, после пополнения стока или корректировки позиций.
      parameters:
        - $ref: '#/components/parameters/idPath'
        - $ref: '#/components/parameters/idempotencyKey'
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Резерв пересчитан
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/Order' }
                  error: { type: 'null' }
        '404': { $ref: '#/components/responses/NotFound' }
        '409': { $ref: '#/components/responses/Conflict' }
        '422': { $ref: '#/components/responses/Unprocessable' }

  /orders/{id}/mark-paid:
    post:
      tags: [Продажи]
      operationId: markOrderPaid
      summary: Отметить заказ оплаченным
      description: |
        Фиксирует оплату, прошедшую вне платформы (перевод, наличные, счёт) — для
        заказов, ожидающих оплаты (`AWAITING_PAYMENT`) при предоплатных методах.
      parameters:
        - $ref: '#/components/parameters/idPath'
        - $ref: '#/components/parameters/idempotencyKey'
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                amount: { $ref: '#/components/schemas/Money' }
                comment: { type: string }
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Оплата зафиксирована
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/Order' }
                  error: { type: 'null' }
        '404': { $ref: '#/components/responses/NotFound' }
        '409': { $ref: '#/components/responses/Conflict' }

  /orders/{id}/line-items:
    put:
      tags: [Продажи]
      operationId: setOrderLineItems
      summary: Заменить позиции заказа
      parameters:
        - $ref: '#/components/parameters/idPath'
        - $ref: '#/components/parameters/ifMatch'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                lines:
                  type: array
                  minItems: 1
                  items: { $ref: '#/components/schemas/OrderLineInput' }
              required: [lines]
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Позиции обновлены
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/Order' }
                  error: { type: 'null' }
        '404': { $ref: '#/components/responses/NotFound' }
        '409': { $ref: '#/components/responses/Conflict' }
        '422': { $ref: '#/components/responses/Unprocessable' }

  /orders/{id}/returnable:
    get:
      tags: [Продажи]
      operationId: getReturnableLines
      summary: Позиции, доступные к возврату
      description: |
        Что и сколько можно вернуть по заказу с учётом доставленного и уже
        возвращённого — в том числе невыкупленный остаток при частичном выкупе.
      parameters:
        - $ref: '#/components/parameters/idPath'
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Доступные к возврату позиции
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/ReturnableLines' }
                  error: { type: 'null' }
        '404': { $ref: '#/components/responses/NotFound' }

  /orders/{id}/files:
    get:
      tags: [Продажи]
      operationId: listOrderFiles
      summary: Документы заказа
      description: Этикетки/накладные/акты заказа (печать при упаковке).
      parameters:
        - $ref: '#/components/parameters/idPath'
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Документы заказа
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: '#/components/schemas/FileObject' }
                  error: { type: 'null' }
        '404': { $ref: '#/components/responses/NotFound' }

  /orders/{id}/sale-records:
    get:
      tags: [Продажи]
      operationId: listSaleRecords
      summary: Реализации по заказу
      description: Учётные документы реализации (для сверки/отчётности). Частичный выкуп даёт несколько записей.
      parameters:
        - $ref: '#/components/parameters/idPath'
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Реализации заказа
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: '#/components/schemas/SaleRecord' }
                  error: { type: 'null' }
        '404': { $ref: '#/components/responses/NotFound' }

  /returns:
    get:
      tags: [Продажи]
      operationId: listReturns
      summary: Список возвратов
      parameters:
        - $ref: '#/components/parameters/cursor'
        - $ref: '#/components/parameters/limit'
        - $ref: '#/components/parameters/updatedSince'
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Страница возвратов
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: '#/components/schemas/ReturnRequest' }
                  meta: { $ref: '#/components/schemas/PageMeta' }
                  error: { type: 'null' }
    post:
      tags: [Продажи]
      operationId: createReturn
      summary: Создать заявку на возврат
      description: Селлер создаёт заявку на возврат позиций ранее оформленного заказа.
      parameters:
        - $ref: '#/components/parameters/idempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/ReturnRequestCreate' }
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '201':
          description: Заявка создана
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/ReturnRequest' }
                  error: { type: 'null' }
        '404': { $ref: '#/components/responses/NotFound' }
        '409': { $ref: '#/components/responses/Conflict' }
        '422': { $ref: '#/components/responses/Unprocessable' }

  /returns/{id}:
    get:
      tags: [Продажи]
      operationId: getReturn
      summary: Получить возврат
      parameters:
        - $ref: '#/components/parameters/idPath'
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Возврат
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/ReturnRequest' }
                  error: { type: 'null' }
        '404': { $ref: '#/components/responses/NotFound' }

  /purchase-orders:
    get:
      tags: [Закупки]
      operationId: listPurchaseOrders
      summary: Заказы поставщикам (закупки)
      parameters:
        - $ref: '#/components/parameters/cursor'
        - $ref: '#/components/parameters/limit'
        - $ref: '#/components/parameters/updatedSince'
        - name: status
          in: query
          schema: { $ref: '#/components/schemas/PurchaseOrderStatus' }
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Страница закупок
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: '#/components/schemas/PurchaseOrder' }
                  meta: { $ref: '#/components/schemas/PageMeta' }
                  error: { type: 'null' }
    post:
      tags: [Закупки]
      operationId: createPurchaseOrder
      summary: Создать закупку
      parameters:
        - $ref: '#/components/parameters/idempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/PurchaseOrderCreate' }
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '201':
          description: Закупка создана
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/PurchaseOrder' }
                  error: { type: 'null' }
        '409': { $ref: '#/components/responses/Conflict' }
        '422': { $ref: '#/components/responses/Unprocessable' }

  /purchase-orders/{id}:
    parameters:
      - $ref: '#/components/parameters/idPath'
    get:
      tags: [Закупки]
      operationId: getPurchaseOrder
      summary: Получить закупку
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Закупка
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/PurchaseOrder' }
                  error: { type: 'null' }
        '404': { $ref: '#/components/responses/NotFound' }
    patch:
      tags: [Закупки]
      operationId: updatePurchaseOrder
      summary: Редактировать закупку
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/PurchaseOrderPatch' }
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Закупка обновлена
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/PurchaseOrder' }
                  error: { type: 'null' }
        '404': { $ref: '#/components/responses/NotFound' }
        '422': { $ref: '#/components/responses/Unprocessable' }

  /purchase-orders/{id}/activate:
    post:
      tags: [Закупки]
      operationId: activatePurchaseOrder
      summary: Активировать закупку
      parameters:
        - $ref: '#/components/parameters/idPath'
        - $ref: '#/components/parameters/idempotencyKey'
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Закупка активирована
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/PurchaseOrder' }
                  error: { type: 'null' }
        '404': { $ref: '#/components/responses/NotFound' }
        '409': { $ref: '#/components/responses/Conflict' }
        '422': { $ref: '#/components/responses/Unprocessable' }

  /purchase-orders/{id}/cancel:
    post:
      tags: [Закупки]
      operationId: cancelPurchaseOrder
      summary: Отменить закупку
      parameters:
        - $ref: '#/components/parameters/idPath'
        - $ref: '#/components/parameters/idempotencyKey'
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Закупка отменена
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/PurchaseOrder' }
                  error: { type: 'null' }
        '404': { $ref: '#/components/responses/NotFound' }
        '409': { $ref: '#/components/responses/Conflict' }

  /purchase-orders/{id}/reconcile:
    post:
      tags: [Закупки]
      operationId: reconcilePurchaseOrder
      summary: Принять расхождение приёмки
      description: Подтверждает расхождение факт/план приёмки — товар уходит в сток (`COMPLETED_WITH_DIFFERENCE`).
      parameters:
        - $ref: '#/components/parameters/idPath'
        - $ref: '#/components/parameters/idempotencyKey'
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Расхождение принято
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/PurchaseOrder' }
                  error: { type: 'null' }
        '404': { $ref: '#/components/responses/NotFound' }
        '409': { $ref: '#/components/responses/Conflict' }

  /purchase-orders/{id}/receipts:
    get:
      tags: [Закупки]
      operationId: listReceipts
      summary: Факты приёмки по закупке
      parameters:
        - $ref: '#/components/parameters/idPath'
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Приёмки
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: '#/components/schemas/Receipt' }
                  error: { type: 'null' }
        '404': { $ref: '#/components/responses/NotFound' }

  /supplier-returns:
    get:
      tags: [Закупки]
      operationId: listSupplierReturns
      summary: Возвраты поставщикам
      parameters:
        - $ref: '#/components/parameters/cursor'
        - $ref: '#/components/parameters/limit'
        - $ref: '#/components/parameters/updatedSince'
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Возвраты поставщикам
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: '#/components/schemas/SupplierReturn' }
                  meta: { $ref: '#/components/schemas/PageMeta' }
                  error: { type: 'null' }
    post:
      tags: [Закупки]
      operationId: createSupplierReturn
      summary: Создать возврат поставщику
      parameters:
        - $ref: '#/components/parameters/idempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/SupplierReturnCreate' }
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '201':
          description: Возврат создан
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/SupplierReturn' }
                  error: { type: 'null' }
        '409': { $ref: '#/components/responses/Conflict' }
        '422': { $ref: '#/components/responses/Unprocessable' }

  /supplier-returns/{id}:
    parameters:
      - $ref: '#/components/parameters/idPath'
    get:
      tags: [Закупки]
      operationId: getSupplierReturn
      summary: Получить возврат поставщику
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Возврат поставщику
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/SupplierReturn' }
                  error: { type: 'null' }
        '404': { $ref: '#/components/responses/NotFound' }

  /supplier-returns/{id}/cancel:
    post:
      tags: [Закупки]
      operationId: cancelSupplierReturn
      summary: Отменить возврат поставщику
      description: Переводит возврат в терминальный статус `CANCELED`. Возврат сохраняется и доступен через `GET`.
      parameters:
        - $ref: '#/components/parameters/idPath'
        - $ref: '#/components/parameters/idempotencyKey'
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                reason: { type: string, description: Причина отмены }
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Возврат отменён
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/SupplierReturn' }
                  error: { type: 'null' }
        '404': { $ref: '#/components/responses/NotFound' }
        '409': { $ref: '#/components/responses/Conflict' }

  /supplier-returns/{id}/activate:
    post:
      tags: [Закупки]
      operationId: activateSupplierReturn
      summary: Активировать возврат поставщику
      description: Переводит возврат из `DRAFT` в первый рабочий статус (`NEW`). До активации возврат можно дособирать/редактировать.
      parameters:
        - $ref: '#/components/parameters/idPath'
        - $ref: '#/components/parameters/idempotencyKey'
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Возврат активирован
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/SupplierReturn' }
                  error: { type: 'null' }
        '404': { $ref: '#/components/responses/NotFound' }
        '409': { $ref: '#/components/responses/Conflict' }
        '422': { $ref: '#/components/responses/Unprocessable' }

  /fulfillments:
    get:
      tags: [Фулфилмент]
      operationId: listFulfillments
      summary: Список отгрузок по заказам
      parameters:
        - $ref: '#/components/parameters/cursor'
        - $ref: '#/components/parameters/limit'
        - $ref: '#/components/parameters/updatedSince'
        - name: orderId
          in: query
          description: Фильтр по заказу
          schema: { type: string, format: uuid }
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Страница отгрузок
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: '#/components/schemas/Fulfillment' }
                  meta: { $ref: '#/components/schemas/PageMeta' }
                  error: { type: 'null' }

  /fulfillments/{id}:
    get:
      tags: [Фулфилмент]
      operationId: getFulfillment
      summary: Получить отгрузку
      parameters:
        - $ref: '#/components/parameters/idPath'
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Отгрузка
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/Fulfillment' }
                  error: { type: 'null' }
        '404': { $ref: '#/components/responses/NotFound' }

  /shipments:
    get:
      tags: [Фулфилмент]
      operationId: listShipments
      summary: Список отправлений
      parameters:
        - $ref: '#/components/parameters/cursor'
        - $ref: '#/components/parameters/limit'
        - $ref: '#/components/parameters/updatedSince'
        - name: orderId
          in: query
          schema: { type: string, format: uuid }
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Страница отправлений
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: '#/components/schemas/Shipment' }
                  meta: { $ref: '#/components/schemas/PageMeta' }
                  error: { type: 'null' }

  /shipments/{id}:
    get:
      tags: [Фулфилмент]
      operationId: getShipment
      summary: Отправление (трекинг, места)
      parameters:
        - $ref: '#/components/parameters/idPath'
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Отправление
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/Shipment' }
                  error: { type: 'null' }
        '404': { $ref: '#/components/responses/NotFound' }

  /inventory:
    get:
      tags: [Остатки]
      operationId: listInventory
      summary: Остатки (доступно/зарезервировано)
      description: Текущие остатки. Сужайте выборку фильтрами `warehouseId` и `sku`.
      parameters:
        - $ref: '#/components/parameters/cursor'
        - $ref: '#/components/parameters/limit'
        - name: warehouseId
          in: query
          schema: { type: string, format: uuid }
        - name: sku
          in: query
          schema: { type: string }
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Уровни остатков
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: '#/components/schemas/InventoryLevel' }
                  meta: { $ref: '#/components/schemas/PageMeta' }
                  error: { type: 'null' }

  /inventory-adjustments:
    get:
      tags: [Остатки]
      operationId: listInventoryAdjustments
      summary: Корректировки остатков
      parameters:
        - $ref: '#/components/parameters/cursor'
        - $ref: '#/components/parameters/limit'
        - $ref: '#/components/parameters/updatedSince'
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Корректировки
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: '#/components/schemas/InventoryAdjustment' }
                  meta: { $ref: '#/components/schemas/PageMeta' }
                  error: { type: 'null' }
    post:
      tags: [Остатки]
      operationId: createInventoryAdjustment
      summary: Создать корректировку остатков
      description: Оприходование / списание / смена состояния (годный↔брак). Для self-fulfillment склада.
      parameters:
        - $ref: '#/components/parameters/idempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/InventoryAdjustmentCreate' }
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '201':
          description: Корректировка применена
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/InventoryAdjustment' }
                  error: { type: 'null' }
        '409': { $ref: '#/components/responses/Conflict' }
        '422': { $ref: '#/components/responses/Unprocessable' }

  /inventory-adjustments/{id}:
    get:
      tags: [Остатки]
      operationId: getInventoryAdjustment
      summary: Получить корректировку остатков
      parameters:
        - $ref: '#/components/parameters/idPath'
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Корректировка остатков
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/InventoryAdjustment' }
                  error: { type: 'null' }
        '404': { $ref: '#/components/responses/NotFound' }

  /transfers:
    get:
      tags: [Остатки]
      operationId: listTransfers
      summary: Перемещения между складами
      parameters:
        - $ref: '#/components/parameters/cursor'
        - $ref: '#/components/parameters/limit'
        - $ref: '#/components/parameters/updatedSince'
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Перемещения
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: '#/components/schemas/Transfer' }
                  meta: { $ref: '#/components/schemas/PageMeta' }
                  error: { type: 'null' }
    post:
      tags: [Остатки]
      operationId: createTransfer
      summary: Создать перемещение
      parameters:
        - $ref: '#/components/parameters/idempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/TransferCreate' }
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '201':
          description: Перемещение создано
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/Transfer' }
                  error: { type: 'null' }
        '409': { $ref: '#/components/responses/Conflict' }
        '422': { $ref: '#/components/responses/Unprocessable' }

  /transfers/{id}:
    get:
      tags: [Остатки]
      operationId: getTransfer
      summary: Получить перемещение
      parameters:
        - $ref: '#/components/parameters/idPath'
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Перемещение
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/Transfer' }
                  error: { type: 'null' }
        '404': { $ref: '#/components/responses/NotFound' }

  /carrier-pickups:
    get:
      tags: [Фулфилмент]
      operationId: listCarrierPickups
      summary: Заявки на забор перевозчиком
      parameters:
        - $ref: '#/components/parameters/cursor'
        - $ref: '#/components/parameters/limit'
        - $ref: '#/components/parameters/updatedSince'
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Заявки на забор
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: '#/components/schemas/CarrierPickup' }
                  meta: { $ref: '#/components/schemas/PageMeta' }
                  error: { type: 'null' }
    post:
      tags: [Фулфилмент]
      operationId: createCarrierPickup
      summary: Заказать забор перевозчиком
      parameters:
        - $ref: '#/components/parameters/idempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/CarrierPickupCreate' }
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '201':
          description: Заявка создана
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/CarrierPickup' }
                  error: { type: 'null' }
        '409': { $ref: '#/components/responses/Conflict' }
        '422': { $ref: '#/components/responses/Unprocessable' }

  /carrier-pickups/{id}:
    parameters:
      - $ref: '#/components/parameters/idPath'
    get:
      tags: [Фулфилмент]
      operationId: getCarrierPickup
      summary: Получить заявку на забор
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Заявка
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/CarrierPickup' }
                  error: { type: 'null' }
        '404': { $ref: '#/components/responses/NotFound' }

  /carrier-pickups/{id}/cancel:
    post:
      tags: [Фулфилмент]
      operationId: cancelCarrierPickup
      summary: Отменить заявку на забор
      description: Переводит заявку в терминальный статус `CANCELED`. Заявка сохраняется и доступна через `GET`.
      parameters:
        - $ref: '#/components/parameters/idPath'
        - $ref: '#/components/parameters/idempotencyKey'
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                reason: { type: string, description: Причина отмены }
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Заявка отменена
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/CarrierPickup' }
                  error: { type: 'null' }
        '404': { $ref: '#/components/responses/NotFound' }
        '409': { $ref: '#/components/responses/Conflict' }

  /products:
    get:
      tags: [Каталог]
      operationId: listProducts
      summary: Товары (карточки с вариациями)
      parameters:
        - $ref: '#/components/parameters/cursor'
        - $ref: '#/components/parameters/limit'
        - $ref: '#/components/parameters/updatedSince'
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Страница товаров
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: '#/components/schemas/Product' }
                  meta: { $ref: '#/components/schemas/PageMeta' }
                  error: { type: 'null' }
    post:
      tags: [Каталог]
      operationId: createProduct
      summary: Создать товар с вариантами
      description: Создаёт карточку товара целиком — с осями вариантов и вариантами (SKU).
      parameters:
        - $ref: '#/components/parameters/idempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/ProductCreate' }
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '201':
          description: Товар создан
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/Product' }
                  error: { type: 'null' }
        '409': { $ref: '#/components/responses/Conflict' }
        '422': { $ref: '#/components/responses/Unprocessable' }

  /products/{id}:
    parameters:
      - $ref: '#/components/parameters/idPath'
    get:
      tags: [Каталог]
      operationId: getProduct
      summary: Получить товар
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Товар
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/Product' }
                  error: { type: 'null' }
        '404': { $ref: '#/components/responses/NotFound' }
    patch:
      tags: [Каталог]
      operationId: updateProduct
      summary: Редактировать товар
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/ProductPatch' }
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Товар обновлён
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/Product' }
                  error: { type: 'null' }
        '404': { $ref: '#/components/responses/NotFound' }
        '422': { $ref: '#/components/responses/Unprocessable' }

  /product-categories:
    get:
      tags: [Каталог]
      operationId: listProductCategories
      summary: Категории каталога
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Дерево категорий
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: '#/components/schemas/ProductCategory' }
                  error: { type: 'null' }

  /skus:
    get:
      tags: [Каталог]
      operationId: listSkus
      summary: Список SKU
      description: 'Поиск по коду/штрихкоду/имени.'
      parameters:
        - $ref: '#/components/parameters/cursor'
        - $ref: '#/components/parameters/limit'
        - $ref: '#/components/parameters/updatedSince'
        - name: q
          in: query
          schema: { type: string }
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: SKU
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: '#/components/schemas/Sku' }
                  meta: { $ref: '#/components/schemas/PageMeta' }
                  error: { type: 'null' }
    post:
      tags: [Каталог]
      operationId: createSku
      summary: Создать SKU
      parameters:
        - $ref: '#/components/parameters/idempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/SkuCreate' }
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '201':
          description: SKU создан
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/Sku' }
                  error: { type: 'null' }
        '409': { $ref: '#/components/responses/Conflict' }

  /skus:batchUpsert:
    post:
      tags: [Каталог]
      operationId: batchUpsertSkus
      summary: Массовое создание/обновление SKU
      description: |
        Пакетная загрузка каталога (онбординг). Сопоставление по `externalId`/`article`.
        В ответе — результат по каждому элементу (частичные ошибки не валят весь пакет).
      parameters:
        - $ref: '#/components/parameters/idempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                items:
                  type: array
                  maxItems: 1000
                  items: { $ref: '#/components/schemas/SkuCreate' }
              required: [items]
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Результат по каждому элементу
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        index: { type: integer }
                        status: { type: string, enum: [CREATED, UPDATED, ERROR] }
                        skuId: { type: string, format: uuid }
                        error: { $ref: '#/components/schemas/ErrorDetail' }
                  error: { type: 'null' }
        '409': { $ref: '#/components/responses/Conflict' }

  /skus/{id}:
    parameters:
      - $ref: '#/components/parameters/idPath'
    get:
      tags: [Каталог]
      operationId: getSku
      summary: Получить SKU
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: SKU
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/Sku' }
                  error: { type: 'null' }
        '404': { $ref: '#/components/responses/NotFound' }
    patch:
      tags: [Каталог]
      operationId: updateSku
      summary: Редактировать SKU
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/SkuPatch' }
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: SKU обновлён
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/Sku' }
                  error: { type: 'null' }
        '404': { $ref: '#/components/responses/NotFound' }
        '422': { $ref: '#/components/responses/Unprocessable' }

  /skus/{id}/bundle-components:
    parameters:
      - $ref: '#/components/parameters/idPath'
    get:
      tags: [Каталог]
      operationId: getBundleComponents
      summary: Состав набора/комплекта
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Компоненты набора
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: '#/components/schemas/BundleComponent' }
                  error: { type: 'null' }
        '404': { $ref: '#/components/responses/NotFound' }
    put:
      tags: [Каталог]
      operationId: setBundleComponents
      summary: Задать состав набора/комплекта
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                components:
                  type: array
                  items: { $ref: '#/components/schemas/BundleComponent' }
                bundleOptionalCount: { type: integer }
              required: [components]
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Состав обновлён
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/Sku' }
                  error: { type: 'null' }
        '422': { $ref: '#/components/responses/Unprocessable' }

  /pricing:quote:
    post:
      tags: [Цены]
      operationId: quotePricing
      summary: Расчёт цены корзины
      description: |
        Возвращает цены позиций со скидками, эффект промокода (скидка на товары +
        отдельно на доставку + бонус-товары). **Округление:** суммы округляются до целых
        минорных единиц (HALF_UP); FIX-скидка распределяется по позициям пропорционально
        с компенсацией остатка, поэтому `total` может не совпадать с наивной суммой `price×qty`.
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/QuoteRequest' }
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Расчёт
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/QuoteResult' }
                  error: { type: 'null' }
        '422': { $ref: '#/components/responses/Unprocessable' }

  /prices:
    get:
      tags: [Цены]
      operationId: listActualPrices
      summary: Актуальные цены SKU
      description: 'Текущие цены по списку SKU. Если `priceListId` не указан — берётся прайс-лист по умолчанию.'
      parameters:
        - name: skus
          in: query
          description: Список skuId через запятую
          schema: { type: string }
        - name: priceListId
          in: query
          schema: { type: string, format: uuid }
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Актуальные цены
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: '#/components/schemas/ActualPrice' }
                  error: { type: 'null' }

  /price-lists:
    get:
      tags: [Цены]
      operationId: listPriceLists
      summary: Прайс-листы
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Прайс-листы
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: '#/components/schemas/PriceList' }
                  error: { type: 'null' }
    post:
      tags: [Цены]
      operationId: createPriceList
      summary: Создать прайс-лист
      parameters:
        - $ref: '#/components/parameters/idempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/PriceListCreate' }
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '201':
          description: Прайс-лист создан
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/PriceList' }
                  error: { type: 'null' }
        '409': { $ref: '#/components/responses/Conflict' }

  /price-lists/{id}/prices:
    put:
      tags: [Цены]
      operationId: setPrices
      summary: Задать цены в прайс-листе
      description: 'Пакетная установка цен. Действует немедленно или с `effectiveFrom` (опц.). Отложенный регламент цен — см. OQ.'
      parameters:
        - $ref: '#/components/parameters/idPath'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                effectiveFrom: { type: string, format: date-time, description: 'Дата вступления в силу (опц.; по умолчанию — сейчас)' }
                items:
                  type: array
                  items:
                    type: object
                    properties:
                      skuId: { type: string, format: uuid }
                      price: { $ref: '#/components/schemas/Money' }
                    required: [skuId, price]
              required: [items]
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Цены обновлены
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { type: 'null' }
                  error: { type: 'null' }
        '404': { $ref: '#/components/responses/NotFound' }
        '422': { $ref: '#/components/responses/Unprocessable' }

  /price-lists/{id}:
    get:
      tags: [Цены]
      operationId: getPriceList
      summary: Получить прайс-лист
      parameters:
        - $ref: '#/components/parameters/idPath'
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Прайс-лист
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/PriceList' }
                  error: { type: 'null' }
        '404': { $ref: '#/components/responses/NotFound' }

  /promotions:
    get:
      tags: [Цены]
      operationId: listPromotions
      summary: Промоакции
      parameters:
        - $ref: '#/components/parameters/cursor'
        - $ref: '#/components/parameters/limit'
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Промоакции
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: '#/components/schemas/Promotion' }
                  meta: { $ref: '#/components/schemas/PageMeta' }
                  error: { type: 'null' }
    post:
      tags: [Цены]
      operationId: createPromotion
      summary: Создать промоакцию
      parameters:
        - $ref: '#/components/parameters/idempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/PromotionCreate' }
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '201':
          description: Промоакция создана
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/Promotion' }
                  error: { type: 'null' }
        '409': { $ref: '#/components/responses/Conflict' }

  /promotions/{id}:
    parameters:
      - $ref: '#/components/parameters/idPath'
    get:
      tags: [Цены]
      operationId: getPromotion
      summary: Получить промоакцию
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Промоакция
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/Promotion' }
                  error: { type: 'null' }
        '404': { $ref: '#/components/responses/NotFound' }
    patch:
      tags: [Цены]
      operationId: updatePromotion
      summary: Редактировать промоакцию
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/PromotionPatch' }
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Промоакция обновлена
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/Promotion' }
                  error: { type: 'null' }
        '404': { $ref: '#/components/responses/NotFound' }
    delete:
      tags: [Цены]
      operationId: deletePromotion
      summary: Удалить промоакцию
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '204': { description: Удалено }
        '404': { $ref: '#/components/responses/NotFound' }

  /promotions:validate:
    post:
      tags: [Цены]
      operationId: validatePromotion
      summary: Проверить применимость промокода
      description: Лёгкая проверка кода к корзине (условия + лимиты) без полного расчёта цены.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                code: { type: string }
                lines:
                  type: array
                  items: { $ref: '#/components/schemas/SkuQty' }
              required: [code]
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Результат проверки
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      valid: { type: boolean }
                      message: { type: string }
                      discount: { $ref: '#/components/schemas/Money' }
                  error: { type: 'null' }

  /delivery:quote:
    post:
      tags: [Доставка]
      operationId: quoteDelivery
      summary: Расчёт доставки (варианты по адресу/корзине)
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/DeliveryQuoteRequest' }
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Варианты доставки
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/DeliveryQuote' }
                  error: { type: 'null' }
        '422': { $ref: '#/components/responses/Unprocessable' }

  /delivery/pickup-points:
    get:
      tags: [Доставка]
      operationId: listPickupPoints
      summary: Пункты выдачи и постаматы
      description: Список ПВЗ/постаматов по городу и каналу доставки (для checkout с самовывозом/примеркой).
      parameters:
        - name: cityCode
          in: query
          description: Код города (ФИАС/КЛАДР)
          schema: { type: string }
        - name: deliveryCode
          in: query
          description: Код службы доставки
          schema: { type: string }
        - $ref: '#/components/parameters/cursor'
        - $ref: '#/components/parameters/limit'
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Список пунктов выдачи
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: '#/components/schemas/PickupPoint' }
                  meta: { $ref: '#/components/schemas/PageMeta' }
                  error: { type: 'null' }

  /delivery/pickup-points/{code}:
    get:
      tags: [Доставка]
      operationId: getPickupPoint
      summary: Пункт выдачи по коду
      parameters:
        - name: code
          in: path
          required: true
          schema: { type: string }
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Пункт выдачи
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/PickupPoint' }
                  error: { type: 'null' }
        '404': { $ref: '#/components/responses/NotFound' }

  /delivery/pickup-points/{code}/dates:
    get:
      tags: [Доставка]
      operationId: getPickupDates
      summary: Доступные даты забора (самовывоз/DBS)
      parameters:
        - name: code
          in: path
          required: true
          schema: { type: string }
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Доступные даты/интервалы
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/PickupDates' }
                  error: { type: 'null' }

  /delivery-channels:
    get:
      tags: [Справочники]
      operationId: listDeliveryChannels
      summary: Каналы доставки
      description: Настроенные каналы доставки (для выбора `deliveryChannelId` при создании заказа).
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Каналы доставки
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: '#/components/schemas/DeliveryChannel' }
                  error: { type: 'null' }

  /delivery-channels/{id}:
    get:
      tags: [Справочники]
      operationId: getDeliveryChannel
      summary: Получить канал доставки
      parameters:
        - $ref: '#/components/parameters/idPath'
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Канал доставки
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/DeliveryChannel' }
                  error: { type: 'null' }
        '404': { $ref: '#/components/responses/NotFound' }

  /warehouses:
    get:
      tags: [Справочники]
      operationId: listWarehouses
      summary: Склады
      description: Справочник складов (для выбора `warehouseId`).
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Склады
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: '#/components/schemas/Warehouse' }
                  error: { type: 'null' }

  /warehouses/{id}:
    get:
      tags: [Справочники]
      operationId: getWarehouse
      summary: Получить склад
      parameters:
        - $ref: '#/components/parameters/idPath'
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Склад
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/Warehouse' }
                  error: { type: 'null' }
        '404': { $ref: '#/components/responses/NotFound' }

  /counterparties:
    get:
      tags: [Справочники]
      operationId: listCounterparties
      summary: Контрагенты
      description: 'Справочник контрагентов (поставщики и др.). Фильтр `role` — например, `PRODUCT_SUPPLIER` для выбора `supplierId`.'
      parameters:
        - $ref: '#/components/parameters/cursor'
        - $ref: '#/components/parameters/limit'
        - name: role
          in: query
          schema: { $ref: '#/components/schemas/CounterpartyRole' }
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Контрагенты
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: '#/components/schemas/Counterparty' }
                  meta: { $ref: '#/components/schemas/PageMeta' }
                  error: { type: 'null' }

  /counterparties/{id}:
    get:
      tags: [Справочники]
      operationId: getCounterparty
      summary: Получить контрагента
      parameters:
        - $ref: '#/components/parameters/idPath'
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Контрагент
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/Counterparty' }
                  error: { type: 'null' }
        '404': { $ref: '#/components/responses/NotFound' }

  /legal-entities:
    get:
      tags: [Справочники]
      operationId: listLegalEntities
      summary: Юридические лица
      description: Справочник юрлиц продавца (для выбора `legalEntityId`).
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Юрлица
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: '#/components/schemas/LegalEntity' }
                  error: { type: 'null' }

  /legal-entities/{id}:
    get:
      tags: [Справочники]
      operationId: getLegalEntity
      summary: Получить юрлицо
      parameters:
        - $ref: '#/components/parameters/idPath'
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Юрлицо
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/LegalEntity' }
                  error: { type: 'null' }
        '404': { $ref: '#/components/responses/NotFound' }

  /profile:
    get:
      tags: [Платформа]
      operationId: getProfile
      summary: Профиль аккаунта
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Профиль
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/Profile' }
                  error: { type: 'null' }

  /config:
    get:
      tags: [Платформа]
      operationId: getConfig
      summary: Настройки витрины/checkout
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Конфигурация
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/TenantConfig' }
                  error: { type: 'null' }

  /reports:
    post:
      tags: [Платформа]
      operationId: createReport
      summary: Запросить отчёт/выгрузку
      description: Создаёт асинхронную задачу формирования отчёта. Готовый файл — `GET /reports/{id}/result`.
      parameters:
        - $ref: '#/components/parameters/idempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/ReportTaskCreate' }
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '202':
          description: Задача создана
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/ReportTask' }
                  error: { type: 'null' }
        '409': { $ref: '#/components/responses/Conflict' }
        '422': { $ref: '#/components/responses/Unprocessable' }

  /reports/{id}:
    get:
      tags: [Платформа]
      operationId: getReport
      summary: Статус задачи отчёта
      parameters:
        - $ref: '#/components/parameters/idPath'
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Задача отчёта
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/ReportTask' }
                  error: { type: 'null' }
        '404': { $ref: '#/components/responses/NotFound' }

  /reports/{id}/result:
    get:
      tags: [Платформа]
      operationId: getReportResult
      summary: Скачать готовый отчёт
      description: Доступно при `status=DONE`. Файл отдаётся по защищённой ссылке (не публичный URL).
      parameters:
        - $ref: '#/components/parameters/idPath'
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Файл отчёта
          content:
            application/octet-stream:
              schema: { type: string, format: binary }
        '404': { $ref: '#/components/responses/NotFound' }
        '409': { $ref: '#/components/responses/Conflict' }

  /invoices:
    get:
      tags: [Финансы]
      operationId: listInvoices
      summary: Счета
      description: Счета за услуги фулфилмента за период.
      parameters:
        - $ref: '#/components/parameters/cursor'
        - $ref: '#/components/parameters/limit'
        - $ref: '#/components/parameters/updatedSince'
        - name: period
          in: query
          description: 'Фильтр по периоду счёта, `YYYY-MM`'
          schema: { type: string, examples: ['2026-06'] }
        - name: legalEntityId
          in: query
          description: Фильтр по юрлицу (для аккаунтов с несколькими юрлицами)
          schema: { type: string, format: uuid }
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Счета
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: '#/components/schemas/Invoice' }
                  meta: { $ref: '#/components/schemas/PageMeta' }
                  error: { type: 'null' }

  /invoices/{id}:
    get:
      tags: [Финансы]
      operationId: getInvoice
      summary: Получить счёт
      parameters:
        - $ref: '#/components/parameters/idPath'
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Счёт (со строками)
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/Invoice' }
                  error: { type: 'null' }
        '404': { $ref: '#/components/responses/NotFound' }

  /orders/{id}/payments:
    get:
      tags: [Финансы]
      operationId: listOrderPayments
      summary: Оплаты по заказу
      parameters:
        - $ref: '#/components/parameters/idPath'
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Оплаты заказа
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: '#/components/schemas/Payment' }
                  error: { type: 'null' }
        '404': { $ref: '#/components/responses/NotFound' }
    post:
      tags: [Финансы]
      operationId: createOrderPayment
      summary: Создать платёжное намерение (ссылка на оплату)
      description: Создаёт платёжное намерение и возвращает ссылку для онлайн-оплаты заказа покупателем.
      parameters:
        - $ref: '#/components/parameters/idPath'
        - $ref: '#/components/parameters/idempotencyKey'
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '201':
          description: Платёжное намерение создано
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/PaymentIntent' }
                  error: { type: 'null' }
        '404': { $ref: '#/components/responses/NotFound' }
        '409': { $ref: '#/components/responses/Conflict' }
        '422': { $ref: '#/components/responses/Unprocessable' }

  /orders/{id}/cod-payments:
    get:
      tags: [Финансы]
      operationId: listOrderCodPayments
      summary: Инкассации COD по заказу
      parameters:
        - $ref: '#/components/parameters/idPath'
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Инкассации наложенного платежа
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: '#/components/schemas/CodPayment' }
                  error: { type: 'null' }
        '404': { $ref: '#/components/responses/NotFound' }

  /balance:
    get:
      tags: [Финансы]
      operationId: getBalance
      summary: Консолидированный баланс
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Баланс
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/Balance' }
                  error: { type: 'null' }

  /webhook-endpoints:
    get:
      tags: [Платформа]
      operationId: listWebhookEndpoints
      summary: Подписки на исходящие вебхуки
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Список подписок
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: '#/components/schemas/WebhookEndpoint' }
                  error: { type: 'null' }
    post:
      tags: [Платформа]
      operationId: createWebhookEndpoint
      summary: Создать подписку на события
      description: |
        События: `order.status_changed`, `order.completed`, `order.shipped`,
        `order.delivered`, `shipment.registered`, `shipment.delivered`,
        `return.accepted`, `return.received`, `return.rejected`, `purchase_order.received`,
        `invoice.issued`, `payment.completed`, `payment.refunded`.
        HMAC-секрет возвращается один раз при создании.
      parameters:
        - $ref: '#/components/parameters/idempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/WebhookEndpointCreate' }
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '201':
          description: Подписка создана. Поле `secret` присутствует в ответе только здесь.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/WebhookEndpointCreated' }
                  error: { type: 'null' }
        '409': { $ref: '#/components/responses/Conflict' }

  /webhook-endpoints/{id}:
    get:
      tags: [Платформа]
      operationId: getWebhookEndpoint
      summary: Получить подписку
      parameters:
        - $ref: '#/components/parameters/idPath'
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Подписка
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/WebhookEndpoint' }
                  error: { type: 'null' }
        '404': { $ref: '#/components/responses/NotFound' }
    patch:
      tags: [Платформа]
      operationId: updateWebhookEndpoint
      summary: Изменить подписку
      description: Меняет `url`, набор событий или флаг `active`. Секрет не возвращается — для смены секрета используйте `/rotate-secret`.
      parameters:
        - $ref: '#/components/parameters/idPath'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/WebhookEndpointPatch' }
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Подписка обновлена
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/WebhookEndpoint' }
                  error: { type: 'null' }
        '404': { $ref: '#/components/responses/NotFound' }
    delete:
      tags: [Платформа]
      operationId: deleteWebhookEndpoint
      summary: Удалить подписку
      parameters:
        - $ref: '#/components/parameters/idPath'
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '204': { description: Подписка удалена }
        '404': { $ref: '#/components/responses/NotFound' }

  /webhook-endpoints/{id}/test:
    post:
      tags: [Платформа]
      operationId: testWebhookEndpoint
      summary: Отправить тестовое событие
      description: Доставляет синтетическое событие `webhook.test` на `url` подписки — для проверки приёма и подписи без реального события.
      parameters:
        - $ref: '#/components/parameters/idPath'
        - $ref: '#/components/parameters/idempotencyKey'
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '202': { description: Тестовое событие поставлено в доставку }
        '404': { $ref: '#/components/responses/NotFound' }

  /webhook-endpoints/{id}/rotate-secret:
    post:
      tags: [Платформа]
      operationId: rotateWebhookSecret
      summary: Перевыпустить HMAC-секрет
      description: Генерирует новый `secret` для подписи. Возвращается один раз в этом ответе; прежний секрет немедленно недействителен.
      parameters:
        - $ref: '#/components/parameters/idPath'
        - $ref: '#/components/parameters/idempotencyKey'
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Секрет перевыпущен. Поле `secret` присутствует только здесь.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/WebhookEndpointCreated' }
                  error: { type: 'null' }
        '404': { $ref: '#/components/responses/NotFound' }

  /webhook-endpoints/{id}/deliveries:
    get:
      tags: [Платформа]
      operationId: listWebhookDeliveries
      summary: Журнал доставок подписки
      description: История попыток доставки события на `url` — для диагностики пропущенных или отклонённых событий.
      parameters:
        - $ref: '#/components/parameters/idPath'
        - $ref: '#/components/parameters/cursor'
        - $ref: '#/components/parameters/limit'
      responses:
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '200':
          description: Страница доставок
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: '#/components/schemas/WebhookDeliveryRecord' }
                  meta: { $ref: '#/components/schemas/PageMeta' }
                  error: { type: 'null' }
        '404': { $ref: '#/components/responses/NotFound' }

webhooks:
  event:
    post:
      operationId: webhookEvent
      tags: [Платформа]
      security: []
      summary: Доставка события на URL подписки
      description: |
        Fulcore отправляет `POST` на зарегистрированный `url`. Подпись — заголовок
        `X-Fulcore-Signature: sha256=<hex>` (HMAC-SHA256 по строке `<timestamp>.<тело>`
        с ключом `secret`) и `X-Fulcore-Timestamp` (unix-секунды, защита от replay).
        Ответьте `2xx` для подтверждения, иначе доставка повторяется.
        Дедуплицируйте по `id` события.
      requestBody:
        content:
          application/json:
            schema: { $ref: '#/components/schemas/WebhookDelivery' }
      responses:
        '200': { description: Событие принято }

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: |
        Bearer-токен (OAuth2/JWT). Запросы выполняются в контексте аккаунта,
        которому выдан токен: доступны только данные этого аккаунта.

  parameters:
    idPath:
      name: id
      in: path
      required: true
      schema: { type: string, format: uuid }
    cursor:
      name: cursor
      in: query
      description: Непрозрачный курсор следующей страницы
      schema: { type: string }
    limit:
      name: limit
      in: query
      description: Размер страницы
      schema: { type: integer, default: 50, maximum: 200 }
    updatedSince:
      name: updatedSince
      in: query
      description: 'Инкрементальная синхронизация: только записи, изменённые после этого момента (по `updatedAt`)'
      schema: { type: string, format: date-time }
    idempotencyKey:
      name: Idempotency-Key
      in: header
      required: true
      description: Ключ идемпотентности (создание / команды с внешним эффектом)
      schema: { type: string }
    ifMatch:
      name: If-Match
      in: header
      required: true
      description: Версия ресурса для оптимистичной блокировки
      schema: { type: string }

  responses:
    NotFound:
      description: Ресурс не найден
      content:
        application/json:
          schema: { $ref: '#/components/schemas/ApiError' }
    Unauthorized:
      description: Токен отсутствует, недействителен или истёк
      content:
        application/json:
          schema: { $ref: '#/components/schemas/ApiError' }
    Forbidden:
      description: Недостаточно прав для операции
      content:
        application/json:
          schema: { $ref: '#/components/schemas/ApiError' }
    RateLimited:
      description: Превышен лимит запросов (см. заголовок `Retry-After`)
      content:
        application/json:
          schema: { $ref: '#/components/schemas/ApiError' }
    Conflict:
      description: Конфликт (идемпотентность / устаревшая версия)
      content:
        application/json:
          schema: { $ref: '#/components/schemas/ApiError' }
    Unprocessable:
      description: Нарушен бизнес-инвариант (список issues)
      content:
        application/json:
          schema: { $ref: '#/components/schemas/ApiError' }
          example:
            data: null
            error:
              code: validation_error
              message: Заказ нельзя передать в исполнение
              issues:
                - path: lines[0].qty
                  code: out_of_stock
                  message: Недостаточно остатка по SKU

  schemas:
    Money:
      type: object
      description: Денежная сумма — целое число в минорных единицах валюты (например, копейки) + код валюты ISO-4217
      properties:
        amountMinor: { type: integer, format: int64, examples: [149900] }
        currency: { type: string, examples: ['RUB'] }
      required: [amountMinor, currency]
    Ref:
      type: object
      description: Краткая ссылка на сущность
      properties:
        id: { type: string, format: uuid }
        number: { type: string, description: Человекочитаемый номер }
        name: { type: string }
    Address:
      type: object
      description: Адрес доставки
      properties:
        country: { type: string }
        region: { type: string }
        city: { type: string }
        street: { type: string }
        house: { type: string }
        flat: { type: string }
        postalCode: { type: string }
      required: [country]
    Person:
      type: object
      description: Получатель / контактное лицо
      properties:
        lastName: { type: string }
        firstName: { type: string }
        middleName: { type: string }
        phones: { type: array, items: { type: string } }
        email: { type: string, format: email }
      required: [phones]
    PageMeta:
      type: object
      properties:
        nextCursor:
          type: [string, 'null']
          description: 'Курсор следующей страницы; `null` — следующей страницы нет'
    ApiError:
      type: object
      description: Конверт ошибки
      properties:
        data: { type: 'null' }
        error: { $ref: '#/components/schemas/ErrorDetail' }
    PriceSummary:
      type: object
      description: Итог по деньгам документа
      properties:
        total: { $ref: '#/components/schemas/Money' }
        vatAmount: { $ref: '#/components/schemas/Money', description: Сумма НДС в составе итога }
    VatRate:
      type: string
      description: 'Ставка НДС: `VAT_0`/`VAT_10`/`VAT_20` — 0/10/20%; `NO_VAT` — без НДС.'
      enum: [VAT_0, VAT_10, VAT_20, NO_VAT]
    FileKind:
      type: string
      description: 'Назначение документа: `LABEL` — этикетка; `INVOICE` — накладная; `ACT` — акт; `OTHER` — иное.'
      enum: [LABEL, INVOICE, ACT, OTHER]
    SkuQty:
      type: object
      description: Строка «SKU × количество»
      properties:
        skuId: { type: string, format: uuid }
        qty: { type: number, exclusiveMinimum: 0 }
      required: [skuId, qty]
    ErrorDetail:
      type: object
      description: Структурированная ошибка (единая форма — верхнеуровневая и по-элементная)
      properties:
        code: { type: string, examples: ['validation_error'] }
        message: { type: string }
        issues:
          type: array
          items:
            type: object
            properties:
              path: { type: string }
              code: { type: string }
              message: { type: string }
    CounterpartyRole:
      type: string
      description: Роль контрагента
      enum: [CUSTOMER, PRODUCT_SUPPLIER, DELIVERY_SERVICE, FULFILLMENT_SERVICE, CALLCENTER_SERVICE]
    PurchaseOrderStatus:
      type: string
      description: |
        Статус закупки (вычисляемый): `DRAFT` — черновик; `NEW` — создан;
        `READY_TO_RECEIVE` — ожидает приёмки; `RECEIVING` — идёт приёмка;
        `DIFFERENCE` — расхождение приёмки (требует подтверждения); `COMPLETED` —
        принято; `COMPLETED_WITH_DIFFERENCE` — принято с расхождением; `CANCELED` — отменён.
      enum: [DRAFT, NEW, READY_TO_RECEIVE, RECEIVING, DIFFERENCE, COMPLETED, COMPLETED_WITH_DIFFERENCE, CANCELED]
    OrderStatus:
      type: string
      description: |
        Текущий статус заказа (вычисляемый). Оформление: `DRAFT` — черновик;
        `NEW` — новый; `AWAITING_CONFIRM` — ждёт подтверждения; `CONFIRM_HOLD` —
        подтверждение на удержании; `INSUFFICIENT_RESERVE` — не хватает остатка;
        `AWAITING_PAYMENT` — ждёт оплаты. Исполнение: `READY_TO_PACK` — к сборке;
        `PACKING` — собирается; `PACKING_ERROR` — ошибка сборки; `PACKED` — собран;
        `SHIPPED` — отгружен. Доставка: `IN_DELIVERY` — в пути; `AT_PICKUP_POINT` —
        в пункте выдачи; `DELIVERED` — доставлен (выкуплен полностью).
        Частичный выкуп: `PARTIALLY_DELIVERED` — выкуплена часть, остаток ещё в
        доставке/возврате; `PARTIALLY_DELIVERED_RETURNED` — выкуплена часть, остаток
        возвращён. Возврат/потеря: `RETURNED` — возвращён полностью;
        `RETURNED_TO_CLIENT` — возвращён отправителю; `LOST` — утерян.
        Отмена: `CANCELING` — отменяется; `WAIT_UNPACK` — ждёт распаковки;
        `WAIT_DELIVERY_CANCEL` — ждёт отмены отправления у перевозчика;
        `CANCELED` — отменён.
      enum:
        - DRAFT
        - NEW
        - AWAITING_CONFIRM
        - CONFIRM_HOLD
        - INSUFFICIENT_RESERVE
        - AWAITING_PAYMENT
        - READY_TO_PACK
        - PACKING
        - PACKING_ERROR
        - PACKED
        - SHIPPED
        - IN_DELIVERY
        - AT_PICKUP_POINT
        - DELIVERED
        - PARTIALLY_DELIVERED
        - PARTIALLY_DELIVERED_RETURNED
        - RETURNED
        - RETURNED_TO_CLIENT
        - LOST
        - CANCELING
        - WAIT_UNPACK
        - WAIT_DELIVERY_CANCEL
        - CANCELED
    PaymentMethod:
      type: string
      description: |
        Способ оплаты: `ONLINE` — онлайн-предоплата; `COD_CASH` / `COD_CARD` —
        наложенный платёж наличными / картой; `NO_COD` — без наложенного платежа
        (предоплачено); `NO_COD_NO_CONTROL` — без контроля оплаты.
      enum: [COD_CASH, COD_CARD, ONLINE, NO_COD, NO_COD_NO_CONTROL]
    PaymentStatus:
      type: string
      description: |
        Статус оплаты: `NOT_PAID` — не оплачен; `PARTIALLY_PAID` — частично
        оплачен; `PAID` — оплачен полностью.
      enum: [NOT_PAID, PARTIALLY_PAID, PAID]
    SaleMode:
      type: string
      description: |
        Момент выкупа/реализации: `ON_DELIVERY` — выкуп по факту доставки (реализация
        при статусе `DELIVERED`); `ON_TAKEOUT` — выкуп при получении/примерке в пункте
        выдачи (реализация при отгрузке; поддерживает частичный выкуп — актуально для одежды).
      enum: [ON_DELIVERY, ON_TAKEOUT]
    OrderDiscount:
      type: object
      description: Скидка, применённая к заказу
      properties:
        kind: { type: string, enum: [PROMOTION, PAYMENT, MANUAL, SKU] }
        value: { $ref: '#/components/schemas/Money' }
        ref: { type: string, description: Ссылка на источник скидки (например, промокод) }
    StatusHistoryEntry:
      type: object
      description: Запись истории смены статуса
      properties:
        occurredAt: { type: string, format: date-time }
        status: { $ref: '#/components/schemas/OrderStatus' }
        reason: { type: string }
    Order:
      type: object
      description: Заказ
      properties:
        id: { type: string, format: uuid }
        number: { type: string, description: Человекочитаемый номер заказа }
        externalId: { type: string, description: Идентификатор заказа во внешней системе (UUID/ключ магазина/маркетплейса) }
        externalNumber: { type: string, description: Человекочитаемый номер заказа во внешней системе (для отображения) }
        status: { $ref: '#/components/schemas/OrderStatus' }
        origin:
          type: string
          description: Источник заказа
          enum: [SELLER_PORTAL, ESHOP, MARKETPLACE, API]
        saleMode: { $ref: '#/components/schemas/SaleMode' }
        operationalModel:
          type: string
          readOnly: true
          description: 'Операционная модель исполнения: `FBO` — со склада платформы; `FBS` — сборка у продавца, консолидация через платформу; `DBS` — продавец доставляет сам. В v1 поддерживаются `FBO` и `DBS`; `FBS` зарезервировано и пока не принимается.'
          enum: [FBO, FBS, DBS]
        legalEntityId: { type: string, format: uuid, readOnly: true, description: Юрлицо продавца, на которое оформлен заказ (см. `GET /legal-entities`) }
        customer: { $ref: '#/components/schemas/Ref' }
        receiver: { $ref: '#/components/schemas/Person' }
        deliveryAddress: { $ref: '#/components/schemas/Address' }
        deliveryOption:
          type: object
          description: Выбранные условия доставки (снимок на момент оформления)
          properties:
            deliveryCode: { type: string }
            etaDays: { type: integer }
        desiredDeliveryDate: { type: string, format: date }
        deliveryInterval: { $ref: '#/components/schemas/TimeInterval' }
        confirmationType:
          type: string
          description: 'Подтверждение заказа: `SELF_CONFIRM` — авто; `CALLCENTER` — через звонок колл-центра.'
          enum: [SELF_CONFIRM, CALLCENTER]
        customerComment: { type: string, description: Комментарий покупателя }
        deliveryComment: { type: string, description: Комментарий для курьера/доставки }
        tags:
          type: array
          description: Метки заказа (произвольные)
          items: { type: string }
        paymentMethod: { $ref: '#/components/schemas/PaymentMethod' }
        paymentStatus: { $ref: '#/components/schemas/PaymentStatus' }
        codExpected: { $ref: '#/components/schemas/Money' }
        price:
          type: object
          properties:
            total: { $ref: '#/components/schemas/Money' }
            vatAmount: { $ref: '#/components/schemas/Money' }
            discounts:
              type: array
              items: { $ref: '#/components/schemas/OrderDiscount' }
        trackingNumber: { type: string, description: Трек-номер основного отправления заказа }
        version: { type: integer }
        createdAt: { type: string, format: date-time }
        updatedAt: { type: string, format: date-time }
        lines:
          type: array
          description: 'Подгружается через ?include=lines'
          items: { $ref: '#/components/schemas/OrderLine' }
        statusHistory:
          type: array
          description: 'Подгружается через ?include=statusHistory'
          items: { $ref: '#/components/schemas/StatusHistoryEntry' }
        cancellation:
          type: object
          description: Данные отмены (если заказ отменён/отменяется)
          properties:
            occurredAt: { type: string, format: date-time }
            reason: { type: string }
      required: [id, number, status]
    TimeInterval:
      type: object
      description: 'Желаемое окно времени доставки (HH:mm)'
      properties:
        from: { type: string, examples: ['09:00'] }
        to: { type: string, examples: ['18:00'] }
    FileObject:
      type: object
      description: Документ заказа (этикетка / накладная / акт)
      properties:
        id: { type: string, format: uuid }
        kind: { $ref: '#/components/schemas/FileKind' }
        url: { type: string, format: uri, description: 'Подписанная непубличная ссылка на документ (может содержать ПДн получателя)' }
        printAtPacking: { type: boolean, description: Печатать при упаковке на складе }
        copies: { type: integer }
        uploadedAt: { type: string, format: date-time }
    FileObjectInput:
      type: object
      properties:
        kind: { $ref: '#/components/schemas/FileKind' }
        url: { type: string, format: uri }
        printAtPacking: { type: boolean }
        copies: { type: integer }
      required: [url]
    SaleRecord:
      type: object
      description: Реализация — учётный документ перехода товара покупателю (для сверки/отчётности/1С). Частичный выкуп даёт несколько записей.
      properties:
        id: { type: string, format: uuid }
        number: { type: string }
        orderId: { type: string, format: uuid }
        kind:
          type: string
          description: '`SALE` — реализация; `REVERSAL` — компенсация (возврат после реализации)'
          enum: [SALE, REVERSAL]
        reversalOfId: { type: string, format: uuid, description: 'Для `REVERSAL` — id исходной реализации' }
        total: { $ref: '#/components/schemas/Money' }
        lines:
          type: array
          items:
            type: object
            properties:
              sku: { $ref: '#/components/schemas/Ref' }
              qty: { type: number }
              sum: { $ref: '#/components/schemas/Money' }
        createdAt: { type: string, format: date-time }
    OrderPatch:
      type: object
      description: Частичное обновление заказа (только разрешённые в текущем статусе поля)
      properties:
        receiver: { $ref: '#/components/schemas/Person' }
        deliveryAddress: { $ref: '#/components/schemas/Address' }
        deliveryChannelId: { type: string, format: uuid }
        pickupPointCode: { type: string, description: 'Смена ПВЗ/постамата — до передачи заказа в доставку' }
    OrderLine:
      type: object
      description: Позиция заказа
      properties:
        id: { type: string, format: uuid }
        lineNo: { type: integer }
        sku: { $ref: '#/components/schemas/Ref' }
        skuType: { type: string, enum: [SKU, SERVICE, PHYSICAL_SET, BUNDLE] }
        qty: { type: number, description: Количество (допускается дробное для весовых товаров) }
        price: { $ref: '#/components/schemas/Money' }
        vatRate: { $ref: '#/components/schemas/VatRate' }
        sum: { $ref: '#/components/schemas/Money' }
        needReserve: { type: boolean, description: 'Резервировать позицию (false — например, для строк-услуг). По умолчанию true.' }
        reservedQty: { type: number, readOnly: true, description: Зарезервировано под позицию }
        availableQty: { type: number, readOnly: true, description: Доступно к резерву }
        stage:
          type: string
          readOnly: true
          description: |
            Стадия позиции (позиционный оборот — истина исполнения на уровне строки;
            критично при частичном выкупе): `ORDERED` — оформлена; `RESERVED` —
            зарезервирована; `PACKED` — упакована; `SHIPPED` — отгружена; `DELIVERED` —
            доставлена; `SOLD` — выкуплена (реализация); `LOST` — утеряна; `RETURNED` —
            возвращена; `RETURNED_DEFECTIVE` — возвращена браком; `RETURNED_TO_CLIENT` —
            возвращена отправителю.
          enum: [ORDERED, RESERVED, PACKED, SHIPPED, DELIVERED, SOLD, LOST, RETURNED, RETURNED_DEFECTIVE, RETURNED_TO_CLIENT]
        markingCodes:
          type: array
          description: Коды маркировки «Честный знак» (Datamatrix), переданные при отгрузке (read-only)
          items: { type: string }
    OrderLineInput:
      type: object
      properties:
        skuId: { type: string, format: uuid }
        qty: { type: number, exclusiveMinimum: 0, description: Количество (допускается дробное для весовых товаров) }
        price: { $ref: '#/components/schemas/Money' }
      required: [skuId, qty]
    OrderCreate:
      type: object
      description: Тело создания заказа
      properties:
        externalId: { type: string, description: Идентификатор заказа во внешней системе (для сопоставления при интеграции) }
        externalNumber: { type: string, description: Человекочитаемый номер заказа во внешней системе }
        receiver: { $ref: '#/components/schemas/Person' }
        deliveryAddress: { $ref: '#/components/schemas/Address' }
        deliveryChannelId: { type: string, format: uuid, description: Канал доставки }
        pickupPointCode: { type: string, description: Код выбранного ПВЗ/постамата (для самовывоза/ПВЗ-доставки) }
        deliveryServices:
          type: array
          description: 'Доп-услуги доставки (`TRYING` — примерка, `PARTIAL_DELIVERY` — частичная выдача и т.п.)'
          items: { type: string }
        paymentMethod: { $ref: '#/components/schemas/PaymentMethod' }
        saleMode: { $ref: '#/components/schemas/SaleMode' }
        promotionCode: { type: string, description: 'Промокод из расчёта `POST /pricing:quote` — применяется к заказу' }
        manualDiscount:
          type: object
          description: Ручная скидка (как в расчёте цены)
          properties:
            percentBps: { type: integer, minimum: 0, maximum: 10000, description: 'Процент в базисных пунктах (10000 = 100%)' }
            fixAmount: { $ref: '#/components/schemas/Money' }
        desiredDeliveryDate: { type: string, format: date }
        deliveryInterval: { $ref: '#/components/schemas/TimeInterval' }
        confirmationType:
          type: string
          enum: [SELF_CONFIRM, CALLCENTER]
        customerComment: { type: string, description: Комментарий покупателя }
        deliveryComment: { type: string, description: Комментарий для курьера/доставки }
        tags:
          type: array
          items: { type: string }
        files:
          type: array
          description: Печатные документы заказа по ссылке (этикетки/накладные) — без загрузки бинарника
          items: { $ref: '#/components/schemas/FileObjectInput' }
        warehouseId:
          type: string
          format: uuid
          description: Склад отгрузки. Если не указан, выбирается по правилам маршрутизации Fulcore.
        lines:
          type: array
          items: { $ref: '#/components/schemas/OrderLineInput' }
      required: [receiver, deliveryAddress, deliveryChannelId, paymentMethod, lines]
    Fulfillment:
      type: object
      description: Отгрузка — передача позиций заказа в доставку
      properties:
        id: { type: string, format: uuid }
        orderId: { type: string, format: uuid }
        status:
          type: string
          description: |
            Статус отгрузки (внешняя проекция складского исполнения): `ASSEMBLING` —
            комплектуется (сборка/ошибки сборки); `ASSEMBLED` — собрана; `SHIPPED` —
            отгружена; `CANCELED` — отменена.
          enum: [ASSEMBLING, ASSEMBLED, SHIPPED, CANCELED]
        shipmentId: { type: string, format: uuid, description: Связанное отправление }
        shipBy: { type: string, format: date-time, description: Плановая дата отгрузки }
        documentsRequired: { type: boolean, description: Требуются сопроводительные документы для отгрузки }
        documentsUploaded: { type: boolean, description: Документы загружены }
        lines:
          type: array
          items:
            type: object
            properties:
              sku: { $ref: '#/components/schemas/Ref' }
              qty: { type: number }
              reservedQty: { type: number }
        createdAt: { type: string, format: date-time }
        updatedAt: { type: string, format: date-time }
    CarrierPickup:
      type: object
      description: Заявка на забор груза перевозчиком (для self-fulfillment / FBS)
      properties:
        id: { type: string, format: uuid }
        type:
          type: string
          description: '`COURIER` — перевозчик приезжает на склад; `SELF_PICKUP` — самопривоз в пункт перевозчика'
          enum: [COURIER, SELF_PICKUP]
        warehouseId: { type: string, format: uuid }
        desiredDate: { type: string, format: date }
        timeSlot: { $ref: '#/components/schemas/TimeInterval' }
        placesCount: { type: integer }
        status: { type: string, enum: [CREATED, AWAITING_CARGO, CARGO_SHIPPED, HOLD, CANCELED] }
        trackingNumber: { type: string }
        updatedAt: { type: string, format: date-time }
    CarrierPickupCreate:
      type: object
      properties:
        type: { type: string, enum: [COURIER, SELF_PICKUP] }
        warehouseId: { type: string, format: uuid }
        desiredDate: { type: string, format: date }
        timeSlot: { $ref: '#/components/schemas/TimeInterval' }
        placesCount: { type: integer }
      required: [type, warehouseId]
    InventoryAdjustment:
      type: object
      description: Корректировка остатков (оприходование / списание / смена состояния)
      properties:
        id: { type: string, format: uuid }
        warehouseId: { type: string, format: uuid }
        op:
          type: string
          description: '`ADD` — оприходовать; `SUBTRACT` — списать; `CONDITION` — сменить состояние (например, годный → брак)'
          enum: [ADD, SUBTRACT, CONDITION]
        reason: { type: string }
        lines:
          type: array
          items:
            type: object
            properties:
              sku: { $ref: '#/components/schemas/Ref' }
              qty: { type: number, exclusiveMinimum: 0 }
              fromCondition: { type: string, enum: [NORMAL, DEFECTIVE, EXPIRED] }
              toCondition: { type: string, enum: [NORMAL, DEFECTIVE, EXPIRED] }
        createdAt: { type: string, format: date-time }
        updatedAt: { type: string, format: date-time }
      required: [warehouseId, op, lines]
    InventoryAdjustmentCreate:
      type: object
      description: Тело создания корректировки остатков
      properties:
        warehouseId: { type: string, format: uuid }
        op:
          type: string
          description: '`ADD` — оприходовать; `SUBTRACT` — списать; `CONDITION` — сменить состояние (например, годный → брак)'
          enum: [ADD, SUBTRACT, CONDITION]
        reason: { type: string }
        lines:
          type: array
          items:
            type: object
            properties:
              skuId: { type: string, format: uuid }
              qty: { type: number, exclusiveMinimum: 0 }
              fromCondition: { type: string, enum: [NORMAL, DEFECTIVE, EXPIRED] }
              toCondition: { type: string, enum: [NORMAL, DEFECTIVE, EXPIRED] }
            required: [skuId, qty]
      required: [warehouseId, op, lines]
    Transfer:
      type: object
      description: Перемещение товара между складами
      properties:
        id: { type: string, format: uuid }
        number: { type: string }
        fromWarehouseId: { type: string, format: uuid }
        toWarehouseId: { type: string, format: uuid }
        needDelivery: { type: boolean }
        status: { type: string, enum: [CREATED, INSUFFICIENT, RESERVED, PICKING, SHIPPED, ARRIVED, RECEIVED, CANCELED] }
        lines:
          type: array
          items:
            type: object
            properties:
              sku: { $ref: '#/components/schemas/Ref' }
              qty: { type: number, exclusiveMinimum: 0 }
              condition: { type: string, enum: [NORMAL, DEFECTIVE, EXPIRED] }
        updatedAt: { type: string, format: date-time }
      required: [fromWarehouseId, toWarehouseId, lines]
    TransferCreate:
      type: object
      properties:
        fromWarehouseId: { type: string, format: uuid }
        toWarehouseId: { type: string, format: uuid }
        needDelivery: { type: boolean }
        lines:
          type: array
          items:
            type: object
            properties:
              skuId: { type: string, format: uuid }
              qty: { type: number, exclusiveMinimum: 0 }
              condition: { type: string, enum: [NORMAL, DEFECTIVE, EXPIRED] }
            required: [skuId, qty]
      required: [fromWarehouseId, toWarehouseId, lines]
    Product:
      type: object
      description: Товар — карточка, группирующая варианты (SKU) по осям (размер/цвет)
      properties:
        id: { type: string, format: uuid }
        number: { type: string, description: Человекочитаемый номер товара }
        name: { type: string }
        fullName: { type: string }
        brandName: { type: string }
        category: { $ref: '#/components/schemas/Ref' }
        attributes:
          type: array
          description: Описательные свойства карточки (общие для всех вариантов)
          items: { $ref: '#/components/schemas/ProductAttribute' }
        options:
          type: array
          description: Оси вариантов товара (например, Размер, Цвет)
          items: { $ref: '#/components/schemas/ProductOption' }
        skus:
          type: array
          description: Варианты товара
          items: { $ref: '#/components/schemas/Ref' }
        defaultSkuId: { type: string, format: uuid, description: Вариант по умолчанию (для отображения) }
        imageRefs:
          type: array
          items: { $ref: '#/components/schemas/ImageRef' }
        status: { type: string, enum: [ACTIVE, DISABLED, DELETED] }
        updatedAt: { type: string, format: date-time }
    ProductOption:
      type: object
      description: Ось вариантов (например, «Размер» со значениями S/M/L)
      properties:
        id: { type: string, format: uuid }
        name: { type: string }
        position: { type: integer }
        values:
          type: array
          items: { $ref: '#/components/schemas/ProductOptionValue' }
    ProductOptionValue:
      type: object
      description: Значение оси варианта
      properties:
        id: { type: string, format: uuid }
        value: { type: string }
        label: { type: string }
        colorHex: { type: string, description: 'HEX-цвет для swatch (для оси «Цвет»)' }
        position: { type: integer }
    ProductAttribute:
      type: object
      description: 'Описательный атрибут товара (например, состав, уход, сезон)'
      properties:
        code: { type: string }
        name: { type: string }
        value: { type: string }
    ImageRef:
      type: object
      description: Ссылка на изображение (бинарник хранится во внешнем media-сервисе)
      properties:
        url: { type: string, format: uri }
        alt: { type: string }
        position: { type: integer }
        role: { type: string, enum: [MAIN, GALLERY, SWATCH] }
    ProductCategory:
      type: object
      description: Категория каталога (дерево)
      properties:
        id: { type: string, format: uuid }
        name: { type: string }
        parentId: { type: string, format: uuid }
        isRoot: { type: boolean }
    ProductCreate:
      type: object
      description: |
        Создание товара целиком: карточка + оси вариантов + варианты (SKU). Оси и их
        значения задаются по имени (id присваивает Fulcore); `optionValues` варианта
        ссылаются на ось/значение по имени.
      properties:
        name: { type: string }
        fullName: { type: string }
        brandName: { type: string }
        categoryId: { type: string, format: uuid }
        attributes:
          type: array
          items: { $ref: '#/components/schemas/ProductAttribute' }
        options:
          type: array
          description: Оси вариантов товара
          items:
            type: object
            properties:
              name: { type: string }
              position: { type: integer }
              values:
                type: array
                items:
                  type: object
                  properties:
                    value: { type: string }
                    label: { type: string }
                    colorHex: { type: string }
                  required: [value]
            required: [name, values]
        skus:
          type: array
          description: Варианты товара
          items:
            type: object
            properties:
              article: { type: string }
              type: { type: string, enum: [SKU, SERVICE, PHYSICAL_SET, BUNDLE] }
              vatRate: { $ref: '#/components/schemas/VatRate' }
              shortName: { type: string }
              marking: { $ref: '#/components/schemas/Marking' }
              optionValues:
                type: array
                description: По одному значению на ось товара
                items:
                  type: object
                  properties:
                    option: { type: string, description: Имя оси }
                    value: { type: string, description: Значение оси }
                  required: [option, value]
            required: [article, type]
        imageRefs:
          type: array
          items: { $ref: '#/components/schemas/ImageRef' }
      required: [name, skus]
    Shipment:
      type: object
      description: Отправление — данные доставки по заказу
      properties:
        id: { type: string, format: uuid }
        orderId: { type: string, format: uuid }
        status:
          type: string
          description: |
            Статус отправления: `REGISTERED` — зарегистрировано у перевозчика;
            `IN_TRANSIT` — в пути; `DELIVERED` — доставлено; `RETURNING` —
            возвращается; `RETURNED` — возвращено отправителю.
          enum: [REGISTERED, IN_TRANSIT, DELIVERED, RETURNING, RETURNED]
        carrier:
          type: object
          properties:
            deliveryCode: { type: string }
            name: { type: string }
        trackingNumber: { type: string }
        eta: { type: string, format: date }
        places:
          type: array
          description: Грузовые места отправления (для трекинга/сверки с перевозчиком)
          items:
            type: object
            properties:
              placeNo: { type: integer }
              weightG: { type: integer }
              dimsMm:
                type: array
                items: { type: integer }
                minItems: 3
                maxItems: 3
              barcode: { type: string }
        updatedAt: { type: string, format: date-time }
    ReturnRequest:
      type: object
      description: Заявка на возврат позиций заказа
      properties:
        id: { type: string, format: uuid }
        orderId: { type: string, format: uuid }
        status:
          type: string
          description: |
            Статус заявки: `REQUESTED` — создана; `ACCEPTED` — принята;
            `REJECTED` — отклонена; `RECEIVED` — товар получен.
          enum: [REQUESTED, ACCEPTED, REJECTED, RECEIVED]
        inspectionStatus:
          type: string
          description: 'Итог инспекции (read-only): `PENDING` — ожидает; `INSPECTING` — на инспекции; `ACCEPTED_TO_STOCK` — годен, на склад; `ACCEPTED_TO_DEFECT` — брак, в дефект; `REJECTED` — отклонён.'
          enum: [PENDING, INSPECTING, ACCEPTED_TO_STOCK, ACCEPTED_TO_DEFECT, REJECTED]
        lines:
          type: array
          items: { $ref: '#/components/schemas/ReturnLine' }
        updatedAt: { type: string, format: date-time }
      required: [id, orderId, status, lines]
    ReturnLine:
      type: object
      properties:
        orderLineId: { type: string, format: uuid }
        qty: { type: number, exclusiveMinimum: 0 }
        reason: { type: string }
        isDefected: { type: boolean, description: Брак (маршрутизируется в дефектный остаток) }
        isReclamation: { type: boolean, description: 'Рекламация (претензия): позиция была выкуплена и возвращена' }
      required: [orderLineId, qty]
    ReturnRequestCreate:
      type: object
      description: Тело создания заявки на возврат
      properties:
        orderId: { type: string, format: uuid }
        lines:
          type: array
          items: { $ref: '#/components/schemas/ReturnLine' }
      required: [orderId, lines]
    ReturnableLines:
      type: object
      description: Позиции заказа, доступные к возврату (с учётом доставленного и возвращённого)
      properties:
        orderId: { type: string, format: uuid }
        lines:
          type: array
          items:
            type: object
            properties:
              orderLineId: { type: string, format: uuid }
              sku: { $ref: '#/components/schemas/Ref' }
              qtyDelivered: { type: number, description: Доставлено/выкуплено }
              qtyReturnable: { type: number, description: Доступно к возврату }
    Batch:
      type: object
      description: Партия (для товаров с учётом сроков годности)
      properties:
        number: { type: string, description: Номер партии }
        productionDate: { type: string, format: date }
        expirationDate: { type: string, format: date }
    PurchaseOrder:
      type: object
      description: Заказ поставщику (закупка товара для пополнения склада)
      properties:
        id: { type: string, format: uuid }
        number: { type: string }
        supplier: { $ref: '#/components/schemas/Ref' }
        warehouseId: { type: string, format: uuid }
        status: { $ref: '#/components/schemas/PurchaseOrderStatus' }
        hasReceivingDiscrepancy: { type: boolean }
        receivingDiscrepancyAccepted: { type: boolean }
        noDiscrepanciesAllowed: { type: boolean, description: 'Жёсткий контракт приёмки: расхождение факт/план блокирует приёмку (а не создаёт `DIFFERENCE`)' }
        dateOfDeliveryPlan: { type: string, format: date }
        price: { $ref: '#/components/schemas/PriceSummary' }
        lines:
          type: array
          items: { $ref: '#/components/schemas/PurchaseOrderLine' }
        updatedAt: { type: string, format: date-time }
      required: [id, supplier, warehouseId]
    PurchaseOrderLine:
      type: object
      properties:
        lineNo: { type: integer }
        sku: { $ref: '#/components/schemas/Ref' }
        qty: { type: number, exclusiveMinimum: 0 }
        price: { $ref: '#/components/schemas/Money' }
        batch: { $ref: '#/components/schemas/Batch' }
        receivedQty: { type: number, description: Фактически принято (read-only; для сверки с qty) }
    PurchaseOrderCreate:
      type: object
      properties:
        supplierId: { type: string, format: uuid }
        warehouseId: { type: string, format: uuid }
        noDiscrepanciesAllowed: { type: boolean, description: 'Жёсткий контракт приёмки: расхождение блокирует приёмку. По умолчанию false.' }
        dateOfDeliveryPlan: { type: string, format: date }
        lines:
          type: array
          items:
            type: object
            properties:
              skuId: { type: string, format: uuid }
              qty: { type: number, exclusiveMinimum: 0 }
              price: { $ref: '#/components/schemas/Money' }
              batch: { $ref: '#/components/schemas/Batch' }
            required: [skuId, qty]
      required: [supplierId, warehouseId, lines]
    PurchaseOrderPatch:
      type: object
      description: Частичное редактирование закупки (передавайте только меняемые поля; до активации)
      properties:
        supplierId: { type: string, format: uuid }
        warehouseId: { type: string, format: uuid }
        dateOfDeliveryPlan: { type: string, format: date }
        lines:
          type: array
          items:
            type: object
            properties:
              skuId: { type: string, format: uuid }
              qty: { type: number, exclusiveMinimum: 0 }
              price: { $ref: '#/components/schemas/Money' }
              batch: { $ref: '#/components/schemas/Batch' }
            required: [skuId, qty]
    Receipt:
      type: object
      description: Факт приёмки по закупке (что и в каком состоянии реально принято)
      properties:
        id: { type: string, format: uuid }
        purchaseOrderId: { type: string, format: uuid }
        warehouseId: { type: string, format: uuid }
        lines:
          type: array
          items:
            type: object
            properties:
              sku: { $ref: '#/components/schemas/Ref' }
              qtyFact: { type: number }
              condition: { type: string, enum: [NORMAL, DEFECTIVE, EXPIRED] }
              batch: { $ref: '#/components/schemas/Batch' }
        createdAt: { type: string, format: date-time }
    SupplierReturn:
      type: object
      description: Возврат товара поставщику
      properties:
        id: { type: string, format: uuid }
        number: { type: string }
        supplier: { $ref: '#/components/schemas/Ref' }
        warehouseId: { type: string, format: uuid }
        reason:
          type: string
          description: 'Причина: `DEFECT` — брак; `SURPLUS` — излишек; `MISGRADE` — пересортица; `EXPIRED` — просрочка; `OTHER` — иное.'
          enum: [DEFECT, SURPLUS, MISGRADE, EXPIRED, OTHER]
        status: { type: string, enum: [DRAFT, NEW, SHIPPED, COMPLETED, CANCELED] }
        lines:
          type: array
          items:
            type: object
            properties:
              sku: { $ref: '#/components/schemas/Ref' }
              qty: { type: number, exclusiveMinimum: 0 }
              price: { $ref: '#/components/schemas/Money' }
        createdAt: { type: string, format: date-time }
        updatedAt: { type: string, format: date-time }
    SupplierReturnCreate:
      type: object
      properties:
        supplierId: { type: string, format: uuid }
        warehouseId: { type: string, format: uuid }
        reason: { type: string, enum: [DEFECT, SURPLUS, MISGRADE, EXPIRED, OTHER] }
        lines:
          type: array
          items:
            type: object
            properties:
              skuId: { type: string, format: uuid }
              qty: { type: number, exclusiveMinimum: 0 }
              price: { $ref: '#/components/schemas/Money' }
            required: [skuId, qty]
      required: [supplierId, warehouseId, reason, lines]
    InventoryLevel:
      type: object
      description: Остаток SKU на складе. `availableQty` и `reservedQty` относятся к указанному `condition`.
      properties:
        skuId: { type: string, format: uuid }
        warehouseId: { type: string, format: uuid }
        condition:
          type: string
          description: 'Состояние товара: `NORMAL` — годный; `DEFECTIVE` — брак; `EXPIRED` — просрочен.'
          enum: [NORMAL, DEFECTIVE, EXPIRED]
        availableQty: { type: number, minimum: 0, description: Доступно к заказу }
        reservedQty: { type: number, minimum: 0, description: Зарезервировано под заказы }
        inProcessQty: { type: number, minimum: 0, readOnly: true, description: В активной сборке/упаковке }
        batches:
          type: array
          readOnly: true
          description: Разбивка остатка по партиям (для SKU с партионным учётом; сроки годности)
          items:
            type: object
            properties:
              number: { type: string, description: Номер партии }
              expirationDate: { type: string, format: date }
              qty: { type: number, minimum: 0 }
    Sku:
      type: object
      description: Складская единица (SKU) — конкретный вариант товара
      properties:
        id: { type: string, format: uuid }
        productId: { type: string, format: uuid, description: Товар, к которому относится вариант }
        number: { type: string, description: Человекочитаемый номер SKU }
        externalId: { type: string, description: Идентификатор SKU во внешней системе мерчанта (ключ проекции/идемпотентности batchUpsert) }
        article: { type: string, description: Артикул продавца }
        optionValues:
          type: array
          description: Выбранные значения осей товара (по одному на ось — Размер=M, Цвет=red)
          items: { $ref: '#/components/schemas/SkuOptionValue' }
        type:
          type: string
          description: |
            Тип: `SKU` — обычный товар; `SERVICE` — услуга; `PHYSICAL_SET` —
            набор (физический); `BUNDLE` — комплект.
          enum: [SKU, SERVICE, PHYSICAL_SET, BUNDLE]
        vatRate: { $ref: '#/components/schemas/VatRate' }
        shortName: { type: string }
        fullName: { type: string }
        barcodes:
          type: array
          description: Штрихкоды SKU
          items: { $ref: '#/components/schemas/Barcode' }
        package: { $ref: '#/components/schemas/SkuPackage' }
        expiration: { $ref: '#/components/schemas/Expiration' }
        batchAccounting:
          type: object
          description: Партионный учёт (номер партии / даты при приёмке)
          properties:
            enabled: { type: boolean }
        bundleComponents:
          type: array
          description: 'Состав набора/комплекта (для `type` PHYSICAL_SET/BUNDLE)'
          items: { $ref: '#/components/schemas/BundleComponent' }
        bundleOptionalCount: { type: integer, description: 'Сколько опциональных компонентов выбирает покупатель (для BUNDLE)' }
        marking: { $ref: '#/components/schemas/Marking' }
        imageRefs:
          type: array
          items: { $ref: '#/components/schemas/ImageRef' }
        status:
          type: string
          description: 'Статус SKU: `ACTIVE` — активен; `DISABLED` — скрыт (не продаётся, остаток виден); `DELETED` — удалён.'
          enum: [ACTIVE, DISABLED, DELETED]
        updatedAt: { type: string, format: date-time }
      required: [id, number, article, type]
    Expiration:
      type: object
      description: 'Срок годности (ОСГ) — критично для FBO (еда/косметика/фарма)'
      properties:
        mode:
          type: string
          description: '`NO_EXPIRATION` — без срока; `USE_EXPIRATION` — со сроком годности (партии учитываются с датами).'
          enum: [NO_EXPIRATION, USE_EXPIRATION]
        daysLimit: { type: integer, description: Срок годности в днях }
        monthsLimit: { type: integer, description: Срок годности в месяцах }
      required: [mode]
    BundleComponent:
      type: object
      description: Компонент набора/комплекта (только плоские SKU, без вложенности)
      properties:
        childSkuId: { type: string, format: uuid }
        qty: { type: number, exclusiveMinimum: 0 }
        optional: { type: boolean, description: 'true — опциональный (только для BUNDLE)' }
      required: [childSkuId, qty]
    Barcode:
      type: object
      properties:
        value: { type: string }
        kind: { type: string, enum: [COMMON, MARKETPLACE] }
        marketplace: { type: string, description: 'Маркетплейс штрихкода (для `kind=MARKETPLACE`): WILDBERRIES, OZON, …' }
        isDefault: { type: boolean }
      required: [value]
    SkuPackage:
      type: object
      description: Габариты и вес для упаковки и расчёта доставки
      properties:
        weightG: { type: integer, description: Вес, граммы }
        dimsMm:
          type: array
          description: 'Габариты ДxШxВ, мм'
          items: { type: integer }
          minItems: 3
          maxItems: 3
    SkuPatch:
      type: object
      description: Частичное обновление SKU
      properties:
        article: { type: string }
        shortName: { type: string }
        fullName: { type: string }
        vatRate: { $ref: '#/components/schemas/VatRate' }
        barcodes:
          type: array
          items: { $ref: '#/components/schemas/Barcode' }
        package: { $ref: '#/components/schemas/SkuPackage' }
        expiration: { $ref: '#/components/schemas/Expiration' }
        marking: { $ref: '#/components/schemas/Marking' }
        bundleOptionalCount: { type: integer }
        status: { type: string, enum: [ACTIVE, DISABLED, DELETED] }
    ProductPatch:
      type: object
      description: Частичное обновление товара
      properties:
        name: { type: string }
        fullName: { type: string }
        brandName: { type: string }
        categoryId: { type: string, format: uuid }
        attributes:
          type: array
          items: { $ref: '#/components/schemas/ProductAttribute' }
        defaultSkuId: { type: string, format: uuid }
        status: { type: string, enum: [ACTIVE, DISABLED, DELETED] }
    SkuOptionValue:
      type: object
      description: Выбор значения оси варианта для SKU
      properties:
        optionId: { type: string, format: uuid }
        valueId: { type: string, format: uuid }
        value: { type: string, description: Денормализованное значение для отображения (read-only) }
      required: [optionId, valueId]
    SkuCreate:
      type: object
      description: Тело создания SKU
      properties:
        externalId: { type: string, description: Идентификатор SKU во внешней системе мерчанта }
        productId: { type: string, format: uuid, description: Товар, к которому относится вариант }
        article: { type: string, description: Артикул продавца }
        type:
          type: string
          enum: [SKU, SERVICE, PHYSICAL_SET, BUNDLE]
        vatRate: { $ref: '#/components/schemas/VatRate' }
        shortName: { type: string }
        fullName: { type: string }
        optionValues:
          type: array
          items: { $ref: '#/components/schemas/SkuOptionValue' }
        barcodes:
          type: array
          items: { $ref: '#/components/schemas/Barcode' }
        package: { $ref: '#/components/schemas/SkuPackage' }
        expiration: { $ref: '#/components/schemas/Expiration' }
        batchAccounting:
          type: object
          properties:
            enabled: { type: boolean }
        bundleComponents:
          type: array
          items: { $ref: '#/components/schemas/BundleComponent' }
        bundleOptionalCount: { type: integer }
        marking: { $ref: '#/components/schemas/Marking' }
      required: [article, type]
    Marking:
      type: object
      description: |
        Обязательная маркировка «Честный знак». `required` — товар подлежит маркировке;
        `productGroup` — товарная группа (например, `LIGHT_INDUSTRY` — одежда/текстиль,
        `SHOES` — обувь, `PERFUMERY` — парфюм). Коды маркировки (Datamatrix) передаются
        при отгрузке в `OrderLine.markingCodes`.
      properties:
        required: { type: boolean }
        productGroup: { type: string }
      required: [required]
    QuoteRequest:
      type: object
      properties:
        lines:
          type: array
          items: { $ref: '#/components/schemas/SkuQty' }
        promotionCode: { type: string }
        paymentMethod: { $ref: '#/components/schemas/PaymentMethod' }
        manualDiscount:
          type: object
          description: Ручная скидка для предпросмотра расчёта
          properties:
            percentBps: { type: integer, minimum: 0, maximum: 10000, description: 'Процент в базисных пунктах (10000 = 100%)' }
            fixAmount: { $ref: '#/components/schemas/Money' }
      required: [lines]
    QuoteResult:
      type: object
      properties:
        total: { $ref: '#/components/schemas/Money' }
        lines:
          type: array
          items:
            type: object
            properties:
              skuId: { type: string, format: uuid }
              base: { $ref: '#/components/schemas/Money' }
              discounts:
                type: array
                items: { $ref: '#/components/schemas/OrderDiscount' }
              total: { $ref: '#/components/schemas/Money' }
        promotion:
          type: object
          description: Эффект промокода
          properties:
            applied: { type: boolean }
            perSku:
              type: array
              description: Разбивка скидки промокода по SKU (для перечёркнутых цен)
              items:
                type: object
                properties:
                  skuId: { type: string, format: uuid }
                  discount: { $ref: '#/components/schemas/Money' }
            deliveryDiscount: { $ref: '#/components/schemas/Money' }
            bonusItems:
              type: array
              description: Бонус-товары по промоакции
              items: { $ref: '#/components/schemas/SkuQty' }
    PriceList:
      type: object
      description: Прайс-лист
      properties:
        id: { type: string, format: uuid }
        code: { type: string }
        name: { type: string }
        currency: { type: string, description: Код валюты ISO-4217 }
        isDefault: { type: boolean }
      required: [code, currency]
    PriceListCreate:
      type: object
      description: Тело создания прайс-листа
      properties:
        code: { type: string }
        name: { type: string }
        currency: { type: string, description: Код валюты ISO-4217 }
      required: [code, currency]
    ActualPrice:
      type: object
      description: Актуальная цена SKU на текущий момент
      properties:
        skuId: { type: string, format: uuid }
        priceListId: { type: string, format: uuid }
        price: { $ref: '#/components/schemas/Money' }
        effectiveFrom: { type: string, format: date-time }
    Promotion:
      type: object
      description: |
        Промоакция / промокод (плоская форма). Сложные условия (комбинации SKU, гео,
        метод оплаты/доставки) — см. open-questions PRC. Поле `id`/`useCount` — read-only.
      properties:
        id: { type: string, format: uuid }
        code: { type: string }
        active: { type: boolean }
        percentBps: { type: integer, minimum: 0, maximum: 10000, description: 'Процентная скидка в базисных пунктах (10000 = 100%). Не более одного из `percentBps`/`fixAmount`; оба пусты — промо только на доставку/бонусы.' }
        fixAmount: { $ref: '#/components/schemas/Money', description: 'Фиксированная скидка. Не более одного из `percentBps`/`fixAmount`.' }
        deliveryDiscountBps: { type: integer, description: 'Скидка на доставку в базисных пунктах (cap 10000 = 100%)' }
        bonusItems:
          type: array
          description: Бонус-товары при срабатывании промо
          items: { $ref: '#/components/schemas/SkuQty' }
        validFrom: { type: string, format: date-time }
        validUntil: { type: string, format: date-time }
        useLimit: { type: integer, description: Глобальный лимит применений }
        useCount: { type: integer, readOnly: true }
        minOrderAmount: { $ref: '#/components/schemas/Money' }
      required: [code]
    PromotionCreate:
      type: object
      description: Тело создания промоакции (без read-only `id`/`useCount`)
      properties:
        code: { type: string }
        active: { type: boolean }
        percentBps: { type: integer, minimum: 0, maximum: 10000, description: 'Процентная скидка в базисных пунктах (10000 = 100%). Не более одного из `percentBps`/`fixAmount`; оба пусты — промо только на доставку/бонусы.' }
        fixAmount: { $ref: '#/components/schemas/Money', description: 'Фиксированная скидка. Не более одного из `percentBps`/`fixAmount`.' }
        deliveryDiscountBps: { type: integer, description: 'Скидка на доставку в базисных пунктах (cap 10000 = 100%)' }
        bonusItems:
          type: array
          description: Бонус-товары при срабатывании промо
          items: { $ref: '#/components/schemas/SkuQty' }
        validFrom: { type: string, format: date-time }
        validUntil: { type: string, format: date-time }
        useLimit: { type: integer, description: Глобальный лимит применений }
        minOrderAmount: { $ref: '#/components/schemas/Money' }
      required: [code]
    PromotionPatch:
      type: object
      description: Частичное редактирование промоакции (передавайте только меняемые поля)
      properties:
        code: { type: string }
        active: { type: boolean }
        percentBps: { type: integer, minimum: 0, maximum: 10000, description: 'Процентная скидка в базисных пунктах (10000 = 100%). Не более одного из `percentBps`/`fixAmount`; оба пусты — промо только на доставку/бонусы.' }
        fixAmount: { $ref: '#/components/schemas/Money', description: 'Фиксированная скидка. Не более одного из `percentBps`/`fixAmount`.' }
        deliveryDiscountBps: { type: integer }
        bonusItems:
          type: array
          items: { $ref: '#/components/schemas/SkuQty' }
        validFrom: { type: string, format: date-time }
        validUntil: { type: string, format: date-time }
        useLimit: { type: integer }
        minOrderAmount: { $ref: '#/components/schemas/Money' }
    DeliveryQuoteRequest:
      type: object
      properties:
        deliveryAddress: { $ref: '#/components/schemas/Address' }
        deliveryCode: { type: string, description: Код канала/службы доставки (для расчёта конкретного варианта) }
        paymentMethod: { $ref: '#/components/schemas/PaymentMethod' }
        codExpected: { $ref: '#/components/schemas/Money' }
        deliveryServices:
          type: array
          description: 'Доп-услуги доставки (например, `TRYING` — примерка, `PARTIAL_DELIVERY` — частичная выдача)'
          items: { type: string }
        lines:
          type: array
          items: { $ref: '#/components/schemas/SkuQty' }
      required: [deliveryAddress, lines]
    DeliveryQuote:
      type: object
      properties:
        options:
          type: array
          items:
            type: object
            properties:
              deliveryCode: { type: string }
              deliveryChannelId: { type: string, format: uuid, description: 'Канал доставки для передачи в `OrderCreate.deliveryChannelId`' }
              name: { type: string }
              type:
                type: string
                description: 'Тип доставки: `COURIER` — курьер; `PVZ` — пункт выдачи; `RUSSIAN_POST` — Почта России.'
                enum: [COURIER, PVZ, RUSSIAN_POST]
              price: { $ref: '#/components/schemas/Money' }
              etaDays: { type: integer, description: Ориентировочный срок доставки (дней) }
              etaDaysMax: { type: integer }
              trying: { type: boolean, description: Доступна примерка }
    PickupPoint:
      type: object
      description: Пункт выдачи заказов / постамат
      properties:
        code: { type: string }
        name: { type: string }
        type:
          type: string
          description: '`POSTAMAT` — постамат (нужен код ячейки); `PVZ` — пункт выдачи; `CASHIER_ISSUE_POINT` — касса выдачи.'
          enum: [POSTAMAT, PVZ, CASHIER_ISSUE_POINT]
        address: { $ref: '#/components/schemas/Address' }
        lat: { type: number }
        lon: { type: number }
        deliveryCode: { type: string }
        worktime: { type: string }
        cashAllowed: { type: boolean }
        cardAllowed: { type: boolean }
        maxCodAmount: { $ref: '#/components/schemas/Money' }
        trying: { type: boolean, description: Доступна примерка }
    DeliveryChannel:
      type: object
      description: Канал доставки (настроенный продавцом). Учётные данные перевозчика не возвращаются.
      properties:
        id: { type: string, format: uuid }
        code: { type: string, description: 'Код канала — совпадает с `deliveryCode` из `POST /delivery:quote` (для сопоставления варианта расчёта с каналом)' }
        name: { type: string }
        type:
          type: string
          description: '`DIRECT_CONTRACT` — прямой договор с перевозчиком; `AGGREGATOR_CONTRACT` — через агрегатор; `SELF_PICKUP` — самовывоз со склада; `OWN_COURIER` — свой курьер.'
          enum: [DIRECT_CONTRACT, AGGREGATOR_CONTRACT, SELF_PICKUP, OWN_COURIER]
        status: { type: string, enum: [ACTIVE, DISABLED] }
        isDefault: { type: boolean }
    PickupDates:
      type: object
      description: Доступные даты/интервалы забора (самовывоз/DBS)
      properties:
        code: { type: string }
        dates:
          type: array
          items:
            type: object
            properties:
              date: { type: string, format: date }
              intervals:
                type: array
                items: { $ref: '#/components/schemas/TimeInterval' }
    Warehouse:
      type: object
      description: Склад (справочник для `warehouseId`). Учётные данные WMS не возвращаются.
      properties:
        id: { type: string, format: uuid }
        name: { type: string }
        type:
          type: string
          description: '`MANUAL` — собственный склад продавца; `FULFILLMENT` — склад фулфилмента (WMS).'
          enum: [MANUAL, FULFILLMENT]
        operationType:
          type: string
          description: '`STORAGE` — хранение; `SORT_CENTER` — сортировочный центр.'
          enum: [STORAGE, SORT_CENTER]
        address: { $ref: '#/components/schemas/Address' }
        status: { type: string, enum: [ACTIVE, DISABLED] }
        isDefault: { type: boolean }
    Counterparty:
      type: object
      description: Контрагент (поставщик/перевозчик/контакт). Паспортные/персональные данные не возвращаются.
      properties:
        id: { type: string, format: uuid }
        kind: { type: string, enum: [PERSON, COMPANY] }
        roles:
          type: array
          items: { $ref: '#/components/schemas/CounterpartyRole' }
        name: { type: string }
        legalEntityId: { type: string, format: uuid }
        status: { type: string, enum: [ACTIVE, DISABLED] }
    LegalEntity:
      type: object
      description: Юридическое лицо продавца (справочник для `legalEntityId`). Банковские реквизиты не возвращаются.
      properties:
        id: { type: string, format: uuid }
        legalName: { type: string }
        inn: { type: string }
        kpp: { type: string }
        ogrn: { type: string }
        orgLegalType:
          type: string
          description: 'Форма: `OOO`, `AO`, `PAO`, `SELF_EMPLOYED` (самозанятый), …'
          enum: [OOO, AO, ZAO, PAO, COMPANY, SELF_EMPLOYED]
        taxType:
          type: string
          description: 'Налоговый режим: `GENERAL` (ОСН), `SIMPLIFIED_INCOMES` (УСН доходы), `SIMPLIFIED_INCOMES_MINUS_EXPENSES`, `PATENT`.'
          enum: [GENERAL, SIMPLIFIED_INCOMES, SIMPLIFIED_INCOMES_MINUS_EXPENSES, PATENT]
        status: { type: string, enum: [ACTIVE, SUSPENDED, DEACTIVATED] }
        isDefault: { type: boolean }
    Profile:
      type: object
      description: Профиль аккаунта продавца
      properties:
        id: { type: string, format: uuid }
        displayName: { type: string }
        status: { type: string }
        timezone: { type: string }
        country: { type: string }
        currency: { type: string }
    TenantConfig:
      type: object
      description: Настройки аккаунта, влияющие на витрину/checkout (только безопасный субсет)
      properties:
        timezone: { type: string }
        country: { type: string }
        currency: { type: string }
        stockVisibility:
          type: object
          properties:
            percentStock: { type: integer, description: 'Процент остатка, показываемого публично (100 = весь)' }
            lastStock: { type: integer, description: Минимальный неотображаемый резерв }
            showStocksPublic: { type: boolean }
        reservePolicy:
          type: object
          properties:
            defaultReserveOrders: { type: boolean, description: Резервировать остаток при создании заказа }
            reserveAfterPayment: { type: boolean, description: Резервировать только после оплаты }
    ReportTask:
      type: object
      description: |
        Асинхронная задача формирования отчёта/выгрузки (заказы, продажи, остатки,
        агентский отчёт и т.п.). Результат скачивается по защищённой ссылке
        `GET /reports/{id}/result` (не публичный URL).
      properties:
        id: { type: string, format: uuid }
        type: { type: string, description: 'Тип: `orders` / `sales` / `inventory` / `agent` / …' }
        params: { $ref: '#/components/schemas/ReportParams' }
        status: { type: string, enum: [NEW, IN_PROGRESS, DONE, ERROR] }
        error: { type: string }
        createdAt: { type: string, format: date-time }
    ReportTaskCreate:
      type: object
      properties:
        type: { type: string, enum: [orders, sales, inventory, agent], description: Тип отчёта }
        params: { $ref: '#/components/schemas/ReportParams' }
      required: [type]
    ReportParams:
      type: object
      description: Параметры отчёта (все поля опциональны, состав зависит от `type`)
      properties:
        from: { type: string, format: date }
        to: { type: string, format: date }
        warehouseId: { type: string, format: uuid }
        legalEntityId: { type: string, format: uuid, description: Для агентского отчёта }
    Invoice:
      type: object
      description: Счёт за период
      properties:
        id: { type: string, format: uuid }
        period: { type: string, description: 'Период счёта, `YYYY-MM`', examples: ['2026-06'] }
        legalEntity: { $ref: '#/components/schemas/Ref', description: Юрлицо, которому выставлен счёт (счета формируются per-юрлицо) }
        total: { $ref: '#/components/schemas/Money' }
        vatAmount: { $ref: '#/components/schemas/Money' }
        status: { type: string, enum: [NEW, AWAITING_PAYMENT, PARTIALLY_PAID, PAID, EXPIRED] }
        externalDocNum: { type: string, description: Номер документа во внешней системе (1С) для сверки }
        pdfUrl: { type: string, format: uri, description: 'Подписанная непубличная ссылка на PDF счёта (ограниченный срок жизни, доступ в контексте токена аккаунта)' }
        lines:
          type: array
          description: Строки счёта (услуги фулфилмента за период)
          items:
            type: object
            properties:
              title: { type: string, description: 'Услуга (приёмка, хранение, сборка, отгрузка…)' }
              qty: { type: number }
              measure: { type: string, description: 'Единица: шт / м³ / день' }
              rate: { $ref: '#/components/schemas/Money' }
              sum: { $ref: '#/components/schemas/Money' }
        updatedAt: { type: string, format: date-time }
    Balance:
      type: object
      description: Текущий баланс расчётов
      properties:
        value:
          $ref: '#/components/schemas/Money'
          description: 'Баланс. Положительное значение — средства в пользу клиента (аванс); отрицательное — задолженность.'
        agentDebt:
          $ref: '#/components/schemas/Money'
          description: Задолженность платформы перед продавцом по инкассированным COD-платежам (агентский долг).
    PaymentIntent:
      type: object
      description: Платёжное намерение — ссылка для онлайн-оплаты заказа
      properties:
        redirectUrl: { type: string, format: uri }
        expiresAt: { type: string, format: date-time }
      required: [redirectUrl, expiresAt]
    Payment:
      type: object
      description: Оплата по заказу (факт онлайн-оплаты или возврата)
      properties:
        id: { type: string, format: uuid }
        orderId: { type: string, format: uuid }
        amount: { $ref: '#/components/schemas/Money' }
        kind:
          type: string
          description: '`PAYMENT_COMPLETE` — оплата; `REFUND` — возврат средств.'
          enum: [PAYMENT_COMPLETE, REFUND]
        provider: { type: string, description: Платёжный провайдер (PSP) }
        commission: { $ref: '#/components/schemas/Money' }
        occurredAt: { type: string, format: date-time }
    CodPayment:
      type: object
      description: Инкассация наложенного платежа перевозчиком по заказу
      properties:
        id: { type: string, format: uuid }
        orderId: { type: string, format: uuid }
        amount: { $ref: '#/components/schemas/Money' }
        status:
          type: string
          description: 'Статус инкассации: `OPEN` / `CONFIRMED` / `CLOSED` / `BLOCKED`.'
          enum: [OPEN, CONFIRMED, CLOSED, BLOCKED]
        trackingNumber: { type: string }
        occurredAt: { type: string, format: date-time }
    WebhookEndpoint:
      type: object
      description: Подписка на исходящие события
      properties:
        id: { type: string, format: uuid }
        url: { type: string, format: uri }
        events:
          type: array
          items: { $ref: '#/components/schemas/WebhookEvent' }
        active: { type: boolean }
        createdAt: { type: string, format: date-time }
      required: [id, url, events]
    WebhookEndpointPatch:
      type: object
      description: Частичное изменение подписки (передавайте только меняемые поля)
      properties:
        url: { type: string, format: uri }
        events:
          type: array
          items: { $ref: '#/components/schemas/WebhookEvent' }
        active: { type: boolean }
    WebhookEndpointCreated:
      description: Подписка сразу после создания — единственный ответ, содержащий `secret`
      allOf:
        - $ref: '#/components/schemas/WebhookEndpoint'
        - type: object
          properties:
            secret:
              type: string
              description: HMAC-секрет для проверки подписи. Показывается только в этом ответе.
          required: [secret]
    WebhookEndpointCreate:
      type: object
      description: Тело создания подписки на события
      properties:
        url: { type: string, format: uri }
        events:
          type: array
          items: { $ref: '#/components/schemas/WebhookEvent' }
      required: [url, events]
    WebhookDelivery:
      type: object
      description: Конверт доставки события вебхука
      properties:
        id: { type: string, format: uuid, description: Идентификатор доставки (для дедупликации) }
        event: { $ref: '#/components/schemas/WebhookAnyEvent' }
        schemaVersion: { type: integer, description: 'Версия схемы `data` для этого типа события (при несовместимом изменении — инкремент)' }
        occurredAt: { type: string, format: date-time, description: Когда произошло бизнес-событие }
        createdAt: { type: string, format: date-time, description: Когда сформирована доставка }
        resource:
          type: object
          description: Ресурс, к которому относится событие
          properties:
            kind: { type: string, description: 'Тип ресурса: order, shipment, return, invoice, payment, purchase_order' }
            id: { type: string, format: uuid }
          required: [kind, id]
        data: { type: object, description: Полезная нагрузка события (поля зависят от типа события) }
      required: [id, event, schemaVersion, occurredAt, createdAt, resource, data]
    WebhookDeliveryRecord:
      type: object
      description: Запись журнала попыток доставки события
      properties:
        id: { type: string, format: uuid }
        event: { $ref: '#/components/schemas/WebhookAnyEvent' }
        status:
          type: string
          enum: [PENDING, DELIVERED, FAILED]
        attempts: { type: integer, description: Число выполненных попыток }
        lastResponseCode: { type: integer, description: HTTP-код последнего ответа подписчика }
        createdAt: { type: string, format: date-time }
      required: [id, event, status, attempts, createdAt]
    WebhookEvent:
      type: string
      description: Тип бизнес-события для подписки (`webhook.test` не подписывается — доставляется только командой `POST /webhook-endpoints/{id}/test`)
      enum:
        - order.status_changed
        - order.completed
        - order.shipped
        - order.delivered
        - shipment.registered
        - shipment.delivered
        - return.accepted
        - return.received
        - return.rejected
        - purchase_order.received
        - invoice.issued
        - payment.completed
        - payment.refunded
    WebhookAnyEvent:
      type: string
      description: Тип события в доставке — бизнес-события плюс синтетическое `webhook.test`
      enum:
        - order.status_changed
        - order.completed
        - order.shipped
        - order.delivered
        - shipment.registered
        - shipment.delivered
        - return.accepted
        - return.received
        - return.rejected
        - purchase_order.received
        - invoice.issued
        - payment.completed
        - payment.refunded
        - webhook.test
    Warning:
      type: object
      description: Предупреждение операции
      properties:
        code: { type: string }
        message: { type: string }
        blocking: { type: boolean, description: true — операция не выполнена }
      required: [code, message, blocking]
