Store Service — API Contract

Этот документ является единственным источником правды для API Store Service.

Бэкенд реализует контракт. Фронтенд потребляет контракт. Отклонения запрещены.

Содержание

Public API (Bearer JWT)

Торговые точки

POS-терминалы (BR 1.5)

Столы зала (dine-in)

Маркетинговая информация (BR 6.1)

Internal API (Service Token)


GET /stores

Получить список торговых точек с пагинацией, поиском и фильтрами.

ПараметрЗначение
AuthBearer JWT (Franchise — все; Franchisee — только ТТ своего ЮЛ; Manager — только своя ТТ; Cashier — 403)

Query Parameters

ParamTypeRequiredDescription
pageintegernoНомер страницы (default: 1)
per_pageintegernoЗаписей на страницу (default: 20, max: 100)
searchstringnoПоиск по названию и адресу
statusstringnoФильтр: draft / published / suspended
legal_entity_iduuidnoФильтр по юридическому лицу
citystringnoФильтр по городу
sortstringnoСортировка: name_asc (default) / name_desc / created_at_desc

Маппинг статусов

Фильтр status маппится на комбинацию полей status + is_published:

  • draftstatus = active AND is_published = false
  • publishedstatus = active AND is_published = true
  • suspendedstatus = suspended AND is_published = false

Response 200

{
  "data": [
    {
      "id": "uuid",
      "name": "string",
      "address": "string",
      "city": "string | null",
      "status": "active | suspended",
      "is_published": "boolean",
      "legal_entity_id": "uuid",
      "legal_entity_name": "string",
      "price_list_id": "uuid | null",
      "phone": "string | null",
      "created_at": "datetime"
    }
  ],
  "meta": {
    "page": "integer",
    "per_page": "integer",
    "total": "integer"
  }
}

Ролевой доступ

  • Franchise: все ТТ франшизы (WHERE franchise_id = :jwt_franchise_id)
  • Franchisee: только ТТ своего ЮЛ (WHERE legal_entity_id IN (ЮЛ франчайзи))
  • Manager: только своя ТТ (WHERE id IN :jwt_store_ids)
  • Cashier: 403 FORBIDDEN

Errors

CodeHTTPКогда
UNAUTHORIZED401Невалидный JWT
FORBIDDEN403Роль Cashier

GET /stores/{id}

Получить детали торговой точки, включая расписание.

ПараметрЗначение
AuthBearer JWT (Franchise — любая; Franchisee — только свои ТТ; Manager — только своя ТТ; Cashier — 403)

Path Parameters

ParamTypeRequiredDescription
iduuidyesID торговой точки

Response 200

{
  "data": {
    "id": "uuid",
    "franchise_id": "uuid",
    "legal_entity_id": "uuid",
    "legal_entity_name": "string",
    "name": "string",
    "address": "string",
    "latitude": "decimal",
    "longitude": "decimal",
    "city": "string | null",
    "phone": "string | null",
    "email": "string | null",
    "status": "active | suspended",
    "is_published": "boolean",
    "price_list_id": "uuid | null",
    "timezone": "string (IANA timezone, e.g. 'Europe/Moscow')",
    "schedule": [
      {
        "day_of_week": "integer (0=Monday..6=Sunday)",
        "open_time": "string (HH:MM) | null",
        "close_time": "string (HH:MM) | null",
        "is_closed": "boolean"
      }
    ],
    "created_at": "datetime",
    "updated_at": "datetime"
  }
}

Errors

CodeHTTPКогда
UNAUTHORIZED401Невалидный JWT
FORBIDDEN403Cashier или доступ к чужой ТТ
STORE_NOT_FOUND404ТТ не найдена или удалена

POST /stores

Создать торговую точку.

ПараметрЗначение
AuthBearer JWT (только Franchise)
Content-Typeapplication/json

Request Body

{
  "name": "string, required — название ТТ (max length 255)",
  "address": "string, required — адрес",
  "latitude": "decimal, required — широта (-90..90)",
  "longitude": "decimal, required — долгота (-180..180)",
  "legal_entity_id": "uuid, required — привязка к ЮЛ",
  "city": "string, optional — город",
  "phone": "string, optional — формат +7XXXXXXXXXX",
  "email": "string, optional — email",
  "timezone": "string, optional — IANA timezone (default 'Europe/Moscow')",
  "schedule": [
    {
      "day_of_week": "integer, required — 0=Monday..6=Sunday",
      "open_time": "string, optional — HH:MM (обязательно если is_closed=false)",
      "close_time": "string, optional — HH:MM (обязательно если is_closed=false)",
      "is_closed": "boolean, optional — default false"
    }
  ]
}

(Доработано в BUG-008)

(Уточнено в BUG-026 — лимит длины name)

ТТ создаётся со статусом active и is_published = false (draft).

Response 201

{
  "data": {
    "id": "uuid",
    "franchise_id": "uuid",
    "legal_entity_id": "uuid",
    "name": "string",
    "address": "string",
    "latitude": "decimal",
    "longitude": "decimal",
    "city": "string | null",
    "phone": "string | null",
    "email": "string | null",
    "status": "active",
    "is_published": false,
    "schedule": [],
    "created_at": "datetime",
    "updated_at": "datetime"
  }
}

Errors

CodeHTTPКогда
UNAUTHORIZED401Невалидный JWT
FORBIDDEN403Роль не Franchise
VALIDATION_ERROR400Невалидные данные (координаты, email, phone)
NAME_DUPLICATE409ТТ с таким названием уже существует в рамках данного ЮЛ
LEGAL_ENTITY_NOT_FOUND404ЮЛ не найдено

PATCH /stores/{id}

Обновить торговую точку.

ПараметрЗначение
AuthBearer JWT (Franchise — все поля; Franchisee/Manager — ограниченный набор)
Content-Typeapplication/json

Path Parameters

ParamTypeRequiredDescription
iduuidyesID торговой точки

Request Body

Partial update — отправляются только изменяемые поля.

{
  "name": "string, optional (max length 255)",
  "address": "string, optional",
  "latitude": "decimal, optional",
  "longitude": "decimal, optional",
  "city": "string, optional",
  "phone": "string, optional",
  "email": "string, optional",
  "timezone": "string, optional — IANA timezone",
  "schedule": [
    {
      "day_of_week": "integer, required — 0..6",
      "open_time": "string, optional — HH:MM",
      "close_time": "string, optional — HH:MM",
      "is_closed": "boolean, optional"
    }
  ]
}

(Уточнено в BUG-026 — лимит длины name)

legal_entity_id нельзя менять после создания

Поле legal_entity_id не принимается в PATCH. Если передано — 422 LEGAL_ENTITY_IMMUTABLE.

Franchisee и Manager могут менять только: name, address, latitude, longitude, city, phone, email, schedule

Franchise может менять все поля (кроме legal_entity_id).

Response 200

Полный объект ТТ (аналогично GET /stores/{id}).

Errors

CodeHTTPКогда
UNAUTHORIZED401Невалидный JWT
FORBIDDEN403Нет прав на редактирование или доступ к чужой ТТ
STORE_NOT_FOUND404ТТ не найдена или удалена
VALIDATION_ERROR400Невалидные данные
NAME_DUPLICATE409ТТ с таким названием уже существует в рамках данного ЮЛ
LEGAL_ENTITY_IMMUTABLE422Попытка изменить legal_entity_id

DELETE /stores/{id}

Удалить торговую точку (soft delete).

ПараметрЗначение
AuthBearer JWT (только Franchise)

Path Parameters

ParamTypeRequiredDescription
iduuidyesID торговой точки

Перед удалением проверяется

  1. ТТ должна быть снята с публикации (is_published = false). Иначе — 422 STORE_IS_PUBLISHED.
  2. Проверяется наличие привязанных сотрудников (через User Service internal API или локально). Если есть — удаление выполняется, но возвращается предупреждение.

Response 204

Нет тела.

Error Response 422 (ТТ опубликована)

{
  "error": {
    "code": "STORE_IS_PUBLISHED",
    "message": "Нельзя удалить опубликованную ТТ. Сначала снимите с публикации."
  }
}

Errors

CodeHTTPКогда
UNAUTHORIZED401Невалидный JWT
FORBIDDEN403Роль не Franchise
STORE_NOT_FOUND404ТТ не найдена или уже удалена
STORE_IS_PUBLISHED422ТТ опубликована — сначала снять с публикации

POST /stores/{id}/publish

Опубликовать торговую точку (сделать видимой для клиентов).

ПараметрЗначение
AuthBearer JWT (только Franchise)

Path Parameters

ParamTypeRequiredDescription
iduuidyesID торговой точки

Требования для публикации

  • status должен быть active
  • is_published должен быть false

Response 200

{
  "data": {
    "id": "uuid",
    "is_published": true
  }
}

Errors

CodeHTTPКогда
UNAUTHORIZED401Невалидный JWT
FORBIDDEN403Роль не Franchise
STORE_NOT_FOUND404ТТ не найдена
STORE_SUSPENDED422ТТ приостановлена (status=suspended), публикация невозможна
ALREADY_PUBLISHED422ТТ уже опубликована

POST /stores/{id}/unpublish

Снять торговую точку с публикации.

ПараметрЗначение
AuthBearer JWT (только Franchise)

Path Parameters

ParamTypeRequiredDescription
iduuidyesID торговой точки

Response 200

{
  "data": {
    "id": "uuid",
    "is_published": false
  }
}

Errors

CodeHTTPКогда
UNAUTHORIZED401Невалидный JWT
FORBIDDEN403Роль не Franchise
STORE_NOT_FOUND404ТТ не найдена
ALREADY_UNPUBLISHED422ТТ уже снята с публикации

PATCH /stores/{id}/price-list

(BR 1.10)

Назначить прейскурант на торговую точку.

ПараметрЗначение
AuthBearer JWT (только Franchise)
Content-Typeapplication/json

Path Parameters

ParamTypeRequiredDescription
iduuidyesID торговой точки

Request Body

{
  "price_list_id": "uuid, required"
}

Response 200

{
  "data": {
    "store_id": "uuid",
    "price_list_id": "uuid"
  }
}

Errors

CodeHTTPКогда
UNAUTHORIZED401Невалидный JWT
FORBIDDEN403Роль не Franchise
STORE_NOT_FOUND404ТТ не найдена или удалена
PRICE_LIST_NOT_FOUND404Прейскурант не найден (cross-service check)
PRICE_LIST_INACTIVE422Прейскурант inactive

DELETE /stores/{id}/price-list

(BR 1.10)

Снять назначение прейскуранта с ТТ (будет использоваться дефолтный).

ПараметрЗначение
AuthBearer JWT (только Franchise)

Path Parameters

ParamTypeRequiredDescription
iduuidyesID торговой точки

Response 204

Нет тела.

Errors

CodeHTTPКогда
UNAUTHORIZED401Невалидный JWT
FORBIDDEN403Роль не Franchise
STORE_NOT_FOUND404ТТ не найдена или удалена

GET /stores/{storeId}/terminals

Список POS-терминалов, привязанных к ТТ.

ПараметрЗначение
AuthBearer JWT (Franchise / Franchisee своих ТТ / Manager своей ТТ)

Path Parameters

ParamTypeRequiredDescription
storeIduuidyesID торговой точки

Response 200

{
  "data": [
    {
      "id": "uuid",
      "store_id": "uuid",
      "label": "string",
      "fs_number": "string",
      "rn_kkt": "string | null",
      "status": "active | inactive",
      "last_seen_at": "datetime | null",
      "created_at": "datetime"
    }
  ]
}

Errors

CodeHTTPКогда
UNAUTHORIZED401Невалидный JWT
FORBIDDEN403Нет доступа к ТТ
STORE_NOT_FOUND404ТТ не найдена

POST /stores/{storeId}/terminals

Создать POS-терминал на ТТ.

ПараметрЗначение
AuthBearer JWT (только Franchise)
Content-Typeapplication/json

Path Parameters

ParamTypeRequiredDescription
storeIduuidyesID торговой точки

Request Body

{
  "label": "string, required — человекочитаемое название (например «Касса 1»)",
  "fs_number": "string, required — заводской номер ФН (UNIQUE глобально)",
  "rn_kkt": "string, optional — регистрационный номер ККТ"
}

Response 201

{
  "data": {
    "id": "uuid",
    "store_id": "uuid",
    "label": "string",
    "fs_number": "string",
    "rn_kkt": "string | null",
    "status": "active",
    "last_seen_at": null,
    "created_at": "datetime"
  }
}

Errors

CodeHTTPКогда
UNAUTHORIZED401Невалидный JWT
FORBIDDEN403Роль не Franchise
STORE_NOT_FOUND404ТТ не найдена
FS_NUMBER_DUPLICATE409ФН уже зарегистрирован на другом терминале
VALIDATION_ERROR400Пустой label / невалидный fs_number

PATCH /stores/{storeId}/terminals/{terminalId}

Обновить POS-терминал (label, rn_kkt, status).

ПараметрЗначение
AuthBearer JWT (только Franchise)
Content-Typeapplication/json

Request Body

{
  "label": "string, optional",
  "rn_kkt": "string, optional",
  "status": "string, optional — active | inactive"
}

fs_number неизменяем после создания.

Response 200

Полный объект терминала.

Errors

CodeHTTPКогда
UNAUTHORIZED401
FORBIDDEN403Роль не Franchise
TERMINAL_NOT_FOUND404
FS_NUMBER_IMMUTABLE422Попытка изменить fs_number

DELETE /stores/{storeId}/terminals/{terminalId}

Удалить POS-терминал (soft delete).

ПараметрЗначение
AuthBearer JWT (только Franchise)

Response 204

Нет тела.

Errors

CodeHTTPКогда
UNAUTHORIZED401
FORBIDDEN403
TERMINAL_NOT_FOUND404

GET /stores/{storeId}/tables

Список столов зала ТТ.

ПараметрЗначение
AuthBearer JWT (Franchise / Franchisee своих ТТ / Manager своей ТТ)

Response 200

{
  "data": [
    {
      "id": "uuid",
      "store_id": "uuid",
      "number": "integer",
      "label": "string | null",
      "capacity": "integer",
      "position_x": "integer",
      "position_y": "integer",
      "status": "free | occupied | reserved",
      "current_order_id": "uuid | null",
      "reserved_note": "string | null",
      "reserved_until": "datetime | null"
    }
  ]
}

POST /stores/{storeId}/tables

Создать стол.

ПараметрЗначение
AuthBearer JWT (Franchise / Manager своей ТТ)

Request Body

{
  "number": "integer, required — номер стола (уникальный в рамках ТТ)",
  "label": "string, optional — доп. метка",
  "capacity": "integer, optional — default 4",
  "position_x": "integer, optional — default 0",
  "position_y": "integer, optional — default 0"
}

Response 201

Полный объект стола (status=free).

Errors

CodeHTTPКогда
TABLE_NUMBER_DUPLICATE409Номер уже занят в рамках ТТ
VALIDATION_ERROR400Невалидный capacity / number

PATCH /tables/{id}

Обновить стол (метку, вместимость, позицию на canvas).

ПараметрЗначение
AuthBearer JWT (Franchise / Manager)

Request Body

{
  "label": "string, optional",
  "capacity": "integer, optional",
  "position_x": "integer, optional",
  "position_y": "integer, optional"
}

number и status меняются отдельными endpoints (/occupy, /release, /reserve, /cancel-reservation).

Response 200

Полный объект стола.


DELETE /tables/{id}

Удалить стол (soft delete).

ПараметрЗначение
AuthBearer JWT (только Franchise)

Response 204

Нет тела.

Errors

CodeHTTPКогда
TABLE_OCCUPIED422Нельзя удалить занятый стол

POST /tables/{id}/occupy

Пометить стол занятым (привязать активный заказ).

ПараметрЗначение
AuthBearer JWT (Manager / Cashier своей ТТ)

Request Body

{
  "order_id": "uuid, required"
}

Response 200

{
  "data": { "id": "uuid", "status": "occupied", "current_order_id": "uuid" }
}

Errors

CodeHTTPКогда
TABLE_ALREADY_OCCUPIED422Стол уже занят другим заказом

POST /tables/{id}/release

Освободить стол (снять активный заказ).

ПараметрЗначение
AuthBearer JWT (Manager / Cashier своей ТТ)

Response 200

{ "data": { "id": "uuid", "status": "free", "current_order_id": null } }

POST /tables/{id}/reserve

Забронировать стол.

ПараметрЗначение
AuthBearer JWT (Manager / Cashier своей ТТ)
Content-Typeapplication/json

Request Body

{
  "note": "string, optional — кто забронировал",
  "until": "datetime, optional — время до которого бронь"
}

Response 200

{
  "data": {
    "id": "uuid",
    "status": "reserved",
    "reserved_note": "string | null",
    "reserved_until": "datetime | null"
  }
}

POST /tables/{id}/cancel-reservation

Снять бронь со стола.

ПараметрЗначение
AuthBearer JWT (Manager / Cashier своей ТТ)

Response 200

{ "data": { "id": "uuid", "status": "free" } }

PATCH /tables/{id}/waiter

(BR 3.2)

Назначить или снять текущего официанта на стол. Используется для привязки чаевых из Нетмонета, когда webhook не содержит order_number.

ПараметрЗначение
AuthBearer JWT (Franchise / Manager своей ТТ)
Content-Typeapplication/json

Request Body

{
  "waiter_id": "uuid | null — employee_id официанта; null для снятия назначения"
}

Response 200

Полный объект стола с обновлённым current_waiter_id.

Errors

CodeHTTPКогда
UNAUTHORIZED401
FORBIDDEN403
TABLE_NOT_FOUND404

POST /internal/stores/count-by-legal-entities

Internal endpoint — только для service-to-service вызовов

Получить количество ТТ для списка юридических лиц (batch).

ПараметрЗначение
AuthService token (X-Service-Token header)
Content-Typeapplication/json

Request Body

{
  "legal_entity_ids": ["uuid", "uuid", "..."]
}

Response 200

{
  "data": {
    "uuid-1": 3,
    "uuid-2": 0,
    "uuid-3": 1
  }
}

Возвращает 0 для ЮЛ без привязанных ТТ. Считаются все ТТ (любой статус, включая закрытые).


Internal endpoint — только для service-to-service вызовов

Получить список ТТ по юридическому лицу.

ПараметрЗначение
AuthService token (X-Service-Token header)

Query Parameters

ParamTypeRequiredDescription
legal_entity_iduuidyesID юридического лица

Response 200

{
  "data": [
    {
      "id": "uuid",
      "name": "string",
      "status": "string",
      "is_published": "boolean"
    }
  ]
}

Errors

CodeHTTPКогда
VALIDATION_ERROR400Не передан legal_entity_id

GET /internal/stores/all

Internal endpoint — только для service-to-service вызовов

Список всех живых ТТ всех франшиз (без deleted_at). Используется Warehouse Service для backfill складов по BR 1.14 после деплоя.

ПараметрЗначение
AuthX-Service-Token header
Path/internal/stores/all (без /api/v1 префикса)

Response 200

{
  "data": [
    {
      "id": "uuid",
      "franchise_id": "uuid",
      "name": "string"
    }
  ]
}

Минимальный набор полей

Возвращается только то, что нужно bootstrap-сценариям других сервисов: id, franchise_id, name. Без адресов, графиков, статусов публикации.

Errors

CodeHTTPКогда
UNAUTHORIZED401Невалидный/отсутствующий X-Service-Token

POST /internal/stores/unpublish-by-legal-entity

Internal endpoint — только для service-to-service вызовов

Снять с публикации все ТТ юридического лица (при приостановке ЮЛ).

ПараметрЗначение
AuthService token (X-Service-Token header)
Content-Typeapplication/json

Request Body

{
  "legal_entity_id": "uuid, required"
}

Response 200

{
  "data": {
    "unpublished_count": "integer"
  }
}

Возвращает 0 если нет опубликованных ТТ. Операция идемпотентна — повторный вызов вернёт 0.

Errors

CodeHTTPКогда
VALIDATION_ERROR400Не передан legal_entity_id

POST /internal/terminals/bind-by-fs

Internal endpoint — только для service-to-service

Поиск POS-терминала по заводскому номеру ФН — используется при первом запуске терминала для получения store_id.

ПараметрЗначение
AuthService token (X-Service-Token header)
Content-Typeapplication/json

Request Body

{ "fs_number": "string, required" }

Response 200

{
  "data": {
    "terminal_id": "uuid",
    "store_id": "uuid",
    "franchise_id": "uuid",
    "label": "string"
  }
}

Errors

CodeHTTPКогда
TERMINAL_NOT_FOUND404ФН не зарегистрирован

GET /internal/tables/by-store/{storeId}

Internal endpoint

Список столов ТТ (для Order Service при выборе стола на кассе).

ПараметрЗначение
AuthService token

Response 200

Массив объектов table (см. GET /stores/{storeId}/tables).


GET /internal/tables/{id}

Internal endpoint

Получить стол по ID.

ПараметрЗначение
AuthService token

Response 200

Полный объект стола.


POST /internal/tables/by-store/{storeId}

Phase 1 desktop-pos — вызывается POS BFF когда менеджер создаёт стол через manager edit-mode

Создать новый стол на ТТ. Тело — CreateZalTableRequest (number, label?, capacity?, position_x?, position_y?).

ПараметрЗначение
AuthService token

Response 201

Полный объект стола.

Errors

  • 409 TABLE_NUMBER_DUPLICATE — стол с таким number уже есть на ТТ
  • 404 STORE_NOT_FOUND

PATCH /internal/tables/{id}

Phase 1 desktop-pos — drag-edit позиции / редактирование capacity/number

Обновить произвольный набор полей: number, label, capacity, position_x, position_y. Поля, которых нет в теле, не меняются.

ПараметрЗначение
AuthService token

Response 200

Полный объект стола.

Errors

  • 409 TABLE_NUMBER_DUPLICATE — стол с таким number уже есть
  • 404 TABLE_NOT_FOUND

DELETE /internal/tables/{id}

Soft delete (deleted_at ставится, столы фильтруются IS NULL).

ПараметрЗначение
AuthService token

Response 204

Errors

  • 409 TABLE_OCCUPIED — нельзя удалить стол с активным заказом, сначала закрой заказ
  • 404 TABLE_NOT_FOUND

POST /internal/tables/{id}/occupy / release / reserve / cancel-reservation

Internal endpoints — вызываются Order Service по событиям заказа + POS BFF (release/reserve/cancel)

Аналогичны публичным /tables/{id}/..., но без JWT-проверки. Используются для синхронизации статуса стола с жизненным циклом заказа и для action sheet кассира на POS.


PATCH /internal/tables/{id}/waiter

BR 3.2 — назначить / снять текущего официанта на стол (для привязки чаевых Нетмонета). Phase 1 desktop-pos готовит endpoint, использование появится в Phase 2/5.

ПараметрЗначение
AuthService token

Request Body

{ "waiter_id": "uuid | null" }

null снимает назначение.

Response 200

Полный объект стола.

Errors

  • 404 TABLE_NOT_FOUND

GET /admin/stores/{storeId}/marketing-slides

(BR 6.1)

Список маркетинговых слайдов ТТ для управления в админке.

ПараметрЗначение
AuthBearer JWT (permission marketing.read + scope ТТ)

Path Parameters

ParamTypeRequiredDescription
storeIduuidyesID торговой точки

Query Parameters

ParamTypeRequiredDescription
activebooleannoФильтр по активности (true — только активные, false — только неактивные, отсутствует — все)

Response 200

{
  "data": [
    {
      "id": "uuid",
      "store_id": "uuid",
      "image_url": "string — public URL картинки в S3",
      "title": "string",
      "order": "integer",
      "active": "boolean",
      "source": "manual | ai_photo_studio",
      "source_ref": "object | null — для ai_photo_studio: { job_id, preset_id }",
      "image_size_bytes": "integer",
      "image_mime": "image/jpeg | image/png | image/webp",
      "created_at": "datetime",
      "updated_at": "datetime"
    }
  ]
}

Сортировка — по order ASC.

Errors

CodeHTTPКогда
UNAUTHORIZED401Невалидный JWT
FORBIDDEN403Нет permission marketing.read или scope не включает ТТ
STORE_NOT_FOUND404ТТ не найдена

POST /admin/stores/{storeId}/marketing-slides

(BR 6.1)

Создать новый слайд (multipart с картинкой).

ПараметрЗначение
AuthBearer JWT (permission marketing.write + scope ТТ)
Content-Typemultipart/form-data

Path Parameters

ParamTypeRequiredDescription
storeIduuidyesID торговой точки

Multipart Fields

FieldTypeRequiredDescription
filebinaryyesjpg/png/webp, ≤ 10 МБ, разрешение ≥ 1280×720
titlestringyesДо 200 символов
activebooleannoDefault true

Источник manual

Слайды через этот эндпоинт создаются с source = manual. Для ai_photo_studio — отдельный flow в BR 6.2 (внутренний endpoint, ещё не определён).

Response 201

Полный объект слайда (как в GET).

order присваивается автоматически — MAX(order) + 1 среди слайдов ТТ.

Errors

CodeHTTPКогда
UNAUTHORIZED401
FORBIDDEN403Нет permission или scope
STORE_NOT_FOUND404ТТ не найдена
VALIDATION_ERROR400Пустой title, недопустимый MIME, размер > 10 МБ, разрешение < 1280×720
MARKETING_SLIDES_LIMIT_REACHED422На ТТ уже 20 активных слайдов
S3_UPLOAD_FAILED500Сбой загрузки в S3

PUT /admin/stores/{storeId}/marketing-slides/{slideId}

(BR 6.1)

Обновить слайд (заголовок, активность, опционально картинка).

ПараметрЗначение
AuthBearer JWT (permission marketing.write + scope ТТ)
Content-Typemultipart/form-data (если меняется картинка) или application/json (только title/active)

Path Parameters

ParamTypeRequiredDescription
storeIduuidyes
slideIduuidyes

Multipart Fields / JSON Body

FieldTypeRequiredDescription
filebinarynoНовая картинка. Если передана — старая удаляется (отложенно).
titlestringno
activebooleanno

Нельзя менять

source, source_ref, order, store_id — иммутабельны. Если переданы — 422.

Response 200

Полный объект слайда.

Errors

CodeHTTPКогда
UNAUTHORIZED401
FORBIDDEN403
SLIDE_NOT_FOUND404Слайд не найден или удалён
VALIDATION_ERROR400
IMMUTABLE_FIELD422Попытка изменить source/order/store_id
MARKETING_SLIDES_LIMIT_REACHED422Активация 21-го активного слайда

PATCH /admin/stores/{storeId}/marketing-slides/reorder

(BR 6.1)

Массовое переназначение порядка (drag-and-drop в админке).

ПараметрЗначение
AuthBearer JWT (permission marketing.write + scope ТТ)
Content-Typeapplication/json

Path Parameters

ParamTypeRequiredDescription
storeIduuidyes

Request Body

{
  "order": [
    { "id": "uuid", "order": 0 },
    { "id": "uuid", "order": 1 },
    { "id": "uuid", "order": 2 }
  ]
}

Транзакционный апдейт

Бэк перезаписывает order всем слайдам ТТ в одной транзакции. Если в массиве пропущен какой-то слайд ТТ — он попадает в конец (order = max + 1).

Response 200

Полный список слайдов ТТ с новыми order (как GET).

Errors

CodeHTTPКогда
UNAUTHORIZED401
FORBIDDEN403
STORE_NOT_FOUND404
SLIDE_NOT_FOUND404Хотя бы один ID не принадлежит ТТ
VALIDATION_ERROR400Дубликаты order в массиве, отрицательные значения

DELETE /admin/stores/{storeId}/marketing-slides/{slideId}

(BR 6.1)

Удалить слайд (soft delete). Картинка в S3 — удаляется отложенно (cron).

ПараметрЗначение
AuthBearer JWT (permission marketing.write + scope ТТ)

Path Parameters

ParamTypeRequiredDescription
storeIduuidyes
slideIduuidyes

Response 204

Нет тела.

Errors

CodeHTTPКогда
UNAUTHORIZED401
FORBIDDEN403
SLIDE_NOT_FOUND404Слайд не найден или уже удалён

GET /internal/stores/{storeId}/marketing-slides/active

Internal endpoint — только для service-to-service (POS BFF)

Список активных слайдов ТТ для отдачи на POS Desktop в standby-режим. Возвращает уже отсортированный по order массив с публичными image URL.

ПараметрЗначение
AuthService token (X-Service-Token header)

Path Parameters

ParamTypeRequiredDescription
storeIduuidyes

Response 200

{
  "data": {
    "slides": [
      {
        "id": "uuid",
        "image_url": "string — public URL",
        "order": "integer"
      }
    ],
    "standby_idle_minutes": "integer — конфиг ТТ (default 5)",
    "standby_transition_seconds": "integer — конфиг ТТ (default 9)"
  }
}

Минимальный набор полей

title, source, created_at не отдаются — на POS они не нужны. Конфиг standby склеивается из полей stores.standby_idle_minutes / standby_transition_seconds для удобства POS BFF.

Errors

CodeHTTPКогда
UNAUTHORIZED401Невалидный/отсутствующий X-Service-Token
STORE_NOT_FOUND404

Общий формат ошибок

{
  "error": {
    "code": "string — машиночитаемый код (UPPER_SNAKE_CASE)",
    "message": "string — человекочитаемое описание",
    "details": [
      {
        "field": "string — путь к полю",
        "message": "string — описание ошибки"
      }
    ]
  }
}