Реализация двусторонней синхронизации каталога товаров с 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,
category_guid UUID,
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С для детекции изменений
);
-- Итоговый статус: товар активен только если активен и в 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,
'category_guid'=> $onecProduct->categoryGuid,
'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 рабочих дней.







