Не нашли ответы на свои вопросы в наших публикациях? Задайте вопрос в службу техподдержки!
Только статьи техподдержки. Общий поиск по сайту (модули Маркета, новости) — /search/.
Синхронизация заказов и остатков
1. Архитектура: два контура
Площадка (Ozon / WB / YM …)
│
▼
Плагин заказов → Controller::syncExtToStore → OrderSync::runSync
│
▼
Заказ Sale → doFinalAction(true) → save
│
▼
Bitrix Catalog: резерв / списание
QUANTITY, QUANTITY_RESERVED
или AMOUNT склада (складской учёт)
│
├─► YandexLiveStocks::touchCachedCounts ← только снимок YM
│
├─► OnProductUpdate / OnStoreProduct*
│ │
│ ▼
│ очередь AUTO_GENERATE ← снимок в БД модуля, не API площадки
│
└─► (отдельно, по расписанию)
профиль экспорта → шаг EXPORT_STOCKS → API Ozon / WB / YM / …
Ключевой разрыв: шаг «заказ сохранён» не вызывает stepExportStocks у профилей экспорта.
Важные файлы
| Роль | Путь |
|---|---|
| Оркестратор заказов |
lib/orders/controller.php
|
| Создание/обновление заказа |
lib/orders/ordersync.php
|
| Агенты заказов |
lib/orders/periodsync.php
|
| Cron заказов |
lib/orders/exporter.php, cli/export/export.php
|
| События Sale → площадка |
lib/orders/eventhandler.php
|
| Внешние URL, очередь каталога |
lib/export/eventhandler.php
|
| Снимок / live YM |
lib/export/yandexlivestocks.php
|
| Очередь AUTO_GENERATE |
lib/export/exporter.php (addToQueue, processQueue)
|
| Трекер изменений остатка |
lib/export/productchangetracker.php
|
| Складской учёт (хелпер) |
lib/helper.php → isCatalogUseStoreControl()
|
| UI синхронизации заказов |
admin/orders/include/tabs/sync.php
|
2. Контур заказов: какие варианты есть
Общий путь после получения заказа с площадки:
Controller::syncExtToStore → поиск заказа в магазине → OrderSync::runSync (или только статус, если MODULE_ACTION = change_status).
Пропускаются неактивный профиль, заказы вне start_date / end_date, статусы из «не использовать». Есть MySQL named lock acrit_ord_*.
2.1. Агент Bitrix (один период)
- Где:
PeriodSync::set/PeriodSync::run, типPlugin::ADD_SYNC_TYPE_SINGLE(по умолчанию). - Когда: в профиле
SYNC[add_active]=Yи задан период. - Плюсы: просто; не нужен crontab.
- Минусы: зависит от хитов сайта; окно lookback легко задать слишком узко и пропустить заказы.
2.2. Два агента (минуты + часы)
- Где:
getAddSyncType() = ADD_SYNC_TYPE_DUAL— Ozon, WB, YM/YM2, Kaspi и ряд других. - Плюсы: свежие заказы часто; «хвост» догоняется длинным окном.
- Минусы: два прогона; риск гонок (частично снимает lock).
2.3. Cron (CLI)
- Где:
cli/export/export.php→ для модуля заказов вызываетсяPeriodSync::run(та же логика, что у агента). - Плюсы: стабильнее агента на нагруженном сайте.
- Минусы: нужен crontab; это всё ещё опрос API, не real-time.
2.4. Ручной запуск
- Где: вкладка «Синхронизация»,
admin/orders/include/actions/man_sync_run.php. - Плюсы: период (день / неделя / всё), опция только новых, контроль глазами.
- Минусы: не автоматика; шаги по ~100 секунд.
2.5. Внешний запрос / уведомления (push)
Роутер: EventHandlerExport::OnProlog. Нужны опция модуля allow_external_request = Y и в профиле EXTERNAL_REQUEST + URL.
| Плагин | Механизм | Комментарий |
|---|---|---|
| Яндекс.Маркет 2 |
API-уведомления (notificationType, PING, проверка IP)
|
Заказ почти сразу; опрос остаётся запасным каналом |
| Яндекс.Маркет (старый) | cart / order-accept / status / cancel | Устаревшая схема |
| Ozon, WB и большинство остальных | нет push заказов | Только опрос агентом/cron |
Плюсы push: низкая задержка.
Минусы: настройка URL, белый список, не универсально для всех площадок.
2.6. Обратный контур (магазин → площадка)
События OnSaleOrderBeforeSaved / OnSaleOrderSaved → Acrit\Core\Orders\EventHandler → onBitrixStatusChanged / onBitrixTrackingChanged.
Это статусы и трек, не загрузка заказа и не выгрузка остатков.
3. Что происходит с остатком, когда создаётся заказ
Acrit не вызывает CCatalogProduct::Update и не пишет StoreProduct, чтобы уменьшить остаток.
Что делает код (OrderSync::runSync):
- Собирает корзину Sale (
updateOrderProducts). - При новом заказе или смене состава:
$order->refreshData(); $order->doFinalAction(true);затем save. - При необходимости выставляет
STORE_IDотгрузки. Если Sale отвергает склад (ограничения доставки), заказ всё равно создаётся, склад может дописаться отложенным SQL. - После успешного save:
YandexLiveStocks::touchCachedCounts($elementIds)— только таблицы снимков YM.
Списание / резерв — поведение Sale + Catalog при включённом количественном учёте и/или складском учёте.
Товары, которые модуль заказов сам создаёт в каталоге (Products::createCatalogProduct): QUANTITY = 0, QUANTITY_TRACE = N, CAN_BUY_ZERO = Y. Такие позиции не резервируются как обычная карточка.
Если количественный учёт выключен или «можно купить при нуле» — заказ появится, каталог не изменится, пушить на другие площадки будет нечего.
4. Контур остатков (экспорт)
Выгрузка остатков не привязана к заказу. Это шаги профиля экспорта.
4.1. Полный / остаточный прогон профиля
- Cron, агент экспорта, ручной запуск в админке.
- Флаги вида
EXPORT_STOCKS, у Ozon ещёEXPORT_STOCKS_ONLY/ режим «остатки и цены». - Склады площадки мапятся на поля каталога:
CATALOG_QUANTITY,QUANTITY_AVAILABLE,CATALOG_STORE_AMOUNT_{id}и варианты с резервом.
Плюсы: единый пайплайн, пачки, склады, обнуление «лишних» SKU.
Минусы: задержка до следующего cron; шаг leftover опасен при двух профилях на один склад.
4.2. AUTO_GENERATE по событию каталога
OnProductAdd/OnProductUpdate→Exporter::addToQueue.OnStoreProductAdd/OnStoreProductUpdate→ то же +ProductChangeTracker::trackStoreStock.- На
OnEpilog/ редиректе —processQueue: пересчёт строк в БД модуля для профилей сAUTO_GENERATE=Y.
Плюсы: снимок в модуле свежий сразу после изменения каталога.
Минусы: площадка об этом не узнает, пока не выполнится шаг API остатков.
4.3. «Быстрые остатки» Яндекс.Маркета (LIVE_STOCKS)
- По умолчанию включено.
- Ответы
/stocksи/cartсчитают остаток в момент запроса:
QUANTITY − |QUANTITY_RESERVED| или значение из маппинга профиля (в том числе склад).
- После заказа
touchCachedCountsобновляет COUNT в таблицахacrit_yandex_marketplace_stocks/acrit_yandex_market_api_stocks.
Плюсы: Маркет не ждёт cron; меньше оверселла после резерва.
Минусы: работает только пока Маркет сам дергает URL. У Ozon и WB такого канала нет.
4.4. Сброс «хвостов» (leftover / RESET_MARKET_STOCKS)
- Обнуляет на площадке SKU, которых нет в текущей сессии выгрузки.
- Ключ keep:
SKU|идентификатор склада. Склад0вместо реального id обнуляет чужие позиции.
Этот шаг нельзя запускать «на один товар после заказа».
5. Складской учёт: выключен и включён
Опция каталога: catalog / default_use_store_control (Helper::isCatalogUseStoreControl()).
В логике заказов Acrit этой галки нет: поведение задаёт Sale после doFinalAction.
В экспорте поля складов показываются, если склады заведены (проверка isCatalogUseStoreControl в выборе полей закомментирована).
| Складской учёт выкл. | Складской учёт вкл. | |
|---|---|---|
| Где остаток |
b_catalog_product.QUANTITY, QUANTITY_RESERVED
|
b_catalog_store_product.AMOUNT по складам
|
| Заказ Acrit |
Резерв на уровне товара, если QUANTITY_TRACE не «нет»
|
Нужен верный STORE_ID отгрузки
|
| Поля экспорта |
CATALOG_QUANTITY, QUANTITY_AVAILABLE
|
CATALOG_STORE_AMOUNT_{id}, *_RESERVED, *_AVAILABLE — явно мапить на склад МП
|
| Риск «сразу на все МП» | Один остаток на все выгрузки — проще |
Профиль A: склад 1 → Ozon, профиль B: склад 2 → WB. Пуш общего QUANTITY даёт оверселл или ноль не там
|
| YM live без маппинга |
getCatalogAvailable = товарный доступный
|
Если мапинг не на склад — снова товарный QUANTITY, это уже ошибка учёта
|
| Отгрузка без склада | Обычно не критично |
Sale может отвергнуть STORE_ID; резерв мог пройти не с того склада
|
6. Обмен с 1С (ERP)
В acrit.core нет клиента CommerceML, sale.export.1c, catalog.import.1c. Упоминания «1С» в коде почти всегда значат 1С-Битрикс (CMS).
Остатки и заказы с ERP идут штатным обменом Bitrix. Acrit видит только результат в каталоге и в заказах Sale.
| Схема клиента | Если пушить остатки сразу после заказа Ozon | Как согласовать |
|---|---|---|
| 1С — хозяин остатков, Bitrix витрина |
Резерв Sale уменьшит Bitrix, через цикл обмена 1С перезапишет QUANTITY «как в 1С» без этого заказа → на МП уйдёт старый остаток, оверселл
|
Сначала заказ в 1С, 1С считает склад, импорт в Bitrix, уже этот остаток пушить на МП. «Сразу» = после импорта 1С |
| Bitrix — хозяин, 1С забирает заказы | Резерв Sale — источник правды. Пуш по каталогу корректен |
Можно пушить после doFinalAction. Не включать leftover на один SKU
|
| Оба пишут QUANTITY | Гонка: заказ Ozon, импорт 1С, выгрузка Acrit | Очередь с паузой и дедупом; не пушить в окне обмена 1С |
| Склады 1С + складской учёт |
1С пишет AMOUNT, Sale резервирует по STORE_ID
|
Явный маппинг: склад 1С ↔ склад Bitrix ↔ склад Ozon/WB в каждом профиле экспорта |
7. Известные ловушки (из кода и практики)
- Ключ leftover
SKU|склад. Склад0вместо id склада обнуляет SKU текущей сессии. - Два профиля экспорта на один склад площадки: шаг сброса хвостов одного профиля затирает остатки другого.
- Сообщение «остатки успешно выгружены» нельзя вешать на пачки обнуления.
- Ozon: при настроенных складах общий
stockможет давать ошибку API — нужныstock_{warehouseId}. - WB FBO-приоритет может обнулять FBS, если на FBO ещё есть количество.
- YM
LIVE_STOCKSвыкл. → в/stocksуходит устаревший снимок → оверселл после резерва. touchCachedCountsне пушит Ozon/WB.AUTO_GENERATE ≠выгрузка на API.- Пустая корзина /
cartblock=Y— состав заказа не обновляется. - Внешние запросы не работают в ajax/cli/админке (
OnPrologих пропускает).
8. Что сделать для режима «заказ на Ozon → остаток на всех интеграциях»
Честно «сразу на все МП одной галкой» нельзя:
- Ozon и WB принимают только исходящий API, с лимитами;
- полный шаг leftover нельзя вызывать на один SKU;
- 1С может перезаписать каталог следом.
8.1. Рекомендуемый контур
Триггер — не «заказ Ozon», а изменение остатка в каталоге
(заказ МП, импорт 1С, ручной заказ, правка в админке — одно событие).
Уже ловятся: OnProductUpdate, OnStoreProduct*. Сейчас из них только AUTO_GENERATE. Нужно добавить:
- Запись в очередь:
product_id+store_id(или только товар, если склады выкл.) + время. - Склейка 5–15 секунд (пачка под лимиты API; при 1С-хозяине — дольше или «после конца обмена»).
- Для каждого профиля экспорта с включёнными остатками: только пачка stocks API этих SKU, без leftover/reset.
- Маппинг:
QUANTITY_AVAILABLEилиCATALOG_STORE_AMOUNT_{id}→warehouseIdплощадки. - Блокировка: не пересекаться с полным прогоном того же профиля.
Плюсы: одна механика на заказ Ozon и на импорт 1С.
Минусы: новая очередь, лимиты, маппинг складов, настройка «кто хозяин остатков».
8.2. Хук только после OrderSync — недостаточно
Сработает для заказа МП, не увидит импорт 1С и ручное списание. Для клиента, где 1С хозяин остатков, пуш до пересчёта 1С вреден. Имеет смысл как дополнительный touch (как сейчас YM), не как единственный канал.
8.3. Чего не делать
| Идея | Почему нет |
|---|---|
| Полный прогон всех профилей на каждый заказ | Минуты, лимиты API, обнуление чужих SKU |
| Считать LIVE_STOCKS универсальным | Ozon и WB сами остаток не спрашивают |
Списывать остаток через CCatalogProduct::Update
|
Двойное списание с Sale, поломка складов и обмена 1С |
Всегда слать общий QUANTITY при включённых складах
|
Оверселл на одном складе, ноль на другом |
8.4. Этапы внедрения
- Контракт профиля экспорта: отдельная галка «оперативные остатки»; только профили с
EXPORT_STOCKS=Yи настроенным складом МП; leftover не вызывать. - Триггер: существующие события каталога → очередь SKU+склад.
- Склейка: агент 5–15 с или ограниченная обработка на OnEpilog; при обмене 1С — ждать конец импорта.
- Пуш: тот же формат пачки, что
stepExportStocks, без reset. - 1С: явная настройка клиента. Хозяин 1С — пуш после импорта. Хозяин Bitrix — пуш после резерва Sale.
- Склады: вкл. — ключ
product|store_id, в профиль только если этот store замаплен. Выкл. — ключ товара, поле доступного количества.
9. Итог
| Вопрос | Ответ по коду |
|---|---|
| Заказ с Ozon сразу меняет остаток на WB? | Нет |
| Кто меняет остаток в Bitrix? |
Sale/Catalog после doFinalAction, не Acrit напрямую
|
| Когда остаток уйдёт на Ozon/WB? | Следующий прогон профиля экспорта с остатками |
| Где «сразу» уже есть? |
Только YM live /stocks и /cart
|
| Учитывается ли галка складского учёта в заказах Acrit? | Нет, смотрит ядро Bitrix |
| Есть ли обмен с 1С ERP в модуле? | Нет |
| Как сделать нужный режим? | Очередь по событию каталога → пачка stocks без leftover; правило «хозяин: Bitrix или 1С»; маппинг складов |
Сейчас «сразу» есть только у Яндекса, потому что он сам спрашивает. Для цепочки Ozon → WB / другие склады Ozon / Avito нужен исходящий пуш по событию каталога, без полного прогона и без leftover, с явным правилом относительно 1С и складов Bitrix. Иначе либо задержка cron, либо оверселл, либо обнуление чужих остатков.
Краткий вывод
Сейчас нет режима «заказ с Ozon → остаток сразу на все интеграции».
Заказы и выгрузка остатков — два независимых контура. Общая точка — каталог 1С-Битрикс. Дальше каждый контур работает по своему расписанию.
- Заказ с площадки создаёт заказ Sale. Модуль сам не уменьшает
QUANTITY. - Резерв и списание делает ядро Bitrix (количественный учёт, складской учёт).
- Выгрузка остатков на Ozon, WB и другие площадки идёт прогоном профиля экспорта (cron / агент / вручную).
- Исключение: Яндекс.Маркет с «быстрыми остатками» — Маркет сам спрашивает
/stocksи/cart, ответ считается из каталога в момент запроса. - Обмена с 1С (ERP) в этом репозитории нет: это штатный обмен Bitrix. Acrit читает то, что уже лежит в каталоге.
Назад в раздел
