# API наличия товаров в магазинах для Shopping Assistant

Если покупатель спрашивает, есть ли товар **в конкретном магазине** или **в каких магазинах поблизости** он доступен, Shopping Assistant нужны актуальные данные из системы клиента. Это особенно важно при большом числе магазинов и частом изменении остатков. Региональные варианты `in_stock` в товарном фиде через `lng` подходят для более стабильных зон обслуживания, но обновление фида не гарантирует наличие на момент вопроса в отдельной точке.

Для такого сценария передайте Gravity Field справочник магазинов и предоставьте серверный HTTPS API для актуальной проверки товара по SKU и магазину. Справочник проще сначала передать файлом; если магазины часто добавляются или меняются, его можно подключить через API. Gravity Field вызывает API остатков во время диалога и использует его ответ как источник сведений о наличии. Адреса ручек, авторизацию, лимиты и правила интерпретации данных согласуйте при подключении. Примеры ниже показывают ожидаемые возможности и формат обмена; сами по себе они не включают проверку остатков в Shopping Assistant.

Товарный фид продолжает передавать карточки и атрибуты товаров. `sku` во внешнем API должен точно совпадать с `sku` в [фиде](/Integration/products_catalogues/general_reqs.md). Общее `in_stock` из фида не подтверждает наличие в конкретном магазине.

## Какие данные нужны

| Возможность | Для чего нужна |
| :--- | :--- |
| Справочник магазинов | Сопоставить название, адрес, город или район из сообщения покупателя со стабильным `storeId`; при нескольких совпадениях предложить уточнить точку. |
| Проверка по `sku` и `storeId` | Ответить, доступен ли один или несколько товаров в выбранных магазинах сейчас. |
| Поиск магазинов с товаром | Ответить на вопрос «где есть» в заданном городе или радиусе без перебора всего справочника магазинов. |

### Справочник магазинов

Справочник можно передать двумя способами. **Рекомендуем начать с файла:** это быстрее для первого запуска, а если магазины меняются редко, файла может быть достаточно и в дальнейшем. API справочника имеет смысл подключать, когда состав и данные точек меняются часто.

В обоих вариантах для каждой точки нужны стабильный `storeId`, название, полный адрес, город, координаты и признак действующего магазина. Название и адрес нужны для ответа покупателю, а `storeId` — для проверки наличия. Не используйте свободный текст адреса как идентификатор точки. Если найдено несколько подходящих магазинов, ассистент должен уточнить выбор до проверки конкретной точки.

#### Вариант 1. Файл справочника

Передайте полный CSV-файл в UTF-8 по согласованной ссылке, доступной для скачивания Gravity Field. Одна строка — один магазин; при обновлении публикуйте полный актуальный список с теми же `storeId` для существующих точек. Периодичность обновления согласуйте при подключении. Файл подходит и как первый этап, даже если позже справочник будет передаваться через API.

```csv
storeId,name,address,city,latitude,longitude,active
kzn-014,Магазин — Kazan Mall,"Казань, ул. Примерная, 1",Казань,55.767,49.137,true
```

#### Вариант 2. API справочника

Ручка поиска должна принимать текстовое описание магазина или географическое ограничение и возвращать подходящие точки. Если магазинов много, поддержите ограничение числа результатов и постраничную выдачу.

Для поиска по местоположению вместо `query` передавайте `city` либо `latitude`, `longitude` и `radiusKm`. Для следующей страницы повторите те же условия и передайте `cursor` из предыдущего `nextCursor`.

Пример адреса ручки: `POST https://shop.example.com/api/assistant/stores/search`.

```json
{
  "requestId": "8f2b1c0a-6c1d-4a0e-9f3b-2d4e5a6b7c8d",
  "query": "Казань, ТЦ Kazan Mall",
  "limit": 10
}
```

```json
{
  "requestId": "8f2b1c0a-6c1d-4a0e-9f3b-2d4e5a6b7c8d",
  "stores": [
    {
      "storeId": "kzn-014",
      "name": "Магазин — Kazan Mall",
      "address": "Казань, ул. Примерная, 1",
      "city": "Казань",
      "latitude": 55.767,
      "longitude": 49.137,
      "active": true
    }
  ],
  "nextCursor": null
}
```

### Проверка остатков

Ручка должна принимать **массив SKU и массив `storeId`** в одном запросе. Верните результат для каждой запрошенной пары, чтобы отсутствие записи нельзя было принять за нулевой остаток. Размеры массивов и максимальное число пар в запросе согласуйте при подключении.

Пример адреса ручки: `POST https://shop.example.com/api/assistant/stock/check`.

```json
{
  "requestId": "8f2b1c0a-6c1d-4a0e-9f3b-2d4e5a6b7c8d",
  "skus": ["1066109"],
  "storeIds": ["kzn-014", "kzn-027"]
}
```

```json
{
  "requestId": "8f2b1c0a-6c1d-4a0e-9f3b-2d4e5a6b7c8d",
  "items": [
    {
      "sku": "1066109",
      "storeId": "kzn-014",
      "status": "available",
      "availableQuantity": 3,
      "pickupAvailable": true,
      "updatedAt": "2026-09-24T10:15:30Z"
    },
    {
      "sku": "1066109",
      "storeId": "kzn-027",
      "status": "out_of_stock",
      "availableQuantity": 0,
      "pickupAvailable": false,
      "updatedAt": "2026-09-24T10:15:30Z"
    }
  ]
}
```

`status` должен различать как минимум `available` (можно купить), `out_of_stock` (товар продаётся в точке, но сейчас недоступен), `not_carried` (точка не продаёт этот товар) и `unknown` (достоверных данных нет). `availableQuantity` передавайте, если доступен остаток **к продаже**, а не только физический остаток на складе. `pickupAvailable` — отдельный признак возможности самовывоза: наличие на полке само по себе не гарантирует оформление заказа. `updatedAt` показывает время актуальности данных по каждой паре.

Если SKU или `storeId` неизвестен, верните явный результат для пары со статусом `unknown` либо согласованную ошибку входных данных. Не превращайте неизвестное значение или технический сбой в `out_of_stock`.

### Поиск магазинов, где товар доступен

Для вопроса «в каких магазинах есть товар» нужна серверная выборка **по SKU и географической области**: например, по коду города или по координатам и радиусу. Верните только действующие магазины с доступным товаром, их `storeId`, адрес, статус, признак самовывоза и `updatedAt`. Поддержите `limit` и `nextCursor`, чтобы число магазинов не определяло размер одного ответа. Если подходящих точек нет, верните пустой список с `200`; если данные получить не удалось — ошибку. Географическое ограничение и максимальный радиус согласуйте при подключении.

Пример адреса ручки: `POST https://shop.example.com/api/assistant/stock/find`.

```json
{
  "requestId": "8f2b1c0a-6c1d-4a0e-9f3b-2d4e5a6b7c8d",
  "sku": "1066109",
  "city": "Казань",
  "limit": 10
}
```

В ответе верните `items[]` со сведениями о магазине и наличии в том же формате, что при проверке по `storeId`, и `nextCursor` для следующей страницы. Для продолжения запроса передайте полученный курсор вместе с теми же `sku` и географическим ограничением.

```json
{
  "requestId": "8f2b1c0a-6c1d-4a0e-9f3b-2d4e5a6b7c8d",
  "items": [
    {
      "sku": "1066109",
      "storeId": "kzn-014",
      "name": "Магазин — Kazan Mall",
      "address": "Казань, ул. Примерная, 1",
      "status": "available",
      "pickupAvailable": true,
      "updatedAt": "2026-09-24T10:15:30Z"
    }
  ],
  "nextCursor": null
}
```

## Контекст диалога и правила ответа

- Передавайте канонический `storeId` выбранного пользователем магазина в [`runtimeContext.ctx.attributes.storeId`](./api_reference.md#как-заполнять-ctx) с **каждым** запросом `/shopping/generate`, если точка уже известна. Обработку этого поля согласуйте при подключении коннектора. `ctx.lng` описывает зону обслуживания для фида и поиска, а не заменяет актуальную проверку остатков в точке.
- Если пользователь назвал магазин в сообщении, сопоставьте его со справочником. При конфликте с выбранной в интерфейсе точкой уточните, о каком магазине спрашивает пользователь. Для фразы «в этом магазине» используйте подтверждённый магазин из контекста диалога.
- SKU берите из `ctx.data` на странице `PRODUCT`, из точного артикула в сообщении или из уже выбранной карточки. Если товар или вариант нельзя определить однозначно, уточните его до проверки.
- В ответе указывайте конкретный магазин и опирайтесь на результат свежей проверки. Для статуса `unknown`, устаревших данных, тайм-аута или ошибки API сообщайте, что наличие подтвердить не удалось. Не выводите наличие в точке из общего `in_stock` фида.

Перед подключением согласуйте, что именно означает `available`: наличие к продаже, возможность резервирования или доступность самовывоза. Данные о сроках новой поставки и доставке требуют дополнительных полей или отдельных API — текущий остаток сам по себе не отвечает на эти вопросы.

## Требования к подключению

- Для API остатков и API справочника: HTTPS, метод `POST` и JSON; вызовы выполняются между серверами Gravity Field и клиента. Авторизацию, адреса ручек и допустимые IP-адреса согласуйте с командой Gravity Field.
- Передавайте `requestId` в запросе и возвращайте его в ответе для диагностики. Ручки только читают данные; повторный вызов не должен менять остатки или резервировать товар.
- Согласуйте тайм-аут, целевое время ответа, нагрузку и допустимый возраст данных. Проверка выполняется во время диалога и должна укладываться в бюджет ответа ассистента.
- Разделяйте пустой достоверный результат (`200`), ошибку запроса (`400`), ошибку авторизации (`401`), временный сбой (`5xx`) и тайм-аут. При сбое ассистент не должен сообщать, что товара нет.
- Проверьте сценарии с магазином из текста и из контекста, несколькими совпавшими магазинами, отсутствием товара, неизвестным SKU, пустой географической выдачей и ошибкой API через тестовые диалоги с `trafficType: "test"`.

## Связанные материалы

[!ref icon="comment-discussion"](./shopping_assistant.md)

[!ref icon="terminal"](./api_reference.md)

[!ref icon="search"](./byoa_search_api.md)
