Перейти к содержимому

Интеграция каталога с 1С (REST JSON API)

Общее описание

Этот API позволяет 1С управлять каталогом наличия на сайте:

  • категории;
  • товары (карточки товаров, атрибуты, фото);
  • остатки (через product_supplies).

Источник данных — 1С, сайт только принимает изменения и показывает их пользователям.

Все методы расположены по префиксу: /api/1c/* (Laravel автоматически добавляет /api).


Авторизация и безопасность

Включение интеграции и настройки

Настройки находятся в админке:

  • Страница: Настройка сайта → таб 1С интеграция.
  • Хранение настроек — класс App\Settings\GeneralSettings.

Доступные опции:

  • Включить интеграцию с 1С (onec_enabled).
  • Склад для остатков 1С (onec_storage_id) — storages.id.
  • Точка выдачи для остатков 1С (onec_pick_up_point_id) — pick_up_points.id.
  • Разрешённые IP для запросов 1С (onec_allowed_ips) — список IP; если пусто, IP не ограничиваются.
  • API токен 1С (onec_token) — случайная строка, используемая в Authorization-заголовке.

Токен можно:

  • Сгенерировать/перевыпустить кнопкой «Сгенерировать / перевыпустить токен».
  • При перевыпуске старый токен становится недействительным.

Как работает проверка на бэкенде

Middleware: App\Http\Middleware\VerifyOnecToken, алиас onec.token.

Проверки:

  1. Интеграция включена

    • Если GeneralSettings->onec_enabled === false:
      • Ответ: 503 Service Unavailable
      • Тело: { "message": "1C integration is disabled." }
  2. Наличие и валидность токена

    • Берётся значение GeneralSettings->onec_token.

    • 1С должна отправлять заголовок:

      http
      Authorization: Bearer {onec_token}
    • Ошибки:

      • Нет заголовка или неверный формат:
        • 401 Unauthorized, { "message": "Missing or invalid Authorization header." }
      • Токен не совпадает:
        • 401 Unauthorized, { "message": "Invalid 1C API token." }
  3. Проверка IP (опционально)

    • Берётся массив GeneralSettings->onec_allowed_ips.
    • Если массив не пустой и IP запроса не содержится в списке:
      • 403 Forbidden, { "message": "Access from this IP address is not allowed for 1C API." }

Базовый health-check

GET /api/1c/ping

Проверяет:

  • доступность API;
  • корректность токена и (опционально) IP.

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

http
GET /api/1c/ping HTTP/1.1
Host: example.com
Authorization: Bearer {TOKEN_IZ_NASTROEK}

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

json
{
  "status": "ok"
}

Категории (ProductCategory)

Структура модели

  • Таблица: product_categories
  • Важные поля:
    • id — внутренний ID сайта;
    • onec_id — внешний ID категории в 1С;
    • parent_id — связь с родителем;
    • name, description, slug, is_active, sort.
    • медиа — коллекция product_categories (Spatie Media Library).

1. Batch upsert категорий

Метод: POST /api/1c/categories/batch-upsert

Назначение: создать/обновить дерево категорий по onec_id.

Поддерживаются два формата тела:

json
[
  {
    "id_1c": "cat-1",
    "parent_id_1c": null,
    "name": "Тормозная система",
    "description": "Описание категории",
    "slug": "tormoznaya-sistema",
    "sort_order": 10,
    "is_active": true,
    "image_url": "https://files.example.com/cat-1.jpg"
  }
]

или

json
{
  "categories": [
    {
      "id_1c": "cat-1",
      "parent_id_1c": null,
      "name": "Тормозная система"
    }
  ]
}

Валидация:

  • categoriesrequired|array (если тело — объект).
  • categories.*.id_1crequired|string|max:191
  • categories.*.parent_id_1cnullable|string|max:191
  • categories.*.namerequired|string|max:191
  • categories.*.descriptionnullable|string
  • categories.*.slugnullable|string|max:191
  • categories.*.sort_ordernullable|integer
  • categories.*.is_activenullable|boolean
  • categories.*.image_urlnullable|string

Поведение:

  • По id_1c ищется/создаётся запись в product_categories (поле onec_id).
  • Обновляются:
    • name, description, is_active (по умолчанию true), sort.
    • slug — если передан, иначе генерируется уникальный slug из name.
  • После создания/обновления всех категорий во втором проходе:
    • выставляются parent_id по parent_id_1c, если родитель найден.
  • Если задан image_url:
    • старая медиа-коллекция product_categories очищается;
    • с URL скачивается и прикрепляется новое изображение.

Пример успешного ответа:

json
{
  "processed": 3,
  "created": 2,
  "updated": 1,
  "errors": []
}

Пример частичной ошибки:

json
{
  "processed": 3,
  "created": 2,
  "updated": 1,
  "errors": [
    {
      "id_1c": "cat-3",
      "message": "Parent category with id_1c=cat-999 not found."
    }
  ]
}

2. Деактивация категорий

Метод: POST /api/1c/categories/delete

Тело:

json
{
  "ids_1c": ["cat-1", "cat-2"]
}

Поведение:

  • Для всех найденных категорий по onec_id:
    • is_active = false;
    • без физического удаления.

Ответ:

json
{
  "requested": 2,
  "deactivated": 2,
  "not_found": []
}

Товары (Product)

Структура модели

  • Таблица: products
  • Важные поля:
    • id — внутренний ID;
    • onec_id — внешний ID товара в 1С;
    • name, brand, article, description, slug, is_active, sort;
    • связи:
      • categories (pivot products_categories);
      • supplies (ProductSupply) — остатки/цены;
      • attributeValues (ProductAttributeValue) — атрибуты.
    • медиа — коллекция products.

1. Batch upsert товаров

Метод: POST /api/1c/products/batch-upsert

Назначение: создать/обновить товары, привязать к категориям, атрибутам и фото.

Тело (формат 1 — массив):

json
[
  {
    "id_1c": "prod-1",
    "name": "Тормозные колодки",
    "brand": "BOSCH",
    "article": "ABC-123",
    "category_id_1c": "cat-1",
    "description_short": "Краткое описание",
    "description_full": "Полное описание...",
    "slug": "tormoznye-kolodki",
    "sort_order": 10,
    "is_active": true,
    "attributes": [
      { "name": "Цвет", "value": "Черный", "type": "text" },
      { "name": "Страна", "value": "Германия" }
    ],
    "images": [
      { "url": "https://files.example.com/prod-1-1.jpg", "sort_order": 1, "is_main": true },
      { "url": "https://files.example.com/prod-1-2.jpg", "sort_order": 2, "is_main": false }
    ]
  }
]

или формат 2 — объект:

json
{
  "products": [
    {
      "id_1c": "prod-1",
      "name": "Тормозные колодки"
    }
  ]
}

Валидация:

  • productsrequired|array (если тело — объект).
  • products.*.id_1crequired|string|max:191
  • products.*.namerequired|string|max:191
  • products.*.brandnullable|string|max:191
  • products.*.articlenullable|string|max:191
  • products.*.category_id_1cnullable|string|max:191
  • products.*.description_shortnullable|string
  • products.*.description_fullnullable|string
  • products.*.slugnullable|string|max:191
  • products.*.sort_ordernullable|integer
  • products.*.is_activenullable|boolean
  • products.*.attributesnullable|array
    • attributes.*.namerequired_with:attributes|string|max:191
    • attributes.*.valuenullable|string
    • attributes.*.typenullable|string|max:50
  • products.*.imagesnullable|array
    • images.*.urlrequired_with:images|string
    • images.*.sort_ordernullable|integer
    • images.*.is_mainnullable|boolean

Поведение:

  • По id_1c ищется/создаётся Product (поле onec_id).
  • Обновляются:
    • name, brand, article;
    • description:
      • если есть description_full — берётся оно;
      • иначе, если описания нет и есть description_short — берётся оно;
    • is_active (по умолчанию true);
    • sort — из sort_order или 0.
  • slug:
    • если передан — используем;
    • иначе — генерируем уникальный slug из name + article.
  • Категория:
    • если есть category_id_1c, ищем ProductCategory по onec_id;
    • при успехе — syncWithoutDetaching связи в products_categories.

Атрибуты

Простой вариант (inline):

  • 1С присылает:

    json
    "attributes": [
      { "name": "Цвет", "value": "Черный", "type": "text" },
      { "name": "Страна", "value": "Германия" }
    ]
  • Бэкенд:

    • в таблице attributes (модель Attribute) делает firstOrCreate по name;
    • в таблице product_attribute_values (ProductAttributeValue) делает updateOrCreate по product_id + attribute_id + type, поле value — строкой.

Таким образом, можно использовать уже существующую систему атрибутов и фильтрации.

Фото

  • Для каждого товара используется коллекция медиа products.
  • При каждом запросе:
    • старая коллекция очищается: clearMediaCollection('products');
    • для каждого изображения вызывается addMediaFromUrl(url) с custom properties:
      • sort_order
      • is_main

Пример успешного ответа:

json
{
  "processed": 5,
  "created": 3,
  "updated": 2,
  "errors": []
}

Пример частичной ошибки (нет категории):

json
{
  "processed": 1,
  "created": 0,
  "updated": 1,
  "errors": [
    {
      "id_1c": "prod-1",
      "message": "Category with id_1c=cat-999 not found."
    }
  ]
}

2. Деактивация товаров

Метод: POST /api/1c/products/delete

Тело:

json
{
  "ids_1c": ["prod-1", "prod-2"]
}

Поведение:

  • Для всех найденных товаров:
    • is_active = false;
    • вызывается $product->delete() — мягкое удаление (SoftDeletes).

Ответ:

json
{
  "requested": 2,
  "deactivated": 2,
  "not_found": []
}

Остатки (ProductSupply)

Структура

  • Таблица: product_supplies
  • Важные поля:
    • product_id, pick_up_point_id, storage_id;
    • purchase_price, selling_price, old_price;
    • quantity, reserved, shipped;
    • supply_date, delivery_days.

Batch update остатков

Метод: POST /api/1c/stocks/batch-update

Назначение: выставление актуальных количеств по товарам от 1С.

  • Привязка к складу/точке выдачи:
    • Используются настройки onec_storage_id и onec_pick_up_point_id из GeneralSettings.

Тело (массив):

json
[
  {
    "product_id_1c": "prod-1",
    "quantity": 15,
    "supply_date": "2026-02-10",
    "delivery_days": 2
  },
  {
    "product_id_1c": "prod-2",
    "quantity": 0
  }
]

или объект:

json
{
  "stocks": [
    {
      "product_id_1c": "prod-1",
      "quantity": 15
    }
  ]
}

Валидация:

  • stocksrequired|array (если тело — объект).
  • stocks.*.product_id_1crequired|string|max:191
  • stocks.*.quantityrequired|integer|min:0
  • stocks.*.supply_datenullable|date
  • stocks.*.delivery_daysnullable|integer|min:0

Предварительные проверки:

  • Если GeneralSettings->onec_enabled === false:

    json
    {
      "message": "1C integration is disabled."
    }

    с кодом 503.

  • Если не настроены onec_storage_id или onec_pick_up_point_id:

    json
    {
      "message": "Default storage and pick-up point for 1C stocks must be configured."
    }

    с кодом 500.

Поведение:

  • Для каждой записи:
    • ищется Product по onec_id = product_id_1c;
    • если не найден — в errors добавляется сообщение и запись пропускается;
    • по комбинации product_id + pick_up_point_id + storage_id:
      • делается firstOrNew в product_supplies;
      • устанавливается quantity в указанное значение;
      • supply_date берётся из запроса или, если пусто, из существующего значения или now();
      • delivery_days — из запроса или из старого значения или 0;
      • поля reserved и shipped не изменяются.

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

json
{
  "processed": 2,
  "updated": 2,
  "errors": []
}

Пример ответа с ошибкой:

json
{
  "processed": 2,
  "updated": 1,
  "errors": [
    {
      "product_id_1c": "prod-999",
      "message": "Product not found for given 1C ID."
    }
  ]
}

Типичный сценарий обмена

  1. Настроить интеграцию в админке

    • Включить 1С интеграция.
    • Выбрать склад и точку выдачи.
    • Сгенерировать токен.
    • (Опционально) задать список разрешённых IP.
  2. Проверить доступность

    • 1С вызывает GET /api/1c/ping с заголовком Authorization: Bearer {token}.
  3. Загрузить/обновить категории

    • POST /api/1c/categories/batch-upsert.
  4. Загрузить/обновить товары

    • POST /api/1c/products/batch-upsert.
  5. Обновлять остатки

    • Периодически вызывать POST /api/1c/stocks/batch-update.
  6. Удалять/скрывать неактуальные данные

    • POST /api/1c/categories/delete
    • POST /api/1c/products/delete

Быстрые примеры запросов (curl)

Пример: ping

bash
curl -X GET "https://example.com/api/1c/ping" \
  -H "Authorization: Bearer YOUR_TOKEN_HERE"

Пример: загрузка одной категории

bash
curl -X POST "https://example.com/api/1c/categories/batch-upsert" \
  -H "Authorization: Bearer YOUR_TOKEN_HERE" \
  -H "Content-Type: application/json" \
  -d '[
    {
      "id_1c": "cat-1",
      "name": "Тормозная система",
      "parent_id_1c": null,
      "is_active": true
    }
  ]'

Пример: загрузка одного товара с фото и атрибутами

bash
curl -X POST "https://example.com/api/1c/products/batch-upsert" \
  -H "Authorization: Bearer YOUR_TOKEN_HERE" \
  -H "Content-Type: application/json" \
  -d '[
    {
      "id_1c": "prod-1",
      "name": "Тормозные колодки",
      "brand": "BOSCH",
      "article": "ABC-123",
      "category_id_1c": "cat-1",
      "is_active": true,
      "attributes": [
        { "name": "Цвет", "value": "Черный" },
        { "name": "Страна", "value": "Германия" }
      ],
      "images": [
        { "url": "https://files.example.com/prod-1-1.jpg", "sort_order": 1, "is_main": true }
      ]
    }
  ]'

Пример: обновление остатков

bash
curl -X POST "https://example.com/api/1c/stocks/batch-update" \
  -H "Authorization: Bearer YOUR_TOKEN_HERE" \
  -H "Content-Type: application/json" \
  -d '[
    { "product_id_1c": "prod-1", "quantity": 10 },
    { "product_id_1c": "prod-2", "quantity": 0 }
  ]'

База знаний для EMS-платформы PlatParts.