Содержание

Server API Документация

API для загрузки данных от внешних систем дистрибьюторов в систему Kibet.

Аутентификация

Все запросы к Server API требуют аутентификации через Bearer token:

Authorization: Bearer YOUR_DISTRIBUTOR_SERVER_TOKEN

Server token привязан к конкретному дистрибьютору и хранится в поле server_token модели Distributor.

Базовый URL

/api/server/v1/{endpoint}

Общие принципы

Для endpoints загрузки данных (POST)

  • Запросы отправляются методом POST
  • Content-Type: application/json
  • Все данные обрабатываются асинхронно через фоновые задачи
  • При успешном принятии запроса возвращается статус 200 и сообщение о принятии данных
  • При наличии существующих записей (по external_key) данные обновляются, иначе создаются новые
  • Все даты передаются в формате ISO 8601: YYYY-MM-DD

Для endpoints работы с заказами (GET, PUT)

  • Используются RESTful методы: GET для получения данных, PUT для обновления
  • Content-Type: application/json
  • Данные обрабатываются синхронно, результат возвращается немедленно
  • Заказы идентифицируются по ID базы данных (не по external_key)

1. Загрузка партнеров (Partners)

Партнеры - это юридические лица, которые являются владельцами магазинов.

Endpoint

POST /api/server/v1/partners

Параметры запроса

Поле Тип Обязательно Описание
partners array Да Массив объектов партнеров
partners[].external_key string Да Уникальный идентификатор партнера во внешней системе
partners[].name string Да Название партнера
partners[].price_list string/null Нет external_key прайс-листа, привязываемого к партнеру

Пример запроса

{
  "partners": [
    {
      "external_key": "partner_001",
      "name": "ООО Торговый Дом",
      "price_list": "retail_2024"
    },
    {
      "external_key": "partner_002",
      "name": "ИП Иванов",
      "price_list": "wholesale_2024"
    }
  ]
}

Пример ответа

{
  "message": "Partners is loaded"
}

Особенности

  • Уникальность проверяется по паре [distributor_id, external_key]
  • При совпадении обновляются поля name и price_list_id
  • Поле price_list — опциональное. Если передан external_key существующего прайс-листа, партнер привязывается к нему. Если не передан или прайс-лист не найден, поле price_list_id устанавливается в null
  • Прайс-лист должен быть предварительно загружен через endpoint /api/server/v1/price_lists
  • Привязка прайс-листа к партнеру определяет, какие цены видит магазин этого партнера в мобильном приложении при просмотре каталога

2. Загрузка единиц измерения (Unit of Measures)

Единицы измерения используются для определения в каких единицах измеряются товары (кг, шт, л и т.д.).

Endpoint

POST /api/server/v1/unit_of_measures

Параметры запроса

Поле Тип Обязательно Описание
unit_of_measures array Да Массив объектов единиц измерения
unit_of_measures[].external_key string Да Уникальный идентификатор единицы измерения во внешней системе
unit_of_measures[].name string Да Название единицы измерения

Пример запроса

{
  "unit_of_measures": [
    {
      "external_key": "kg",
      "name": "Килограмм"
    },
    {
      "external_key": "pcs",
      "name": "Штука"
    },
    {
      "external_key": "liter",
      "name": "Литр"
    }
  ]
}

Пример ответа

{
  "message": "Unit of measures is loaded"
}

Особенности

  • Уникальность проверяется по паре [distributor_id, external_key]
  • При совпадении обновляется только поле name

3. Загрузка категорий номенклатуры (Categories)

Категории используются для классификации товаров. Поддерживается иерархическая структура категорий (родитель-потомок).

Endpoint

POST /api/server/v1/categories

Параметры запроса

Поле Тип Обязательно Описание
categories array Да Массив объектов категорий
categories[].external_key string Да Уникальный идентификатор категории во внешней системе
categories[].name string Да Название категории
categories[].info string/null Нет Дополнительная информация о категории
categories[].parent string/null Нет external_key родительской категории (для иерархической структуры)

Пример запроса

{
  "categories": [
    {
      "external_key": "food",
      "name": "Продукты питания",
      "info": "Все виды продуктов питания",
      "parent": null
    },
    {
      "external_key": "dairy",
      "name": "Молочные продукты",
      "info": "Молоко, сыр, йогурты",
      "parent": "food"
    },
    {
      "external_key": "milk",
      "name": "Молоко",
      "parent": "dairy"
    },
    {
      "external_key": "beverages",
      "name": "Напитки",
      "parent": null
    }
  ]
}

Пример ответа

{
  "message": "Categories is loaded"
}

Особенности

  • Уникальность проверяется по паре [distributor_id, external_key]
  • При совпадении обновляются поля name и info
  • Поддерживается иерархическая структура: категория может иметь родительскую категорию
  • Для корневых категорий поле parent должно быть null или отсутствовать
  • Родительская категория указывается через её external_key
  • Если родительская категория не найдена, в логах появится предупреждение, но загрузка не прервется
  • При повторной загрузке категории без поля parent, её связь с родителем будет удалена
  • Загрузка происходит в три этапа:
    1. Создание/обновление всех категорий
    2. Установка связей parent-child для категорий с указанным parent
    3. Удаление связей для категорий, где parent больше не указан

Пример иерархии

Продукты питания (food)
├── Молочные продукты (dairy)
│   ├── Молоко (milk)
│   └── Сыр (cheese)
└── Мясо (meat)

Напитки (beverages)
├── Газированные (soda)
└── Соки (juice)

4. Загрузка товаров (Products)

Товары - это номенклатура дистрибьютора с привязкой к категориям и единицам измерения.

Endpoint

POST /api/server/v1/products

Параметры запроса

Поле Тип Обязательно Описание
products array Да Массив объектов товаров
products[].external_key string Да Уникальный идентификатор товара во внешней системе
products[].name string Да Название товара
products[].category string Да external_key категории товара
products[].article string/null Нет Артикул товара
products[].ean string/null Нет Штрих-код (EAN) товара
products[].info string/null Нет Дополнительная информация о товаре
products[].product_unit_of_measures array Да Массив единиц измерения для товара
products[].product_unit_of_measures[].unit_of_measure string Да external_key единицы измерения
products[].product_unit_of_measures[].quantum integer Да Квант (минимальное количество для заказа)
products[].product_unit_of_measures[].count integer Да Количество базовых единиц в данной единице измерения

Пример запроса

{
  "products": [
    {
      "external_key": "milk_32",
      "name": "Молоко 3.2%",
      "category": "dairy",
      "article": "MLK-001",
      "ean": "4607012345678",
      "info": "Пастеризованное молоко",
      "product_unit_of_measures": [
        {
          "unit_of_measure": "pcs",
          "quantum": 1,
          "count": 1
        },
        {
          "unit_of_measure": "box",
          "quantum": 12,
          "count": 12
        }
      ]
    },
    {
      "external_key": "bread_white",
      "name": "Хлеб белый",
      "category": "bakery",
      "article": "BRD-001",
      "product_unit_of_measures": [
        {
          "unit_of_measure": "pcs",
          "quantum": 1,
          "count": 1
        }
      ]
    }
  ]
}

Пример ответа

{
  "message": "Products is loaded"
}

Особенности

  • Уникальность проверяется по паре [distributor_id, external_key]
  • При совпадении обновляются все поля товара
  • Категория должна быть предварительно загружена через endpoint /api/server/v1/categories
  • Единицы измерения должны быть предварительно загружены через endpoint /api/server/v1/unit_of_measures
  • При обновлении товара все старые единицы измерения удаляются и создаются новые
  • Каждая единица измерения может использоваться для товара только один раз
  • quantum - минимальное количество для заказа в данной единице измерения (целое положительное число)
  • count - сколько базовых единиц содержится в данной единице измерения (целое положительное число)
  • Если категория или единица измерения с указанным external_key не найдены, будет ошибка валидации

Примеры использования единиц измерения

Пример 1: Молоко в бутылках и ящиках - Штука (pcs): quantum=1, count=1 - продается поштучно - Ящик (box): quantum=12, count=12 - в ящике 12 бутылок, минимум 12 штук

Пример 2: Сахар в килограммах и мешках - Килограмм (kg): quantum=1, count=1 - продается от 1 кг - Мешок (bag): quantum=1, count=50 - мешок содержит 50 кг


5. Загрузка прайс-листов (Price Lists)

Прайс-листы содержат цены на товары дистрибьютора. Каждый прайс-лист включает набор товаров с их ценами.

Endpoint

POST /api/server/v1/price_lists

Параметры запроса

Поле Тип Обязательно Описание
price_lists array Да Массив объектов прайс-листов
price_lists[].external_key string Да Уникальный идентификатор прайс-листа во внешней системе
price_lists[].name string Да Название прайс-листа
price_lists[].product_prices array Да Массив цен на товары
price_lists[].product_prices[].product string Да external_key товара
price_lists[].product_prices[].price number Да Цена товара

Пример запроса

{
  "price_lists": [
    {
      "external_key": "retail_2024",
      "name": "Розничные цены 2024",
      "product_prices": [
        {
          "product": "milk_32",
          "price": 85.50
        },
        {
          "product": "bread_white",
          "price": 50.00
        },
        {
          "product": "cheese_hard",
          "price": 650.00
        }
      ]
    },
    {
      "external_key": "wholesale_2024",
      "name": "Оптовые цены 2024",
      "product_prices": [
        {
          "product": "milk_32",
          "price": 75.00
        },
        {
          "product": "bread_white",
          "price": 42.00
        }
      ]
    }
  ]
}

Пример ответа

{
  "message": "Price lists is loaded"
}

Особенности

  • Уникальность проверяется по паре [distributor_id, external_key]
  • При совпадении обновляется название прайс-листа
  • Все товары должны быть предварительно загружены через endpoint /api/server/v1/products
  • При обновлении прайс-листа все старые цены удаляются и создаются новые
  • Если товар с указанным external_key не найден, будет ошибка валидации
  • Цена должна быть неотрицательным числом
  • Один товар может присутствовать в прайс-листе только один раз

6. Загрузка складов (Warehouses)

Склады - это места хранения товаров дистрибьютора. На складах учитываются остатки товаров.

Endpoint

POST /api/server/v1/warehouses

Параметры запроса

Поле Тип Обязательно Описание
warehouses array Да Массив объектов складов
warehouses[].external_key string Да Уникальный идентификатор склада во внешней системе
warehouses[].name string Да Название склада
warehouses[].address string/null Нет Адрес склада

Пример запроса

{
  "warehouses": [
    {
      "external_key": "warehouse_main",
      "name": "Главный склад",
      "address": "г. Москва, ул. Складская, д. 15"
    },
    {
      "external_key": "warehouse_secondary",
      "name": "Второй склад",
      "address": "г. Москва, ул. Промышленная, д. 8"
    },
    {
      "external_key": "warehouse_mobile",
      "name": "Мобильный склад"
    }
  ]
}

Пример ответа

{
  "message": "Warehouses is loaded"
}

Особенности

  • Уникальность проверяется по паре [distributor_id, external_key]
  • При совпадении обновляются поля name и address
  • Поле address является опциональным
  • Склады используются для учета остатков товаров

7. Загрузка остатков (Remainders)

Остатки - это текущее количество товаров на складах в разных единицах измерения.

Endpoint

POST /api/server/v1/remainders

Параметры запроса

Поле Тип Обязательно Описание
remainders array Да Массив объектов остатков
remainders[].warehouse string Да external_key склада
remainders[].product string Да external_key товара
remainders[].unit_of_measure string Да external_key единицы измерения
remainders[].count integer Да Количество товара на складе

Пример запроса

{
  "remainders": [
    {
      "warehouse": "warehouse_main",
      "product": "milk_32",
      "unit_of_measure": "pcs",
      "count": 150
    },
    {
      "warehouse": "warehouse_main",
      "product": "milk_32",
      "unit_of_measure": "box",
      "count": 12
    },
    {
      "warehouse": "warehouse_secondary",
      "product": "bread_white",
      "unit_of_measure": "pcs",
      "count": 200
    },
    {
      "warehouse": "warehouse_main",
      "product": "cheese_hard",
      "unit_of_measure": "kg",
      "count": 45
    }
  ]
}

Пример ответа

{
  "message": "Remainders is loaded"
}

Особенности

  • Уникальность проверяется по триплету [warehouse_id, product_id, unit_of_measure_id]
  • При совпадении обновляется только поле count
  • Склад, товар и единица измерения должны быть предварительно загружены
  • count должен быть неотрицательным целым числом
  • Можно загружать остатки одного товара на разных складах
  • Можно загружать остатки одного товара в разных единицах измерения на одном складе
  • Если склад, товар или единица измерения с указанным external_key не найдены, будет ошибка валидации

Примеры использования

Пример 1: Разные склады json { "warehouse": "warehouse_main", "product": "milk_32", "unit_of_measure": "pcs", "count": 150 } и json { "warehouse": "warehouse_secondary", "product": "milk_32", "unit_of_measure": "pcs", "count": 80 } Один и тот же товар может иметь разные остатки на разных складах.

Пример 2: Разные единицы измерения json { "warehouse": "warehouse_main", "product": "milk_32", "unit_of_measure": "pcs", "count": 150 } и json { "warehouse": "warehouse_main", "product": "milk_32", "unit_of_measure": "box", "count": 12 } На одном складе можно учитывать остатки товара в разных единицах измерения (например, 150 штук или 12 ящиков).


8. Загрузка магазинов (Distributor Shops)

Магазины - это торговые точки, которые принадлежат партнерам и получают товары от дистрибьютора.

Endpoint

POST /api/server/v1/distributor_shops

Параметры запроса

Поле Тип Обязательно Описание
shops array Да Массив объектов магазинов
shops[].external_key string Да Уникальный идентификатор магазина во внешней системе
shops[].name string Да Название магазина
shops[].address string Да Адрес магазина
shops[].partner string Да external_key партнера-владельца магазина
shops[].inn string Нет ИНН магазина
shops[].kpp string Нет КПП магазина
shops[].warehouse string Нет external_key склада, привязанного к магазину
shops[].stop boolean Нет Стоп-отгрузка (по умолчанию false)
shops[].credit_limit decimal Нет Кредитный лимит
shops[].debt decimal Нет Задолженность

Пример запроса

{
  "shops": [
    {
      "external_key": "shop_001",
      "name": "Продукты у дома",
      "address": "г. Москва, ул. Ленина, д. 10",
      "partner": "partner_001",
      "inn": "7707123456",
      "kpp": "770701001",
      "warehouse": "warehouse_main",
      "stop": false,
      "credit_limit": 50000.00,
      "debt": 12500.50
    },
    {
      "external_key": "shop_002",
      "name": "Магазин на углу",
      "address": "г. Москва, ул. Пушкина, д. 5",
      "partner": "partner_002",
      "inn": "7708654321"
    }
  ]
}

Пример ответа

{
  "message": "Shops is loaded"
}

Особенности

  • Уникальность проверяется по паре [distributor_id, external_key]
  • Партнер должен быть предварительно загружен через endpoint /api/server/v1/partners
  • При указании external_key партнера система автоматически находит соответствующего партнера
  • Если указан warehouse, склад должен быть предварительно загружен через endpoint /api/server/v1/warehouses
  • Если warehouse указан, но склад не найден — возвращается ошибка Not found warehouses with external_key: ...

9. Загрузка накладных (Invoices)

Накладные содержат информацию о товарах, которые должны быть доставлены в магазины.

Endpoint

POST /api/server/v1/invoices

Параметры запроса

Поле Тип Обязательно Описание
invoices array Да Массив объектов накладных
invoices[].external_key string Да Уникальный идентификатор накладной во внешней системе
invoices[].shop string Да external_key магазина-получателя
invoices[].number string Да Номер накладной
invoices[].date string Да Дата накладной (формат: YYYY-MM-DD)
invoices[].shipping_date string/null Нет Дата отгрузки (формат: YYYY-MM-DD)
invoices[].invoice_items array Да Массив товарных позиций накладной
invoices[].invoice_items[].number number Да Номер позиции в накладной
invoices[].invoice_items[].product string Да external_key товара
invoices[].invoice_items[].unit_of_measure string Да external_key единицы измерения
invoices[].invoice_items[].count number Да Количество товара
invoices[].invoice_items[].price number Да Цена за единицу
invoices[].invoice_items[].amount number Да Общая сумма позиции

Пример запроса

{
  "invoices": [
    {
      "external_key": "invoice_001",
      "shop": "shop_001",
      "number": "НАК-2024-001",
      "date": "2024-12-05",
      "shipping_date": "2024-12-06",
      "invoice_items": [
        {
          "number": 1,
          "product": "milk_32",
          "unit_of_measure": "pcs",
          "count": 50,
          "price": 75.50,
          "amount": 3775.00
        },
        {
          "number": 2,
          "product": "bread_white",
          "unit_of_measure": "pcs",
          "count": 100,
          "price": 45.00,
          "amount": 4500.00
        }
      ]
    }
  ]
}

Пример ответа

{
  "message": "Invoices is loaded"
}

Особенности

  • Уникальность накладной проверяется по паре [shop_id, external_key]
  • Магазин должен быть предварительно загружен через endpoint /api/server/v1/distributor_shops
  • Товар должен быть предварительно загружен через endpoint /api/server/v1/products
  • Единица измерения должна быть предварительно загружена через endpoint /api/server/v1/unit_of_measures
  • Если магазин, товар или единица измерения с указанным external_key не найдены, будет ошибка валидации и уведомление на email дистрибьютора
  • Товарные позиции (invoice_items) создаются/обновляются вместе с накладной
  • При загрузке накладной система автоматически устанавливает FK-связь товарных позиций с записями в справочниках товаров и единиц измерения

10. Загрузка доставок (Deliveries)

Доставки связывают накладные с конкретными водителями и магазинами для организации логистики.

Endpoint

POST /api/server/v1/deliveries

Параметры запроса

Поле Тип Обязательно Описание
deliveries array Да Массив объектов доставок
deliveries[].external_key string Да Уникальный идентификатор доставки во внешней системе
deliveries[].shop string Да external_key магазина-получателя
deliveries[].number string Да Номер доставки
deliveries[].date string Да Дата доставки (формат: YYYY-MM-DD)
deliveries[].driver string Да Имя водителя
deliveries[].invoices array Да Массив external_key накладных для доставки

Пример запроса

{
  "deliveries": [
    {
      "external_key": "delivery_001",
      "shop": "shop_001",
      "number": "ДОС-2024-001",
      "date": "2024-12-06",
      "driver": "Иванов Иван Иванович",
      "invoices": [
        "invoice_001",
        "invoice_002"
      ]
    },
    {
      "external_key": "delivery_002",
      "shop": "shop_002",
      "number": "ДОС-2024-002",
      "date": "2024-12-06",
      "driver": "Петров Петр Петрович",
      "invoices": [
        "invoice_003"
      ]
    }
  ]
}

Пример ответа

{
  "message": "Deliveries is loaded"
}

Особенности

  • Уникальность проверяется по паре [distributor_id, external_key]
  • Магазин должен быть предварительно загружен через endpoint /api/server/v1/distributor_shops
  • Все накладные в массиве invoices должны быть предварительно загружены через endpoint /api/server/v1/invoices
  • Если магазин или накладные с указанными external_key не найдены, будет ошибка валидации
  • Водитель создается автоматически, если не существует

11. Загрузка фотографий товаров (Product Photos)

Фотографии товаров позволяют загружать изображения для номенклатуры дистрибьютора. Фотографии загружаются по URL и сохраняются в системе через Active Storage на S3.

Endpoint

POST /api/server/v1/product_photos

Параметры запроса

Поле Тип Обязательно Описание
product_photos array Да Массив объектов фотографий товаров
product_photos[].product_external_key string Да external_key товара, к которому привязывается фотография
product_photos[].external_key string Да Уникальный идентификатор фотографии во внешней системе
product_photos[].photo_url string Да URL для загрузки фотографии

Пример запроса

{
  "product_photos": [
    {
      "product_external_key": "milk_32",
      "external_key": "photo_milk_32_1",
      "photo_url": "https://example.com/images/milk_front.jpg"
    },
    {
      "product_external_key": "milk_32",
      "external_key": "photo_milk_32_2",
      "photo_url": "https://example.com/images/milk_back.jpg"
    },
    {
      "product_external_key": "bread_white",
      "external_key": "photo_bread_1",
      "photo_url": "https://example.com/images/bread.jpg"
    }
  ]
}

Пример ответа

{
  "message": "Product photos is loaded"
}

Особенности

  • Уникальность проверяется по паре [product_id, external_key]
  • Товар должен быть предварительно загружен через endpoint /api/server/v1/products
  • Если товар с указанным external_key не найден, будет ошибка валидации
  • При совпадении external_key для одного товара фотография обновляется
  • Фотографии загружаются асинхронно через фоновое задание
  • Система скачивает изображение по указанному URL и сохраняет его в S3 (Yandex Cloud)
  • Поддерживаются стандартные форматы изображений (JPEG, PNG, GIF и др.)
  • Один товар может иметь несколько фотографий с разными external_key
  • Если загрузка фотографии по URL не удалась, ошибка логируется, но обработка продолжается

12. Получение заказов (Orders)

Endpoints для работы с заказами, созданными покупателями через мобильное приложение. В отличие от других Server API endpoints, которые используют POST для загрузки данных, endpoints для заказов используют RESTful подход для чтения и обновления.

12.1. Получение списка незабранных заказов

Возвращает список заказов, которые ещё не были экспортированы во внешнюю систему (где exported_at IS NULL).

Endpoint

GET /api/server/v1/orders

Параметры запроса

Без параметров. Возвращает все незабранные заказы текущего дистрибьютора.

Пример запроса

curl -X GET http://localhost:3000/api/server/v1/orders \
  -H "Authorization: Bearer YOUR_SERVER_TOKEN"

Пример ответа (200 OK)

{
  "orders": [
    {
      "id": 1,
      "distributor_shop": "shop_001",
      "comment": "Срочная доставка",
      "shipping_date": "2026-01-20T10:00:00.000Z",
      "status": "new",
      "created_at": "2026-01-14T12:00:00.000Z",
      "updated_at": "2026-01-14T12:00:00.000Z",
      "order_items": [
        {
          "product": "PROD-123",
          "unit_of_measure": "шт",
          "count": 10,
          "price": "150.50"
        },
        {
          "product": "PROD-456",
          "unit_of_measure": "кг",
          "count": 5,
          "price": "200.00"
        }
      ]
    },
    {
      "id": 2,
      "distributor_shop": "shop_002",
      "comment": null,
      "shipping_date": "2026-01-21T14:00:00.000Z",
      "status": "new",
      "created_at": "2026-01-14T15:30:00.000Z",
      "updated_at": "2026-01-14T15:30:00.000Z",
      "order_items": [
        {
          "product": "PROD-789",
          "unit_of_measure": "л",
          "count": 20,
          "price": "85.00"
        }
      ]
    }
  ]
}

Особенности

  • Возвращаются только заказы, где exported_at IS NULL (ещё не экспортированные)
  • Заказы отсортированы по дате создания (от старых к новым)
  • Для каждого заказа включается полная информация о товарных позициях
  • В товарных позициях возвращается product - external_key товара (для связи с внешней системой)
  • Единица измерения возвращается как unit_of_measure (название, например "шт", "кг")
  • Поле distributor_shop содержит external_key магазина дистрибьютора
  • Информация о магазине покупателя не возвращается (внутренняя информация системы)
  • После экспорта заказа он больше не будет появляться в этом списке
  • Используется eager loading для предотвращения N+1 запросов

Поля заказа

Поле Тип Описание
id integer ID заказа в системе
distributor_shop string External key магазина дистрибьютора
comment string/null Комментарий к заказу
shipping_date datetime/null Желаемая дата доставки
status string Статус заказа: "new", "in_progress", "shipped"
created_at datetime Дата создания заказа
updated_at datetime Дата последнего обновления
order_items array Массив товарных позиций

Поля товарной позиции

Поле Тип Описание
product string External key товара для связи с вашей системой
unit_of_measure string Название единицы измерения (шт, кг, л и т.д.)
count integer Количество товара
price decimal Цена за единицу товара

12.2. Обновление заказа

Обновляет заказ: при первом вызове помечает его как экспортированный (устанавливает exported_at), а также позволяет обновить статус заказа в любой момент. При смене статуса сотрудникам магазина автоматически отправляется уведомление.

Endpoint

PUT /api/server/v1/orders/:id

Параметры запроса

Параметр Тип Обязательный Описание
id integer Да ID заказа в URL (path parameter)
status string Нет Новый статус заказа: "new", "in_progress", "shipped"

Тело запроса в формате JSON (опционально):

{
  "status": "in_progress"
}

Пример запроса — пометить как экспортированный

curl -X PUT http://localhost:3000/api/server/v1/orders/1 \
  -H "Authorization: Bearer YOUR_SERVER_TOKEN"

Пример запроса — обновить статус

curl -X PUT http://localhost:3000/api/server/v1/orders/1 \
  -H "Authorization: Bearer YOUR_SERVER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"status": "shipped"}'

Пример ответа (200 OK)

{
  "message": "Order marked as exported",
  "order": {
    "id": 1,
    "status": "shipped",
    "exported_at": "2026-01-14T15:30:45.123Z"
  }
}

Возможные ошибки

404 Not Found - заказ не найден или принадлежит другому дистрибьютору:

{
  "error": "Order not found"
}

422 Unprocessable Entity - передан неизвестный статус:

{
  "error": "Invalid status: unknown_status"
}

401 Unauthorized - неверный токен аутентификации:

{
  "errors": "Unauthorized"
}

Особенности

  • exported_at устанавливается только при первом вызове; повторные вызовы не перезаписывают это поле
  • Статус можно обновлять многократно, в том числе после того, как заказ уже помечен как экспортированный
  • При изменении статуса сотрудники магазина получают push-уведомление
  • Можно обновлять только заказы своего дистрибьютора
  • Заказ идентифицируется по ID базы данных (не по external_key)

Рекомендуемый workflow для внешней системы

  1. Периодический опрос: Раз в N минут/часов вызывать GET /api/server/v1/orders для получения новых заказов
  2. Обработка заказов: Импортировать полученные заказы в свою систему
  3. Подтверждение экспорта: Для каждого успешно импортированного заказа вызвать PUT /api/server/v1/orders/:id
  4. Обновление статуса: При изменении статуса в своей системе вызывать PUT /api/server/v1/orders/:id с параметром status
  5. Обработка ошибок: При ошибке импорта не вызывать PUT, заказ останется в списке для повторной попытки

Порядок загрузки данных

Для корректной работы системы рекомендуется загружать данные в следующем порядке:

  1. Unit of Measures - единицы измерения (независимы)
  2. Categories - категории номенклатуры (независимы, поддерживают иерархию)
  3. Warehouses - склады (независимы)
  4. Products - товары (требуют существующих категорий и единиц измерения)
  5. Product Photos - фотографии товаров (требуют существующих товаров)
  6. Price Lists - прайс-листы (требуют существующих товаров)
  7. Partners - партнеры (могут загружаться без price_list; для привязки прайс-листа требуют существующих прайс-листов)
  8. Remainders - остатки товаров на складах (требуют существующих складов, товаров и единиц измерения)
  9. Distributor Shops - магазины (требуют существующих партнеров)
  10. Invoices - накладные (требуют существующих магазинов, товаров и единиц измерения)
  11. Deliveries - доставки (требуют существующих магазинов и накладных)

Примечание: Partners можно загрузить без price_list на первом этапе (до загрузки Price Lists), а затем повторно загрузить с price_list после того, как прайс-листы будут готовы — запись обновится.

Обработка ошибок

Ошибка аутентификации

{
  "errors": "Unauthorized"
}

Статус: 401

Ошибка валидации JSON

При невалидной структуре данных будет возвращена ошибка с описанием проблемы:

{
  "error": "JSON validation failed: The property '#/shops/0' did not contain a required property of 'name'"
}

Ошибка отсутствия связанных данных

Если указаны external_key несуществующих сущностей:

{
  "error": "Not found shops with external_key: shop_999, shop_888"
}

При загрузке накладных возможны комбинированные ошибки (несколько типов объединяются через ;):

{
  "error": "Not found products with external_key: milk_32, bread_white; Not found unit_of_measures with external_key: box"
}

Асинхронная обработка

Endpoints для загрузки данных (POST) обрабатываются асинхронно через фоновые задачи:

  • Exchange::PartnerExchangeJob - обработка партнеров
  • Exchange::UnitOfMeasureExchangeJob - обработка единиц измерения
  • Exchange::CategoryExchangeJob - обработка категорий номенклатуры
  • Exchange::ProductExchangeJob - обработка товаров
  • Exchange::PriceListExchangeJob - обработка прайс-листов
  • Exchange::WarehouseExchangeJob - обработка складов
  • Exchange::DistributorShopExchangeJob - обработка магазинов
  • Exchange::InvoiceExchangeJob - обработка накладных
  • Exchange::DeliveryExchangeJob - обработка доставок

Это означает, что: - Успешный ответ от API означает только принятие данных в очередь - Фактическая обработка происходит в фоне через систему фоновых задач - Ошибки обработки логируются в систему

Важно: Endpoints для работы с заказами (GET /api/server/v1/orders и PUT /api/server/v1/orders/:id) работают синхронно и возвращают результат немедленно, без использования фоновых задач.

Примеры использования

cURL

# Загрузка партнеров с привязкой прайс-листа
curl -X POST http://localhost:3000/api/server/v1/partners \
  -H "Authorization: Bearer YOUR_SERVER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "partners": [
      {
        "external_key": "partner_001",
        "name": "ООО Торговый Дом",
        "price_list": "retail_2024"
      }
    ]
  }'

# Загрузка магазинов
curl -X POST http://localhost:3000/api/server/v1/distributor_shops \
  -H "Authorization: Bearer YOUR_SERVER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "shops": [
      {
        "external_key": "shop_001",
        "name": "Продукты у дома",
        "address": "г. Москва, ул. Ленина, д. 10",
        "partner": "partner_001"
      }
    ]
  }'

Ruby

require 'net/http'
require 'json'

uri = URI('http://localhost:3000/api/server/v1/partners')
http = Net::HTTP.new(uri.host, uri.port)
request = Net::HTTP::Post.new(uri.path, {
  'Authorization' => 'Bearer YOUR_SERVER_TOKEN',
  'Content-Type' => 'application/json'
})
request.body = {
  partners: [
    { external_key: 'partner_001', name: 'ООО Торговый Дом' }
  ]
}.to_json

response = http.request(request)
puts response.body

Python

import requests
import json

url = 'http://localhost:3000/api/server/v1/partners'
headers = {
    'Authorization': 'Bearer YOUR_SERVER_TOKEN',
    'Content-Type': 'application/json'
}
data = {
    'partners': [
        {
            'external_key': 'partner_001',
            'name': 'ООО Торговый Дом'
        }
    ]
}

response = requests.post(url, headers=headers, data=json.dumps(data))
print(response.json())