Реалізація двусторонної синхронізації каталогу товарів з 1С
Двусторонння синхронізація — це не просто імпорт і експорт окремо. Це управління конфліктами: що відбувається, коли ціна змінилась і в 1С, і на сайте одночасно? Хто джерело правди для конкретного поля? Без чітких правил синхронізація перетворюється на хаос.
Визначення джерел правди
Перший крок — зафіксувати для кожного поля, яка система є мастером:
| Поле | Мастер | Логіка |
|---|---|---|
| Назва товара | 1С | 1С — система номенклатури |
| Артикул / SKU | 1С | Артикул задається у обліковій системі |
| Опис | Сайт | Маркетингові тексти пишуються редактором |
| Ціна | 1С | Ціноутворення у обліковій системі |
| Залишки | 1С | Реальний облік на складах |
| Зображення | Сайт | Фото обробляються окремо |
| SEO-поля | Сайт | Meta title/description — на стороне сайту |
| Статус активності | Обидва | 1С може знятти з продажу, сайт теж |
Схема бази даних
CREATE TABLE products (
id BIGSERIAL PRIMARY KEY,
onec_guid UUID UNIQUE, -- ідентифікатор 1С
sku TEXT,
-- Поля з 1С (перезаписуються при кожній синхронізації)
name_1c TEXT,
price_1c NUMERIC(12,2),
stock_1c INTEGER,
active_1c BOOLEAN DEFAULT true,
-- Поля сайту (не перезаписуються синхронізацією)
description TEXT,
meta_title TEXT,
meta_description TEXT,
images JSONB,
active_site BOOLEAN DEFAULT true,
-- Метаінформація синхронізації
last_sync_1c TIMESTAMPTZ,
sync_hash_1c CHAR(64) -- хеш для детекції змін
);
-- Остаточний статус: товар активен тільки якщо активен і в 1С, і на сайті
CREATE VIEW products_active AS
SELECT * FROM products WHERE active_1c = true AND active_site = true;
Алгоритм синхронізації: 1С → сайт
class OnecToSiteSyncService
{
public function sync(CommerceMLData $data): SyncResult
{
$result = new SyncResult();
foreach ($data->products as $onecProduct) {
$syncHash = $this->computeHash($onecProduct);
$product = Product::firstOrNew(['onec_guid' => $onecProduct->guid]);
// Пропускаємо, якщо дані не змінились
if ($product->exists && $product->sync_hash_1c === $syncHash) {
$result->skipped++;
continue;
}
// Оновлюємо ТІЛЬКИ поля з 1С (не трогаємо description, images й т.д.)
$product->fill([
'sku' => $onecProduct->sku,
'name_1c' => $onecProduct->name,
'price_1c' => $onecProduct->price,
'stock_1c' => $onecProduct->stock,
'active_1c' => $onecProduct->active,
'last_sync_1c' => now(),
'sync_hash_1c' => $syncHash,
]);
$product->save();
$result->updated++;
}
// Товари, які 1С більше не виґужає — деактивуємо
$syncedGuids = $data->products->pluck('guid');
Product::whereNotIn('onec_guid', $syncedGuids)
->update(['active_1c' => false]);
return $result;
}
}
Синхронізація: сайт → 1С
Сайт передає в 1С тільки те, що змінилось на сайті і має значення для 1С: нові замовлення, повернення, підтвердження оплати.
class SiteToOnecSyncService
{
public function getUnsyncedOrders(): Collection
{
return Order::where('sent_to_1c', false)
->where('status', '!=', 'draft')
->with(['items.product', 'customer'])
->get();
}
}
Обнаруження та розв'язання конфліктів
Конфлікт: поле active_site було виставлено оператором сайту в false (знято з публікації), але наступна виґужка з 1С містить active = true. За правилами таблиці вище — 1С є мастером для active_1c, але active_site не трогається.
Результат: active_1c = true, active_site = false → товар не відображається. Оператор сайту зберігає контроль.
Мониторинг синхронізації
-- Останні статуси синхронізації
SELECT
source,
COUNT(*) FILTER (WHERE status = 'success') AS success,
COUNT(*) FILTER (WHERE status = 'error') AS errors,
MAX(finished_at) AS last_run,
AVG(EXTRACT(EPOCH FROM (finished_at - started_at))) AS avg_duration_sec
FROM sync_logs
WHERE started_at > NOW() - INTERVAL '7 days'
GROUP BY source;
Лінія часу
Двусторонія синхронізація каталогу з 1С, включаючи тестування на реальній конфігурації: 14–20 робочих днів.







