Store Service — API Contract
Этот документ является единственным источником правды для API Store Service.
Бэкенд реализует контракт. Фронтенд потребляет контракт. Отклонения запрещены.
Содержание
Public API (Bearer JWT)
Торговые точки
- GET /stores
- GET /stores/{id}
- POST /stores
- PATCH /stores/{id}
- DELETE /stores/{id}
- POST /stores/{id}/publish
- POST /stores/{id}/unpublish
- PATCH /stores/{id}/price-list
- DELETE /stores/{id}/price-list
POS-терминалы (BR 1.5)
- GET /stores/{storeId}/terminals
- POST /stores/{storeId}/terminals
- PATCH /stores/{storeId}/terminals/{terminalId}
- DELETE /stores/{storeId}/terminals/{terminalId}
Столы зала (dine-in)
- GET /stores/{storeId}/tables
- POST /stores/{storeId}/tables
- PATCH /tables/{id}
- DELETE /tables/{id}
- POST /tables/{id}/occupy
- POST /tables/{id}/release
- POST /tables/{id}/reserve
- POST /tables/{id}/cancel-reservation
- PATCH /tables/{id}/waiter (BR 3.2)
Маркетинговая информация (BR 6.1)
- GET /admin/stores/{storeId}/marketing-slides
- POST /admin/stores/{storeId}/marketing-slides
- PUT /admin/stores/{storeId}/marketing-slides/{slideId}
- PATCH /admin/stores/{storeId}/marketing-slides/reorder
- DELETE /admin/stores/{storeId}/marketing-slides/{slideId}
Internal API (Service Token)
- POST /internal/stores/count-by-legal-entities
- GET /internal/stores (by legal_entity_id)
- GET /internal/stores/all
- POST /internal/stores/unpublish-by-legal-entity
- POST /internal/terminals/bind-by-fs
- GET /internal/tables/by-store/{storeId}
- POST /internal/tables/by-store/{storeId} (create — Phase 1 desktop-pos)
- GET /internal/tables/{id}
- PATCH /internal/tables/{id} (update position/capacity/number — Phase 1)
- DELETE /internal/tables/{id} (soft delete — Phase 1)
- POST /internal/tables/{id}/occupy
- POST /internal/tables/{id}/release
- POST /internal/tables/{id}/reserve
- POST /internal/tables/{id}/cancel-reservation
- PATCH /internal/tables/{id}/waiter (BR 3.2 — Phase 1)
- GET /internal/stores/{storeId}/marketing-slides/active (BR 6.1 — POS BFF)
GET /stores
Получить список торговых точек с пагинацией, поиском и фильтрами.
| Параметр | Значение |
|---|---|
| Auth | Bearer JWT (Franchise — все; Franchisee — только ТТ своего ЮЛ; Manager — только своя ТТ; Cashier — 403) |
Query Parameters
| Param | Type | Required | Description |
|---|---|---|---|
page | integer | no | Номер страницы (default: 1) |
per_page | integer | no | Записей на страницу (default: 20, max: 100) |
search | string | no | Поиск по названию и адресу |
status | string | no | Фильтр: draft / published / suspended |
legal_entity_id | uuid | no | Фильтр по юридическому лицу |
city | string | no | Фильтр по городу |
sort | string | no | Сортировка: name_asc (default) / name_desc / created_at_desc |
Маппинг статусов
Фильтр
statusмаппится на комбинацию полейstatus+is_published:
draft→status = active AND is_published = falsepublished→status = active AND is_published = truesuspended→status = 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
| Code | HTTP | Когда |
|---|---|---|
UNAUTHORIZED | 401 | Невалидный JWT |
FORBIDDEN | 403 | Роль Cashier |
GET /stores/{id}
Получить детали торговой точки, включая расписание.
| Параметр | Значение |
|---|---|
| Auth | Bearer JWT (Franchise — любая; Franchisee — только свои ТТ; Manager — только своя ТТ; Cashier — 403) |
Path Parameters
| Param | Type | Required | Description |
|---|---|---|---|
id | uuid | yes | ID торговой точки |
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
| Code | HTTP | Когда |
|---|---|---|
UNAUTHORIZED | 401 | Невалидный JWT |
FORBIDDEN | 403 | Cashier или доступ к чужой ТТ |
STORE_NOT_FOUND | 404 | ТТ не найдена или удалена |
POST /stores
Создать торговую точку.
| Параметр | Значение |
|---|---|
| Auth | Bearer JWT (только Franchise) |
| Content-Type | application/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
| Code | HTTP | Когда |
|---|---|---|
UNAUTHORIZED | 401 | Невалидный JWT |
FORBIDDEN | 403 | Роль не Franchise |
VALIDATION_ERROR | 400 | Невалидные данные (координаты, email, phone) |
NAME_DUPLICATE | 409 | ТТ с таким названием уже существует в рамках данного ЮЛ |
LEGAL_ENTITY_NOT_FOUND | 404 | ЮЛ не найдено |
PATCH /stores/{id}
Обновить торговую точку.
| Параметр | Значение |
|---|---|
| Auth | Bearer JWT (Franchise — все поля; Franchisee/Manager — ограниченный набор) |
| Content-Type | application/json |
Path Parameters
| Param | Type | Required | Description |
|---|---|---|---|
id | uuid | yes | ID торговой точки |
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,scheduleFranchise может менять все поля (кроме
legal_entity_id).
Response 200
Полный объект ТТ (аналогично GET /stores/{id}).
Errors
| Code | HTTP | Когда |
|---|---|---|
UNAUTHORIZED | 401 | Невалидный JWT |
FORBIDDEN | 403 | Нет прав на редактирование или доступ к чужой ТТ |
STORE_NOT_FOUND | 404 | ТТ не найдена или удалена |
VALIDATION_ERROR | 400 | Невалидные данные |
NAME_DUPLICATE | 409 | ТТ с таким названием уже существует в рамках данного ЮЛ |
LEGAL_ENTITY_IMMUTABLE | 422 | Попытка изменить legal_entity_id |
DELETE /stores/{id}
Удалить торговую точку (soft delete).
| Параметр | Значение |
|---|---|
| Auth | Bearer JWT (только Franchise) |
Path Parameters
| Param | Type | Required | Description |
|---|---|---|---|
id | uuid | yes | ID торговой точки |
Перед удалением проверяется
- ТТ должна быть снята с публикации (
is_published = false). Иначе —422 STORE_IS_PUBLISHED.- Проверяется наличие привязанных сотрудников (через User Service internal API или локально). Если есть — удаление выполняется, но возвращается предупреждение.
Response 204
Нет тела.
Error Response 422 (ТТ опубликована)
{
"error": {
"code": "STORE_IS_PUBLISHED",
"message": "Нельзя удалить опубликованную ТТ. Сначала снимите с публикации."
}
}Errors
| Code | HTTP | Когда |
|---|---|---|
UNAUTHORIZED | 401 | Невалидный JWT |
FORBIDDEN | 403 | Роль не Franchise |
STORE_NOT_FOUND | 404 | ТТ не найдена или уже удалена |
STORE_IS_PUBLISHED | 422 | ТТ опубликована — сначала снять с публикации |
POST /stores/{id}/publish
Опубликовать торговую точку (сделать видимой для клиентов).
| Параметр | Значение |
|---|---|
| Auth | Bearer JWT (только Franchise) |
Path Parameters
| Param | Type | Required | Description |
|---|---|---|---|
id | uuid | yes | ID торговой точки |
Требования для публикации
statusдолжен бытьactiveis_publishedдолжен бытьfalse
Response 200
{
"data": {
"id": "uuid",
"is_published": true
}
}Errors
| Code | HTTP | Когда |
|---|---|---|
UNAUTHORIZED | 401 | Невалидный JWT |
FORBIDDEN | 403 | Роль не Franchise |
STORE_NOT_FOUND | 404 | ТТ не найдена |
STORE_SUSPENDED | 422 | ТТ приостановлена (status=suspended), публикация невозможна |
ALREADY_PUBLISHED | 422 | ТТ уже опубликована |
POST /stores/{id}/unpublish
Снять торговую точку с публикации.
| Параметр | Значение |
|---|---|
| Auth | Bearer JWT (только Franchise) |
Path Parameters
| Param | Type | Required | Description |
|---|---|---|---|
id | uuid | yes | ID торговой точки |
Response 200
{
"data": {
"id": "uuid",
"is_published": false
}
}Errors
| Code | HTTP | Когда |
|---|---|---|
UNAUTHORIZED | 401 | Невалидный JWT |
FORBIDDEN | 403 | Роль не Franchise |
STORE_NOT_FOUND | 404 | ТТ не найдена |
ALREADY_UNPUBLISHED | 422 | ТТ уже снята с публикации |
PATCH /stores/{id}/price-list
(BR 1.10)
Назначить прейскурант на торговую точку.
| Параметр | Значение |
|---|---|
| Auth | Bearer JWT (только Franchise) |
| Content-Type | application/json |
Path Parameters
| Param | Type | Required | Description |
|---|---|---|---|
id | uuid | yes | ID торговой точки |
Request Body
{
"price_list_id": "uuid, required"
}Response 200
{
"data": {
"store_id": "uuid",
"price_list_id": "uuid"
}
}Errors
| Code | HTTP | Когда |
|---|---|---|
UNAUTHORIZED | 401 | Невалидный JWT |
FORBIDDEN | 403 | Роль не Franchise |
STORE_NOT_FOUND | 404 | ТТ не найдена или удалена |
PRICE_LIST_NOT_FOUND | 404 | Прейскурант не найден (cross-service check) |
PRICE_LIST_INACTIVE | 422 | Прейскурант inactive |
DELETE /stores/{id}/price-list
(BR 1.10)
Снять назначение прейскуранта с ТТ (будет использоваться дефолтный).
| Параметр | Значение |
|---|---|
| Auth | Bearer JWT (только Franchise) |
Path Parameters
| Param | Type | Required | Description |
|---|---|---|---|
id | uuid | yes | ID торговой точки |
Response 204
Нет тела.
Errors
| Code | HTTP | Когда |
|---|---|---|
UNAUTHORIZED | 401 | Невалидный JWT |
FORBIDDEN | 403 | Роль не Franchise |
STORE_NOT_FOUND | 404 | ТТ не найдена или удалена |
GET /stores/{storeId}/terminals
Список POS-терминалов, привязанных к ТТ.
| Параметр | Значение |
|---|---|
| Auth | Bearer JWT (Franchise / Franchisee своих ТТ / Manager своей ТТ) |
Path Parameters
| Param | Type | Required | Description |
|---|---|---|---|
storeId | uuid | yes | ID торговой точки |
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
| Code | HTTP | Когда |
|---|---|---|
UNAUTHORIZED | 401 | Невалидный JWT |
FORBIDDEN | 403 | Нет доступа к ТТ |
STORE_NOT_FOUND | 404 | ТТ не найдена |
POST /stores/{storeId}/terminals
Создать POS-терминал на ТТ.
| Параметр | Значение |
|---|---|
| Auth | Bearer JWT (только Franchise) |
| Content-Type | application/json |
Path Parameters
| Param | Type | Required | Description |
|---|---|---|---|
storeId | uuid | yes | ID торговой точки |
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
| Code | HTTP | Когда |
|---|---|---|
UNAUTHORIZED | 401 | Невалидный JWT |
FORBIDDEN | 403 | Роль не Franchise |
STORE_NOT_FOUND | 404 | ТТ не найдена |
FS_NUMBER_DUPLICATE | 409 | ФН уже зарегистрирован на другом терминале |
VALIDATION_ERROR | 400 | Пустой label / невалидный fs_number |
PATCH /stores/{storeId}/terminals/{terminalId}
Обновить POS-терминал (label, rn_kkt, status).
| Параметр | Значение |
|---|---|
| Auth | Bearer JWT (только Franchise) |
| Content-Type | application/json |
Request Body
{
"label": "string, optional",
"rn_kkt": "string, optional",
"status": "string, optional — active | inactive"
}
fs_numberнеизменяем после создания.
Response 200
Полный объект терминала.
Errors
| Code | HTTP | Когда |
|---|---|---|
UNAUTHORIZED | 401 | |
FORBIDDEN | 403 | Роль не Franchise |
TERMINAL_NOT_FOUND | 404 | |
FS_NUMBER_IMMUTABLE | 422 | Попытка изменить fs_number |
DELETE /stores/{storeId}/terminals/{terminalId}
Удалить POS-терминал (soft delete).
| Параметр | Значение |
|---|---|
| Auth | Bearer JWT (только Franchise) |
Response 204
Нет тела.
Errors
| Code | HTTP | Когда |
|---|---|---|
UNAUTHORIZED | 401 | |
FORBIDDEN | 403 | |
TERMINAL_NOT_FOUND | 404 |
GET /stores/{storeId}/tables
Список столов зала ТТ.
| Параметр | Значение |
|---|---|
| Auth | Bearer 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
Создать стол.
| Параметр | Значение |
|---|---|
| Auth | Bearer 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
| Code | HTTP | Когда |
|---|---|---|
TABLE_NUMBER_DUPLICATE | 409 | Номер уже занят в рамках ТТ |
VALIDATION_ERROR | 400 | Невалидный capacity / number |
PATCH /tables/{id}
Обновить стол (метку, вместимость, позицию на canvas).
| Параметр | Значение |
|---|---|
| Auth | Bearer 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).
| Параметр | Значение |
|---|---|
| Auth | Bearer JWT (только Franchise) |
Response 204
Нет тела.
Errors
| Code | HTTP | Когда |
|---|---|---|
TABLE_OCCUPIED | 422 | Нельзя удалить занятый стол |
POST /tables/{id}/occupy
Пометить стол занятым (привязать активный заказ).
| Параметр | Значение |
|---|---|
| Auth | Bearer JWT (Manager / Cashier своей ТТ) |
Request Body
{
"order_id": "uuid, required"
}Response 200
{
"data": { "id": "uuid", "status": "occupied", "current_order_id": "uuid" }
}Errors
| Code | HTTP | Когда |
|---|---|---|
TABLE_ALREADY_OCCUPIED | 422 | Стол уже занят другим заказом |
POST /tables/{id}/release
Освободить стол (снять активный заказ).
| Параметр | Значение |
|---|---|
| Auth | Bearer JWT (Manager / Cashier своей ТТ) |
Response 200
{ "data": { "id": "uuid", "status": "free", "current_order_id": null } }POST /tables/{id}/reserve
Забронировать стол.
| Параметр | Значение |
|---|---|
| Auth | Bearer JWT (Manager / Cashier своей ТТ) |
| Content-Type | application/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
Снять бронь со стола.
| Параметр | Значение |
|---|---|
| Auth | Bearer JWT (Manager / Cashier своей ТТ) |
Response 200
{ "data": { "id": "uuid", "status": "free" } }PATCH /tables/{id}/waiter
(BR 3.2)
Назначить или снять текущего официанта на стол. Используется для привязки чаевых из Нетмонета, когда webhook не содержит order_number.
| Параметр | Значение |
|---|---|
| Auth | Bearer JWT (Franchise / Manager своей ТТ) |
| Content-Type | application/json |
Request Body
{
"waiter_id": "uuid | null — employee_id официанта; null для снятия назначения"
}Response 200
Полный объект стола с обновлённым current_waiter_id.
Errors
| Code | HTTP | Когда |
|---|---|---|
UNAUTHORIZED | 401 | |
FORBIDDEN | 403 | |
TABLE_NOT_FOUND | 404 |
POST /internal/stores/count-by-legal-entities
Internal endpoint — только для service-to-service вызовов
Получить количество ТТ для списка юридических лиц (batch).
| Параметр | Значение |
|---|---|
| Auth | Service token (X-Service-Token header) |
| Content-Type | application/json |
Request Body
{
"legal_entity_ids": ["uuid", "uuid", "..."]
}Response 200
{
"data": {
"uuid-1": 3,
"uuid-2": 0,
"uuid-3": 1
}
}Возвращает 0 для ЮЛ без привязанных ТТ. Считаются все ТТ (любой статус, включая закрытые).
GET /internal/stores (by legal_entity_id)
Internal endpoint — только для service-to-service вызовов
Получить список ТТ по юридическому лицу.
| Параметр | Значение |
|---|---|
| Auth | Service token (X-Service-Token header) |
Query Parameters
| Param | Type | Required | Description |
|---|---|---|---|
legal_entity_id | uuid | yes | ID юридического лица |
Response 200
{
"data": [
{
"id": "uuid",
"name": "string",
"status": "string",
"is_published": "boolean"
}
]
}Errors
| Code | HTTP | Когда |
|---|---|---|
VALIDATION_ERROR | 400 | Не передан legal_entity_id |
GET /internal/stores/all
Internal endpoint — только для service-to-service вызовов
Список всех живых ТТ всех франшиз (без deleted_at). Используется Warehouse Service для backfill складов по BR 1.14 после деплоя.
| Параметр | Значение |
|---|---|
| Auth | X-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
| Code | HTTP | Когда |
|---|---|---|
UNAUTHORIZED | 401 | Невалидный/отсутствующий X-Service-Token |
POST /internal/stores/unpublish-by-legal-entity
Internal endpoint — только для service-to-service вызовов
Снять с публикации все ТТ юридического лица (при приостановке ЮЛ).
| Параметр | Значение |
|---|---|
| Auth | Service token (X-Service-Token header) |
| Content-Type | application/json |
Request Body
{
"legal_entity_id": "uuid, required"
}Response 200
{
"data": {
"unpublished_count": "integer"
}
}Возвращает 0 если нет опубликованных ТТ. Операция идемпотентна — повторный вызов вернёт 0.
Errors
| Code | HTTP | Когда |
|---|---|---|
VALIDATION_ERROR | 400 | Не передан legal_entity_id |
POST /internal/terminals/bind-by-fs
Internal endpoint — только для service-to-service
Поиск POS-терминала по заводскому номеру ФН — используется при первом запуске терминала для получения store_id.
| Параметр | Значение |
|---|---|
| Auth | Service token (X-Service-Token header) |
| Content-Type | application/json |
Request Body
{ "fs_number": "string, required" }Response 200
{
"data": {
"terminal_id": "uuid",
"store_id": "uuid",
"franchise_id": "uuid",
"label": "string"
}
}Errors
| Code | HTTP | Когда |
|---|---|---|
TERMINAL_NOT_FOUND | 404 | ФН не зарегистрирован |
GET /internal/tables/by-store/{storeId}
Internal endpoint
Список столов ТТ (для Order Service при выборе стола на кассе).
| Параметр | Значение |
|---|---|
| Auth | Service token |
Response 200
Массив объектов table (см. GET /stores/{storeId}/tables).
GET /internal/tables/{id}
Internal endpoint
Получить стол по ID.
| Параметр | Значение |
|---|---|
| Auth | Service 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?).
| Параметр | Значение |
|---|---|
| Auth | Service 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. Поля, которых нет в теле, не меняются.
| Параметр | Значение |
|---|---|
| Auth | Service token |
Response 200
Полный объект стола.
Errors
409 TABLE_NUMBER_DUPLICATE— стол с такимnumberуже есть404 TABLE_NOT_FOUND
DELETE /internal/tables/{id}
Soft delete (deleted_at ставится, столы фильтруются IS NULL).
| Параметр | Значение |
|---|---|
| Auth | Service 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.
| Параметр | Значение |
|---|---|
| Auth | Service token |
Request Body
{ "waiter_id": "uuid | null" }null снимает назначение.
Response 200
Полный объект стола.
Errors
404 TABLE_NOT_FOUND
GET /admin/stores/{storeId}/marketing-slides
(BR 6.1)
Список маркетинговых слайдов ТТ для управления в админке.
| Параметр | Значение |
|---|---|
| Auth | Bearer JWT (permission marketing.read + scope ТТ) |
Path Parameters
| Param | Type | Required | Description |
|---|---|---|---|
storeId | uuid | yes | ID торговой точки |
Query Parameters
| Param | Type | Required | Description |
|---|---|---|---|
active | boolean | no | Фильтр по активности (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
| Code | HTTP | Когда |
|---|---|---|
UNAUTHORIZED | 401 | Невалидный JWT |
FORBIDDEN | 403 | Нет permission marketing.read или scope не включает ТТ |
STORE_NOT_FOUND | 404 | ТТ не найдена |
POST /admin/stores/{storeId}/marketing-slides
(BR 6.1)
Создать новый слайд (multipart с картинкой).
| Параметр | Значение |
|---|---|
| Auth | Bearer JWT (permission marketing.write + scope ТТ) |
| Content-Type | multipart/form-data |
Path Parameters
| Param | Type | Required | Description |
|---|---|---|---|
storeId | uuid | yes | ID торговой точки |
Multipart Fields
| Field | Type | Required | Description |
|---|---|---|---|
file | binary | yes | jpg/png/webp, ≤ 10 МБ, разрешение ≥ 1280×720 |
title | string | yes | До 200 символов |
active | boolean | no | Default true |
Источник
manualСлайды через этот эндпоинт создаются с
source = manual. Дляai_photo_studio— отдельный flow в BR 6.2 (внутренний endpoint, ещё не определён).
Response 201
Полный объект слайда (как в GET).
order присваивается автоматически — MAX(order) + 1 среди слайдов ТТ.
Errors
| Code | HTTP | Когда |
|---|---|---|
UNAUTHORIZED | 401 | |
FORBIDDEN | 403 | Нет permission или scope |
STORE_NOT_FOUND | 404 | ТТ не найдена |
VALIDATION_ERROR | 400 | Пустой title, недопустимый MIME, размер > 10 МБ, разрешение < 1280×720 |
MARKETING_SLIDES_LIMIT_REACHED | 422 | На ТТ уже 20 активных слайдов |
S3_UPLOAD_FAILED | 500 | Сбой загрузки в S3 |
PUT /admin/stores/{storeId}/marketing-slides/{slideId}
(BR 6.1)
Обновить слайд (заголовок, активность, опционально картинка).
| Параметр | Значение |
|---|---|
| Auth | Bearer JWT (permission marketing.write + scope ТТ) |
| Content-Type | multipart/form-data (если меняется картинка) или application/json (только title/active) |
Path Parameters
| Param | Type | Required | Description |
|---|---|---|---|
storeId | uuid | yes | |
slideId | uuid | yes |
Multipart Fields / JSON Body
| Field | Type | Required | Description |
|---|---|---|---|
file | binary | no | Новая картинка. Если передана — старая удаляется (отложенно). |
title | string | no | |
active | boolean | no |
Нельзя менять
source,source_ref,order,store_id— иммутабельны. Если переданы —422.
Response 200
Полный объект слайда.
Errors
| Code | HTTP | Когда |
|---|---|---|
UNAUTHORIZED | 401 | |
FORBIDDEN | 403 | |
SLIDE_NOT_FOUND | 404 | Слайд не найден или удалён |
VALIDATION_ERROR | 400 | |
IMMUTABLE_FIELD | 422 | Попытка изменить source/order/store_id |
MARKETING_SLIDES_LIMIT_REACHED | 422 | Активация 21-го активного слайда |
PATCH /admin/stores/{storeId}/marketing-slides/reorder
(BR 6.1)
Массовое переназначение порядка (drag-and-drop в админке).
| Параметр | Значение |
|---|---|
| Auth | Bearer JWT (permission marketing.write + scope ТТ) |
| Content-Type | application/json |
Path Parameters
| Param | Type | Required | Description |
|---|---|---|---|
storeId | uuid | yes |
Request Body
{
"order": [
{ "id": "uuid", "order": 0 },
{ "id": "uuid", "order": 1 },
{ "id": "uuid", "order": 2 }
]
}Транзакционный апдейт
Бэк перезаписывает
orderвсем слайдам ТТ в одной транзакции. Если в массиве пропущен какой-то слайд ТТ — он попадает в конец (order = max + 1).
Response 200
Полный список слайдов ТТ с новыми order (как GET).
Errors
| Code | HTTP | Когда |
|---|---|---|
UNAUTHORIZED | 401 | |
FORBIDDEN | 403 | |
STORE_NOT_FOUND | 404 | |
SLIDE_NOT_FOUND | 404 | Хотя бы один ID не принадлежит ТТ |
VALIDATION_ERROR | 400 | Дубликаты order в массиве, отрицательные значения |
DELETE /admin/stores/{storeId}/marketing-slides/{slideId}
(BR 6.1)
Удалить слайд (soft delete). Картинка в S3 — удаляется отложенно (cron).
| Параметр | Значение |
|---|---|
| Auth | Bearer JWT (permission marketing.write + scope ТТ) |
Path Parameters
| Param | Type | Required | Description |
|---|---|---|---|
storeId | uuid | yes | |
slideId | uuid | yes |
Response 204
Нет тела.
Errors
| Code | HTTP | Когда |
|---|---|---|
UNAUTHORIZED | 401 | |
FORBIDDEN | 403 | |
SLIDE_NOT_FOUND | 404 | Слайд не найден или уже удалён |
GET /internal/stores/{storeId}/marketing-slides/active
Internal endpoint — только для service-to-service (POS BFF)
Список активных слайдов ТТ для отдачи на POS Desktop в standby-режим. Возвращает уже отсортированный по order массив с публичными image URL.
| Параметр | Значение |
|---|---|
| Auth | Service token (X-Service-Token header) |
Path Parameters
| Param | Type | Required | Description |
|---|---|---|---|
storeId | uuid | yes |
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
| Code | HTTP | Когда |
|---|---|---|
UNAUTHORIZED | 401 | Невалидный/отсутствующий X-Service-Token |
STORE_NOT_FOUND | 404 |
Общий формат ошибок
{
"error": {
"code": "string — машиночитаемый код (UPPER_SNAKE_CASE)",
"message": "string — человекочитаемое описание",
"details": [
{
"field": "string — путь к полю",
"message": "string — описание ошибки"
}
]
}
}