Order Service — API Contract

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

(Добавлено в BR 2.1)

Содержание

Заказы

Позиции заказа

Оплата

Жизненный цикл

Агрегаторский flow (Я.Еда / МД)

Клиент заказа (BR 3.1)

Internal (service-to-service)


POST /orders

Создать новый заказ. Заказ создаётся в статусе new с пустым списком позиций. Номер заказа генерируется автоматически (per-store per-day).

ПараметрЗначение
AuthBearer JWT (Cashier, Manager, Franchisee, Franchise)
Content-Typeapplication/json

Request Body

{
  "store_id": "uuid",            // required
  "order_type": "takeaway",      // required, default: "takeaway"
  "comment": "string"            // optional
}
FieldTypeRequiredDescription
store_iduuidyesТорговая точка. Должна быть доступна по роли
order_typestringyesТип заказа: takeaway (MVP)
commentstringnoКомментарий к заказу

Response 201

{
  "data": {
    "id": "uuid",
    "franchise_id": "uuid",
    "store_id": "uuid",
    "order_number": "001",
    "order_type": "takeaway",
    "status": "new",
    "total": 0.00,
    "payment_method": null,
    "paid_amount": null,
    "rrn": null,
    "card_last4": null,
    "comment": "string | null",
    "cancel_reason": null,
    "created_by": "uuid",
    "paid_at": null,
    "completed_at": null,
    "cancelled_at": null,
    "created_at": "datetime",
    "updated_at": "datetime",
    "items": []
  }
}

Errors

CodeHTTPОписание
VALIDATION_ERROR400Не указан store_id или невалидный order_type
UNAUTHORIZED401Нет токена или невалидный
FORBIDDEN403Нет доступа к store_id
STORE_NOT_FOUND404ТТ не найдена

GET /orders/{id}

Получить заказ с позициями.

ПараметрЗначение
AuthBearer JWT (Franchise — все; Franchisee/Manager/Cashier — по store_id)

Path Parameters

ParamTypeDescription
iduuidID заказа

Response 200

{
  "data": {
    "id": "uuid",
    "franchise_id": "uuid",
    "store_id": "uuid",
    "customer_id": "uuid | null",
    "order_number": "001",
    "order_type": "takeaway",
    "status": "new",
    "total": 630.00,
    "payment_method": "cash | null",
    "paid_amount": 630.00,
    "rrn": "string | null",
    "card_last4": "string | null",
    "comment": "string | null",
    "cancel_reason": "string | null",
    "created_by": "uuid",
    "paid_at": "datetime | null",
    "completed_at": "datetime | null",
    "cancelled_at": "datetime | null",
    "created_at": "datetime",
    "updated_at": "datetime",
    "items": [
      {
        "id": "uuid",
        "product_id": "uuid",
        "product_name": "Шаурма классическая",
        "quantity": 2.000,
        "unit_price": 250.00,
        "total_price": 500.00,
        "modifiers": [
          {
            "group_name": "Соус",
            "option_name": "Чесночный",
            "price": 30.00
          }
        ],
        "notes": "без лука",
        "created_at": "datetime"
      }
    ]
  }
}

Errors

CodeHTTPОписание
UNAUTHORIZED401Нет токена
FORBIDDEN403Нет доступа к заказу (не та ТТ)
ORDER_NOT_FOUND404Заказ не найден

GET /orders

Список заказов с фильтрацией и пагинацией.

ПараметрЗначение
AuthBearer JWT (Franchise — все; Franchisee — свои ТТ; Manager/Cashier — своя ТТ)

Query Parameters

ParamTypeRequiredDescription
store_iduuidnoФильтр по ТТ
statusstringnoФильтр по статусу: new, in_progress, ready, closed, cancelled
date_fromdatenoДата от (включительно), формат: YYYY-MM-DD
date_todatenoДата до (включительно), формат: YYYY-MM-DD
pageintegernodefault: 1
per_pageintegernodefault: 20, max: 100

Response 200

{
  "data": [
    {
      "id": "uuid",
      "store_id": "uuid",
      "order_number": "001",
      "order_type": "takeaway",
      "status": "new",
      "total": 630.00,
      "payment_method": "cash | null",
      "paid_amount": 630.00,
      "created_by": "uuid",
      "created_at": "datetime",
      "paid_at": "datetime | null",
      "completed_at": "datetime | null",
      "cancelled_at": "datetime | null"
    }
  ],
  "meta": {
    "page": 1,
    "per_page": 20,
    "total": 42
  }
}

Errors

CodeHTTPОписание
UNAUTHORIZED401Нет токена
FORBIDDEN403Нет доступа к store_id

PATCH /orders/{id}

Обновить заказ. Можно менять только комментарий, только в статусе new.

ПараметрЗначение
AuthBearer JWT (Cashier, Manager)
Content-Typeapplication/json

Path Parameters

ParamTypeDescription
iduuidID заказа

Request Body

{
  "comment": "string"            // optional
}

Response 200

{
  "data": {
    "id": "uuid",
    "franchise_id": "uuid",
    "store_id": "uuid",
    "order_number": "001",
    "order_type": "takeaway",
    "status": "new",
    "total": 630.00,
    "comment": "Обновлённый комментарий",
    "created_at": "datetime",
    "updated_at": "datetime"
  }
}

Errors

CodeHTTPОписание
UNAUTHORIZED401Нет токена
FORBIDDEN403Нет доступа к заказу
ORDER_NOT_FOUND404Заказ не найден
ORDER_NOT_EDITABLE422Заказ не в статусе new

POST /orders/{id}/items

Добавить позицию в заказ. Только в статусе new. Пересчитывает total заказа.

ПараметрЗначение
AuthBearer JWT (Cashier, Manager, Franchisee, Franchise)
Content-Typeapplication/json

Path Parameters

ParamTypeDescription
iduuidID заказа

Request Body

{
  "product_id": "uuid",           // required
  "product_name": "string",       // required — денормализовано
  "quantity": 2.000,              // required
  "unit_price": 250.00,           // required — денормализовано
  "modifiers": [                  // optional
    {
      "group_name": "Соус",
      "option_name": "Чесночный",
      "price": 30.00
    }
  ],
  "notes": "без лука"            // optional
}
FieldTypeRequiredDescription
product_iduuidyesID товара из каталога
product_namestringyesНазвание товара (денормализовано)
quantitydecimalyesКоличество
unit_pricedecimalyesЦена за единицу (денормализовано из прейскуранта)
modifiersarraynoМодификаторы: [{group_name, option_name, price}]
notesstringnoКомментарий к позиции

Response 201

{
  "data": {
    "id": "uuid",
    "order_id": "uuid",
    "product_id": "uuid",
    "product_name": "Шаурма классическая",
    "quantity": 2.000,
    "unit_price": 250.00,
    "total_price": 500.00,
    "modifiers": [
      {
        "group_name": "Соус",
        "option_name": "Чесночный",
        "price": 30.00
      }
    ],
    "notes": "без лука",
    "created_at": "datetime"
  }
}

Errors

CodeHTTPОписание
VALIDATION_ERROR400Отсутствуют обязательные поля
UNAUTHORIZED401Нет токена
FORBIDDEN403Нет доступа к заказу
ORDER_NOT_FOUND404Заказ не найден
ORDER_NOT_EDITABLE422Заказ не в статусе new

DELETE /orders/{id}/items/{itemId}

Удалить позицию из заказа. Только в статусе new. Пересчитывает total заказа.

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

Path Parameters

ParamTypeDescription
iduuidID заказа
itemIduuidID позиции

Response 204

Нет тела ответа.

Errors

CodeHTTPОписание
UNAUTHORIZED401Нет токена
FORBIDDEN403Нет доступа к заказу
ORDER_NOT_FOUND404Заказ не найден
ORDER_ITEM_NOT_FOUND404Позиция не найдена
ORDER_NOT_EDITABLE422Заказ не в статусе new

POST /orders/{id}/pay

Зафиксировать факт оплаты. Записывает payment_method, paid_amount, rrn, card_last4. Не меняет статус заказа (изменено в BR 2.5 — раньше переводил в ready).

Допустимо из статусов ready, delivered (оплата может идти до выдачи, или курьер принимает наличку после доставки). Из других статусов — 409 INVALID_TRANSITION.

ПараметрЗначение
AuthBearer JWT (Cashier, Manager)
Content-Typeapplication/json

Path Parameters

ParamTypeDescription
iduuidID заказа

Request Body

{
  "method": "cash",              // required: cash | card | qr | mixed
  "amount": 630.00,             // required
  "rrn": "string",              // optional — для card/qr
  "card_last4": "1234"          // optional — для card
}
FieldTypeRequiredDescription
methodstringyesСпособ оплаты: cash, card, qr, mixed
amountdecimalyesСумма оплаты
rrnstringnoRRN транзакции (от PBF, для карточных)
card_last4stringnoПоследние 4 цифры карты

Response 200

{
  "data": {
    "id": "uuid",
    "franchise_id": "uuid",
    "store_id": "uuid",
    "order_number": "001",
    "order_type": "takeaway",
    "status": "ready",
    "total": 630.00,
    "payment_method": "cash",
    "paid_amount": 630.00,
    "rrn": null,
    "card_last4": null,
    "paid_at": "datetime",
    "created_at": "datetime",
    "updated_at": "datetime"
  }
}

Errors

CodeHTTPОписание
VALIDATION_ERROR400Отсутствует method или amount
UNAUTHORIZED401Нет токена
FORBIDDEN403Нет доступа к заказу
ORDER_NOT_FOUND404Заказ не найден
ORDER_ALREADY_PAID422Заказ уже оплачен

POST /orders/{id}/complete

Завершить заказ (выдан клиенту). Допустимо из ready (для внутренних заказов) или delivered (для доставки после курьера). Проставляет completed_at, переводит в closed.

Инвариант (добавлено в BR 2.5): paid_at IS NOT NULL обязательно, иначе 409 ORDER_NOT_PAID. Legacy-записи без paid_at (до BR 2.5) этому правилу не подчиняются — проверяется только для новых переходов.

ПараметрЗначение
AuthBearer JWT (Cashier, Manager)

Path Parameters

ParamTypeDescription
iduuidID заказа

Response 200

{
  "data": {
    "id": "uuid",
    "franchise_id": "uuid",
    "store_id": "uuid",
    "order_number": "001",
    "order_type": "takeaway",
    "status": "closed",
    "total": 630.00,
    "payment_method": "cash",
    "paid_amount": 630.00,
    "completed_at": "datetime",
    "created_at": "datetime",
    "updated_at": "datetime"
  }
}

Errors

CodeHTTPОписание
UNAUTHORIZED401Нет токена
FORBIDDEN403Нет доступа к заказу
ORDER_NOT_FOUND404Заказ не найден
INVALID_STATUS_TRANSITION422Заказ не в статусе ready

POST /orders/{id}/cancel

Отменить заказ. Допустимо из статусов new, in_progress, ready. Из closed — нельзя. Проставляет cancelled_at, cancel_reason, переводит в cancelled.

ПараметрЗначение
AuthBearer JWT (Franchise — любой; Franchisee — свои ТТ; Manager — своя ТТ; Cashier — свой заказ)
Content-Typeapplication/json

Path Parameters

ParamTypeDescription
iduuidID заказа

Request Body

{
  "reason": "Клиент отказался"   // required
}
FieldTypeRequiredDescription
reasonstringyesПричина отмены

Response 200

{
  "data": {
    "id": "uuid",
    "franchise_id": "uuid",
    "store_id": "uuid",
    "order_number": "001",
    "order_type": "takeaway",
    "status": "cancelled",
    "total": 630.00,
    "cancel_reason": "Клиент отказался",
    "cancelled_at": "datetime",
    "created_at": "datetime",
    "updated_at": "datetime"
  }
}

Errors

CodeHTTPОписание
VALIDATION_ERROR400Не указана причина
UNAUTHORIZED401Нет токена
FORBIDDEN403Нет доступа к заказу
ORDER_NOT_FOUND404Заказ не найден
ORDER_ALREADY_CLOSED422Заказ в статусе closed — отмена невозможна

POST /orders/{id}/accept

Принять агрегаторский заказ (Я.Еда / МД) — статус newaccepted. Публикует order.status.changed, который Aggregator Service транслирует в платформу.

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

Path Parameters

ParamTypeRequiredDescription
iduuidyesID заказа

Response 200

{ "data": { "id": "uuid", "status": "accepted", "previous_status": "new" } }

Errors

CodeHTTPКогда
ORDER_NOT_FOUND404Заказ не найден
ORDER_NOT_AGGREGATOR422Заказ не из агрегатора
ORDER_INVALID_TRANSITION422Переход из текущего статуса невозможен

POST /orders/{id}/ready

Пометить агрегаторский заказ готовым — acceptedready. Публикует order.status.changed.

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

Response 200

{ "data": { "id": "uuid", "status": "ready", "previous_status": "accepted" } }

Errors

CodeHTTPКогда
ORDER_NOT_FOUND404
ORDER_INVALID_TRANSITION422

POST /orders/{id}/hand-over

Отдать готовый заказ курьеру агрегатора — readyhanded_over. Публикует order.status.changed.

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

Response 200

{ "data": { "id": "uuid", "status": "handed_over", "previous_status": "ready" } }

Errors

CodeHTTPКогда
ORDER_NOT_FOUND404
ORDER_INVALID_TRANSITION422

POST /orders/{id}/reject

Отклонить агрегаторский заказ (с указанием причины). Публикует order.status.changed со статусом rejected.

ПараметрЗначение
AuthBearer JWT (Cashier / Manager / Franchisee / Franchise)
Content-Typeapplication/json

Request Body

{ "reason": "string, required — причина отклонения" }

Response 200

{ "data": { "id": "uuid", "status": "rejected", "reason": "string" } }

Errors

CodeHTTPКогда
VALIDATION_ERROR400Не указана причина
ORDER_NOT_FOUND404
ORDER_INVALID_TRANSITION422

PATCH /orders/{id}/customer

(BR 3.1)

Прикрепить клиента к заказу. Вызывается с POS (кассир после поиска/создания клиента) или из админки. Только для незакрытых заказов.

ПараметрЗначение
AuthBearer JWT + permission customers.create_quick (для кассира на POS) или orders.edit (для менеджера в админке)
Content-Typeapplication/json

Path Parameters

ParamTypeDescription
iduuidID заказа

Request Body

{ "customer_id": "uuid" }

Если текущий заказ уже имеет customer_id — он заменяется.

Response 200

Заказ с обновлённым customer_id (полный объект как в GET /orders/{id}).

Errors

CodeHTTPОписание
ORDER_NOT_FOUND404
CUSTOMER_NOT_FOUND404Клиент не существует (Order Service валидирует через GET /internal/customers/{id} Customer Service) или другая франшиза
ORDER_FINALIZED422Заказ в статусе closed или cancelled — клиента менять нельзя
UNAUTHORIZED401
FORBIDDEN403

DELETE /orders/{id}/customer

(BR 3.1)

Открепить клиента от заказа (обнулить customer_id). Только для незакрытых заказов.

ПараметрЗначение
AuthBearer JWT + permission customers.create_quick или orders.edit

Response 204

Errors

CodeHTTPОписание
ORDER_NOT_FOUND404
ORDER_FINALIZED422Заказ в статусе closed или cancelled

Жизненный цикл — переходы статусов (BR 2.5)

Секция добавлена в BR 2.5. Каждый endpoint = явный переход одного направления между статусами. Ошибка 409 INVALID_TRANSITION если текущий статус не допускает перехода.

POST /orders/{id}/start-cooking

«Начать готовку» — переход new → accepted. Триггерит кухню / повара начать обработку заказа. Проставляет accepted_at, accepted_by.

ПараметрЗначение
AuthBearer JWT (Cashier / Manager с orders.edit)

Path Parameters

ParamTypeDescription
iduuidID заказа

Response 200

{
  "data": {
    "id": "uuid",
    "status": "accepted",
    "accepted_at": "datetime",
    "accepted_by": "uuid"
  }
}

Errors

CodeHTTPОписание
UNAUTHORIZED401
FORBIDDEN403Нет permission orders.edit или нет scope-доступа к ТТ заказа
ORDER_NOT_FOUND404
INVALID_TRANSITION409Заказ не в статусе new

POST /orders/{id}/mark-ready

«Готово» — переход accepted → ready. Проставляет ready_at.

ПараметрЗначение
AuthBearer JWT (Cashier / Manager с orders.edit)

Path Parameters

ParamTypeDescription
iduuidID заказа

Response 200

{
  "data": {
    "id": "uuid",
    "status": "ready",
    "ready_at": "datetime"
  }
}

Errors

CodeHTTPОписание
UNAUTHORIZED401
FORBIDDEN403
ORDER_NOT_FOUND404
INVALID_TRANSITION409Заказ не в статусе accepted

POST /orders/{id}/checkout

Shortcut для быстрой продажи: new → closed за один вызов. Разрешён только если order_type = takeaway и requires_kitchen = false (в заказе нет позиций, требующих приготовления). Внутренне выполняет pay + close атомарно.

ПараметрЗначение
AuthBearer JWT (Cashier / Manager с orders.edit)
Content-Typeapplication/json

Path Parameters

ParamTypeDescription
iduuidID заказа

Request Body

Тот же что и у POST /orders/{id}/pay{ method, amount, rrn, card_last4 }.

Response 200

Заказ в статусе closed со всеми полями paid_at и completed_at.

Errors

CodeHTTPОписание
UNAUTHORIZED401
FORBIDDEN403
ORDER_NOT_FOUND404
INVALID_TRANSITION409Заказ не в статусе new
ORDER_FLOW_MISMATCH409Тип не takeaway или requires_kitchen=true — нужен полный flow
VALIDATION_ERROR400Invalid payment body

POST /orders/{id}/hand-over-to-courier

Передать заказ курьеру — переход ready → handed_over. Проставляет handed_over_at, courier_id.

ПараметрЗначение
AuthBearer JWT (Cashier / Manager с orders.edit)
Content-Typeapplication/json

Path Parameters

ParamTypeDescription
iduuidID заказа

Request Body

{
  "courier_id": "uuid"
}
FieldTypeRequiredDescription
courier_iduuidyesID сотрудника-курьера (должен иметь permission orders.delivery)

Response 200

{
  "data": {
    "id": "uuid",
    "status": "handed_over",
    "handed_over_at": "datetime",
    "courier_id": "uuid"
  }
}

Errors

CodeHTTPОписание
UNAUTHORIZED401
FORBIDDEN403
ORDER_NOT_FOUND404
COURIER_NOT_FOUND404courier_id не существует или не имеет orders.delivery
INVALID_TRANSITION409Заказ не в статусе ready
WRONG_ORDER_TYPE409Заказ не delivery

POST /orders/{id}/start-delivery

Курьер начал путь — переход handed_over → in_delivery. Проставляет in_delivery_at.

ПараметрЗначение
AuthBearer JWT с permission orders.delivery (либо orders.edit для надпрокси)

Path Parameters

ParamTypeDescription
iduuidID заказа

Response 200

{
  "data": {
    "id": "uuid",
    "status": "in_delivery",
    "in_delivery_at": "datetime"
  }
}

Errors

CodeHTTPОписание
UNAUTHORIZED401
FORBIDDEN403Нет orders.delivery, или courier_id не совпадает с текущим user
ORDER_NOT_FOUND404
INVALID_TRANSITION409Заказ не в статусе handed_over

POST /orders/{id}/confirm-delivery

Клиент получил заказ — переход in_delivery → delivered. Проставляет delivered_at.

ПараметрЗначение
AuthBearer JWT с permission orders.delivery

Path Parameters

ParamTypeDescription
iduuidID заказа

Response 200

{
  "data": {
    "id": "uuid",
    "status": "delivered",
    "delivered_at": "datetime"
  }
}

Errors

CodeHTTPОписание
UNAUTHORIZED401
FORBIDDEN403
ORDER_NOT_FOUND404
INVALID_TRANSITION409Заказ не в статусе in_delivery

GET /internal/orders/active-by-stations

(BR 5.1 — KDS)

Активные заказы для выбранных кухонных станций, для подгрузки списка на KDS-устройстве. Возвращает заказы в статусе accepted (или указанном) с позициями указанных станций. Позиции других станций тоже возвращаются (для контекста), но KDS-приложение их рендерит серым без управления.

ПараметрЗначение
AuthX-Service-Token (вызывается из pos-bff)

Query Parameters

ParamTypeRequiredDescription
franchise_iduuidyesИз JWT pos-bff
store_iduuidyesТТ устройства (из kds_devices.store_id)
station_idsstring (csv UUID)yesВыбранные при логине станции
statusstringno, default=acceptedФильтр статуса (для KDS — только accepted)
limitintno, default=50

Response 200

{
  "data": [
    {
      "id": "uuid",
      "store_id": "uuid",
      "order_number": "001",
      "order_type": "dine_in",
      "status": "accepted",
      "expected_ready_at": "2026-04-29T14:00:00Z",
      "accepted_at": "2026-04-29T13:45:00Z",
      "comment": "без лука",
      "table_id": "uuid",
      "client_name": null,
      "items": [
        {
          "id": "uuid",
          "product_id": "uuid",
          "product_name": "Карбонара",
          "kitchen_station_id": "uuid",
          "kitchen_station_name": "Горячий цех",
          "quantity": 1,
          "modifiers": [{"group_name":"Соус","option_name":"Карбонара","price":0}],
          "notes": "без перца",
          "kitchen_status": "preparing",
          "kitchen_started_at": "2026-04-29T13:50:00Z",
          "kitchen_ready_at": null,
          "requires_kitchen": true
        }
      ],
      "created_at": "2026-04-29T13:45:00Z"
    }
  ],
  "meta": { "total": 5, "page": 1, "per_page": 50 }
}

Позиции возвращаются все — фильтрация по station_ids делается на клиенте/в pos-bff (для отображения серых позиций в карточке).

Errors

CodeHTTPОписание
UNAUTHORIZED401Невалидный X-Service-Token
INVALID_PARAMS400Неправильный формат station_ids или отсутствуют обязательные query

PATCH /orders/{id}/items/{itemId}/kitchen-status

(BR 5.1 — KDS)

Изменение kitchen_status одной позиции через KDS-устройство (тап «В работе» или «Готово»). Откат ready → preparing запрещён.

ПараметрЗначение
AuthBearer JWT с permission kds.access
Content-Typeapplication/json

Path Parameters

ParamTypeDescription
iduuidID заказа
itemIduuidID позиции

Request Body

{ "kitchen_status": "preparing" }

Допустимые значения: preparing, ready. (pending через KDS не выставляется — это default.)

Response 200

Обновлённый item (формат как в GET /orders/{id}items[]).

Errors

CodeHTTPОписание
UNAUTHORIZED401
FORBIDDEN403Нет permission kds.access
INVALID_FRANCHISE403Multi-tenancy violation
ORDER_NOT_FOUND404
ITEM_NOT_FOUND404
ORDER_NOT_IN_KITCHEN_FLOW422Заказ не в accepted
KITCHEN_STATUS_BACKWARD422Попытка ready → preparing (откат запрещён)
VALIDATION_ERROR400Невалидное значение kitchen_status

Side-effects

  • preparingkitchen_started_at = NOW() (если ещё не было)
  • readykitchen_ready_at = NOW()
  • Публикуется order.item.kitchen_status_changed (см. Events)
  • Если все items заказа в readyorders.status = ready, ready_at = NOW(), событие order.ready с trigger=kds_auto

PATCH /orders/{id}/kitchen-status

(BR 5.1 — KDS)

Atomic batch — закрыть все позиции одной кухонной станции одним вызовом. Используется кнопкой «Готово» по станции на KDS.

ПараметрЗначение
AuthBearer JWT с permission kds.access
Content-Typeapplication/json

Path Parameters

ParamTypeDescription
iduuidID заказа

Query Parameters

ParamTypeRequiredDescription
station_iduuidyesСтанция, чьи позиции массово закрываются

Request Body

{ "kitchen_status": "ready" }

В P0 поддерживается только ready (массовое закрытие). preparing для batch — не имеет смысла.

Response 200

Обновлённый заказ со всеми позициями (формат как GET /orders/{id}).

Errors

CodeHTTPОписание
UNAUTHORIZED401
FORBIDDEN403Нет permission kds.access
INVALID_FRANCHISE403
ORDER_NOT_FOUND404
STATION_NOT_FOUND404На станции нет позиций в этом заказе
ORDER_NOT_IN_KITCHEN_FLOW422Заказ не в accepted
STATION_NOT_READY422Не все позиции этой станции готовы (был pending/preparing без принудительного «Готово») — kdc сам должен вызвать PATCH /items/{itemId}/kitchen-status сначала
VALIDATION_ERROR400

Side-effects

  • Для каждой позиции с kitchen_station_id = station_id И kitchen_status != 'ready' ставится kitchen_status = 'ready', kitchen_ready_at = NOW()
  • Публикуется одно событие order.item.kitchen_status_changed per item
  • Триггерится автопереход accepted → ready если все позиции готовы

Альтернативная семантика (P1+)

В P0 endpoint требует чтобы все позиции станции были предварительно в ready. Это даёт более явный пользовательский поток: повар сначала тапает «Готово» на каждой позиции, потом «Готово на станцию» — sanity check. В P1+ может быть relaxed.


GET /internal/orders/customer-summary

(BR 3.1)

Агрегированная статистика заказов клиента. Используется Customer Service для пересчёта dynamic групп (правила total_spent_all_time, days_inactive и т.п.).

ПараметрЗначение
AuthX-Service-Token

Query Parameters

ParamTypeRequiredDescription
customer_iduuidyes

Response 200

{
  "data": {
    "customer_id": "uuid",
    "total_spent_all_time": "12450.50",
    "orders_count": 15,
    "first_order_at": "2025-11-01T12:00:00Z",
    "last_order_at": "2026-04-18T14:20:00Z",
    "orders_last_30_days": {
      "count": 3,
      "total": "1890.00"
    },
    "orders_last_90_days": {
      "count": 8,
      "total": "5400.00"
    }
  }
}

Учитываются только заказы со статусом closed (завершённые с оплатой). Возвраты (refund_records) уменьшают total_spent_*.

Errors

CodeHTTPОписание
UNAUTHORIZED401Нет или невалидный X-Service-Token

POST /internal/orders/{orderId}/refund

Создать запись возврата для существующего оплаченного заказа. Вызывается из POS BFF (кассир на терминале) или Admin BFF (менеджер в админке). Идемпотентен по client_refund_id: повторный вызов с тем же id возвращает 200 OK и существующую запись вместо 201.

ПараметрЗначение
AuthX-Service-Token
Content-Typeapplication/json

Path Parameters

ParamTypeDescription
orderIduuidID оригинального заказа

Request Body

{
  "client_refund_id": "pos-rfnd-1776...-12345",
  "amount": 1500.01,
  "type": "refund",
  "payment_method": "card",
  "rrn": "610605053907",
  "fiscal_doc_number": "16",
  "initiated_by": "pos",
  "cashier_id": "uuid"
}
FieldTypeRequiredDescription
client_refund_idstringyesИдемпотентный ключ (равен externalId чека возврата в ФЯ)
amountdecimalyesСумма возврата. Должна быть > 0 и ≤ order.total − уже_возвращённое
typestringnorefund (по умолчанию) или reversal
payment_methodstringyescash или card
rrnstringnoRRN возврата от эквайера (для card)
fiscal_doc_numberstringnoНомер фискального документа чека возврата в ФЯ
initiated_bystringnopos (по умолчанию) или admin
cashier_iduuidyesID кассира, проводившего возврат

Response 201 (новый возврат) или 200 (идемпотентный повтор)

{
  "data": {
    "id": "uuid",
    "order_id": "uuid",
    "client_refund_id": "pos-rfnd-1776...-12345",
    "amount": 1500.01,
    "type": "refund",
    "payment_method": "card",
    "rrn": "610605053907",
    "fiscal_doc_number": "16",
    "initiated_by": "pos",
    "cashier_id": "uuid",
    "created_at": "datetime",
    "idempotent": false
  }
}

Публикует Kafka-событие order.refunded (при новом создании). Не публикует при идемпотентном повторе.

Errors

CodeHTTPОписание
VALIDATION_ERROR400Невалидное тело (amount ≤ 0, нет client_refund_id)
ORDER_NOT_FOUND404Заказ не найден
ORDER_NOT_PAID422Нельзя возвращать неоплаченный заказ
REFUND_EXCEEDS_ORDER_TOTAL422Суммарный возврат превысил бы сумму заказа

POST /internal/shifts/open

Зафиксировать факт открытия кассовой смены на POS-терминале. Order Service не хранит shift persistence — только публикует Kafka pos.shift.opened для консьюмеров (админка). Вызывается POS BFF при явном открытии смены кассиром через ServiceMenu.

ПараметрЗначение
AuthX-Service-Token
Content-Typeapplication/json

Request Body

{
  "store_id": "uuid",
  "franchise_id": "uuid",
  "cashier_id": "uuid",
  "fiscal_cycle": 2,
  "opened_at": "2026-04-16T15:36:00+08:00"
}

Response 200

{ "data": { "fiscal_cycle": 2, "published": true } }

POST /internal/shifts/close

Зафиксировать закрытие смены (Z-отчёт). Публикует pos.shift.closed с итогами.

ПараметрЗначение
AuthX-Service-Token
Content-Typeapplication/json

Request Body

{
  "store_id": "uuid",
  "franchise_id": "uuid",
  "cashier_id": "uuid",
  "fiscal_cycle": 2,
  "closed_at": "2026-04-16T23:59:00+08:00",
  "z_report_doc_number": 42,
  "sales_total": 50000.00,
  "refunds_total": 500.00,
  "cash_total": 10000.00,
  "card_total": 40000.00,
  "sales_count": 35,
  "refunds_count": 1
}

Response 200

{ "data": { "fiscal_cycle": 2, "published": true } }

GET /internal/orders/shift-report

Агрегация заказов за период для отчёта по смене. Internal endpoint для User Service.

(Добавлено в BR 2.2)

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

Query Parameters

ParamTypeRequiredDescription
store_iduuidyesТТ
employee_iduuidnoФильтр по сотруднику (кассиру)
fromdatetimeyesНачало периода (ISO 8601)
todatetimeyesКонец периода (ISO 8601)

Response 200

{
  "data": {
    "total_revenue": 15420.00,
    "cash_amount": 5200.00,
    "card_amount": 8720.00,
    "qr_amount": 1500.00,
    "refund_amount": 0.00,
    "discount_amount": 0.00,
    "total_orders": 23,
    "completed_orders": 21,
    "cancelled_orders": 2,
    "average_check": 734.29,
    "top_items": [
      {
        "product_id": "uuid",
        "name": "Латте 0.4",
        "quantity": 15,
        "amount": 4500.00
      },
      {
        "product_id": "uuid",
        "name": "Капучино 0.3",
        "quantity": 12,
        "amount": 3600.00
      }
    ]
  }
}
FieldTypeDescription
total_revenuedecimalСумма paid_amount всех оплаченных заказов
cash_amountdecimalОплата наличными
card_amountdecimalОплата картой
qr_amountdecimalОплата QR
refund_amountdecimalВозвраты (0 — не реализованы в Phase 1)
discount_amountdecimalСкидки (0 — не реализованы в Phase 1)
total_ordersintegerВсего заказов
completed_ordersintegerЗавершённых (status = closed)
cancelled_ordersintegerОтменённых (status = cancelled)
average_checkdecimalСредний чек (total_revenue / completed_orders)
top_itemsarrayТоп-10 товаров по сумме продаж
top_items[].product_iduuidID товара
top_items[].namestringНазвание товара
top_items[].quantityintegerСуммарное количество
top_items[].amountdecimalСуммарная стоимость

Фильтрация по paid_at

Заказы фильтруются по paid_at BETWEEN :from AND :to. Неоплаченные заказы не попадают в отчёт.

Errors

CodeHTTPКогда
VALIDATION_ERROR400Не передан store_id или from
UNAUTHORIZED401Невалидный или отсутствующий Service Token