Покупець замовляє товар, якого немає у постачальника — класична проблема дропшипінгу. За статистикою, до 15% замовлень зривається через застарілі залишки, а в пікові сезони — до 30%. Ми стикалися з проектами, де магазин втрачав 20% виручки. Наприклад, один із клієнтів з п'ятьма постачальниками замовив налаштування синхронізації і через два тижні скоротив скасування замовлень на 90%. Щоб цього уникнути, залишки на сайті повинні синхронізуватися з реальною наявністю у постачальників. Бітрікс зберігає залишки на складах у таблиці b_catalog_store_product, наше завдання — тримати цю таблицю в актуальному стані.
Ми пропонуємо налаштувати синхронізацію залишків під ключ: від аналізу джерел даних до моніторингу та алертингу. Строк реалізації — від 5 до 14 робочих днів залежно від кількості постачальників та складності форматів. Замовте налаштування у нас — ваші клієнти перестануть стикатися з несподівано відсутніми товарами.
Як влаштована архітектура синхронізації?
Вибір схеми залежить від технічних можливостей постачальника та вимог до актуальності даних. Розглянемо три варіанти:
Push-вебхук від постачальника — постачальник сам повідомляє при зміні залишку. Це в 3 рази швидше pull-фідів, але вимагає від постачальника технічної можливості надсилати POST-запити на наш ендпоінт.
Pull-фід за розкладом — агент Бітрікс завантажує файл (XML/CSV/JSON) з FTP або HTTP постачальника кожні N хвилин. Це найпоширеніший варіант, підходить для 80% проектів.
Прямий доступ до API постачальника — ми запитуємо залишок у момент перегляду товару або додавання в кошик. Навантажує API постачальника, але дає дані в реальному часі.
Pull-синхронізація через фід
Агент Бітрікс запускається за розкладом і оновлює залишки. Нижче приклад класу, який ми використовуємо в продакшені:
// /local/lib/Dropshipping/StockSync/FeedProcessor.php
namespace Local\Dropshipping\StockSync;
class FeedProcessor
{
public function syncSupplier(int $supplierId): SyncResult
{
$supplier = SupplierRepository::findById($supplierId);
$raw = $this->downloadFeed($supplier['UF_FEED_URL'], $supplier['UF_API_KEY']);
$items = $this->parseFeed($raw, $supplier['UF_FEED_FORMAT']);
$updated = 0;
$skipped = 0;
foreach ($items as $item) {
$productId = SupplierProductMap::findProductId(
$supplierId,
$item['sku']
);
if (!$productId) {
$skipped++;
continue;
}
$this->updateStock($productId, $supplier['UF_STORE_ID'], (int)$item['quantity']);
$updated++;
}
return new SyncResult($supplierId, $updated, $skipped);
}
private function updateStock(int $productId, int $storeId, int $quantity): void
{
$existing = \CCatalogStoreProduct::GetList(
[],
['PRODUCT_ID' => $productId, 'STORE_ID' => $storeId]
)->Fetch();
if ($existing) {
\CCatalogStoreProduct::Update($existing['ID'], ['AMOUNT' => $quantity]);
} else {
\CCatalogStoreProduct::Add([
'PRODUCT_ID' => $productId,
'STORE_ID' => $storeId,
'AMOUNT' => $quantity,
]);
}
// Оновлюємо загальний залишок у b_catalog_product
$total = $this->getTotalStock($productId);
\CCatalogProduct::Update($productId, ['QUANTITY' => $total]);
}
}
Чому ми використовуємо інтерфейс парсерів?
Постачальники передають дані в різних форматах. Ми реалізуємо FeedParserInterface та конкретні парсери під кожен формат — це спрощує додавання нових постачальників, наприклад, CSV або XML. Інтерфейсний підхід скорочує час інтеграції нового постачальника на 40% порівняно з монолітним парсером.
interface FeedParserInterface
{
public function parse(string $raw): array; // повертає [{sku, quantity}, ...]
}
class CsvFeedParser implements FeedParserInterface
{
public function parse(string $raw): array
{
$lines = str_getcsv($raw, "\n");
$result = [];
// Визначаємо роздільник: ; або ,
$delimiter = str_contains($lines[0], ';') ? ';' : ',';
foreach (array_slice($lines, 1) as $line) { // пропускаємо заголовок
[$sku, $qty] = str_getcsv($line, $delimiter);
if ($sku && is_numeric($qty)) {
$result[] = ['sku' => trim($sku), 'quantity' => (int)$qty];
}
}
return $result;
}
}
class XmlFeedParser implements FeedParserInterface
{
public function parse(string $raw): array
{
$xml = simplexml_load_string($raw, 'SimpleXMLElement', LIBXML_NOCDATA);
$result = [];
foreach ($xml->offer as $offer) {
$result[] = [
'sku' => (string)$offer->vendorCode,
'quantity' => (int)$offer->stock,
];
}
return $result;
}
}
Push-синхронізація (вебхук від постачальника)
Якщо постачальник може надсилати POST-запити, ми реалізуємо ендпоінт з аутентифікацією за ключем:
// /local/ajax/supplier/stock-webhook.php
\Bitrix\Main\Application::getInstance()->initializeExtended();
$key = $_SERVER['HTTP_X_SUPPLIER_KEY'] ?? '';
$supplierId = \Local\Dropshipping\SupplierAuth::validate($key);
if (!$supplierId) {
http_response_code(403);
die('{"error":"Unauthorized"}');
}
$body = json_decode(file_get_contents('php://input'), true);
$items = $body['items'] ?? [];
$processor = new \Local\Dropshipping\StockSync\FeedProcessor();
$result = $processor->syncItems($supplierId, $items);
echo json_encode(['updated' => $result->updated, 'skipped' => $result->skipped]);
Реєстрація агента синхронізації
Приклад реєстрації агента
// Агент запускається кожні 30 хвилин для кожного постачальника
\CAgent::Add([
'NAME' => '\Local\Dropshipping\StockSync\SyncAgent::run();',
'MODULE_ID' => 'local',
'PERIOD' => 1800, // 30 хвилин
'NEXT_EXEC' => date('d.m.Y H:i:s', time() + 1800),
'ACTIVE' => 'Y',
]);
Як обробляти товари з нульовим залишком?
При обнуленні залишку ми не просто ставимо QUANTITY = 0, а знімаємо товар з продажу:
private function handleZeroStock(int $productId): void
{
// Встановлюємо статус «Немає в наявності» через b_catalog_product
\CCatalogProduct::Update($productId, [
'QUANTITY' => 0,
'QUANTITY_TRACE' => 'Y',
'CAN_BUY_ZERO' => 'N',
'NEGATIVE_AMOUNT_TRACE' => 'N',
]);
// Скидаємо кеш картки товару
\Bitrix\Main\Data\TaggedCache::clearByTag('iblock_id_' . CATALOG_IBLOCK_ID);
}
Логування та моніторинг
Кожна синхронізація логується в таблицю HL-блоку StockSyncLog:
| Поле | Тип | Опис |
|---|---|---|
UF_SUPPLIER_ID |
integer | ID постачальника |
UF_DATE |
datetime | Дата синхронізації |
UF_UPDATED |
integer | Кількість оновлених позицій |
UF_SKIPPED |
integer | Не співставлені артикули |
UF_DURATION |
integer | Час виконання, мс |
UF_ERROR |
string | Текст помилки (якщо є) |
Якщо за останні 2 години немає успішної синхронізації для активного постачальника — агент-монітор надсилає алерт на пошту технічного адміністратора.
Як додати нового постачальника? (покрокова інструкція)
- Отримайте від постачальника зразок даних (XML/CSV/JSON) та домовтеся про розклад вивантаження.
- Створіть картку постачальника в Бітрікс (Highload-блок Suppliers) із зазначенням типу синхронізації та параметрів підключення.
- Реалізуйте парсер під формат постачальника, реалізувавши
FeedParserInterface(наш шаблон скорочує час на 40%). - Зареєструйте агент з періодом 5–30 хвилин залежно від частоти оновлень.
- Перевірте на тестових даних: співставлення артикулів, обробку нульових залишків, логування.
- Увімкніть моніторинг — алерти при збоях.
Що входить у налаштування синхронізації?
| Етап | Що робимо | Результат |
|---|---|---|
| Аналіз | Вивчаємо формати даних постачальників, прописуємо карту артикулів | Документація з інтеграції |
| Проектування | Вибираємо схему (push/pull/API), проектуємо архітектуру | Архітектурна схема |
| Реалізація | Пишемо парсери, агенти, обробники, логування | Робочий код в репозиторії |
| Тестування | Перевіряємо на тестових даних, емулюємо сценарії | Протокол тестування |
| Деплой | Розгортаємо на бойовому сервері, налаштовуємо моніторинг | Доступи, дашборди, алерти |
| Навчання | Документуємо процес додавання нового постачальника | Wiki в корпоративному порталі |
| Підтримка | 2 місяці безкоштовної підтримки після релізу | SLA 4 години |
Наша команда має 8+ років досвіду з 1С-Бітрікс та понад 50 успішних інтеграцій з постачальниками. Отримайте консультацію інженера — ми розрахуємо строки та вартість за один робочий день. Зв'яжіться з нами, щоб уникнути втрат через неактуальні залишки.







