Yandex Metrika
sanches.free

Как отследить сбой обмена заказами между Битрикс и 1С и получить письмо

Зачем отдельный мониторинг

Статус обмена в административной части не всегда заметен до звонка из отдела продаж. Проще один раз вставить в точку, где обработчик заказов уже сообщил «я отработал», сохранение своей метки времени и запускать короткую CLI‑проверку по расписанию. Если давно не было живого mode=query или у свежих заказов нет отметки импорта в учётную систему, скрипт отправляет письмо с понятной темой.

Метка в сценарии обмена

В том месте штатного кода, где уже фиксируют последний экспорт (после строки с COption::SetOptionString('sale', 'last_export_time_'.$curPage, time())), добавьте фиксацию пользовательского счётчика через API настроек. Так вы отвязываете контроль от внутренних ключей и можете переименовать параметр, если в проекте заняты короткие имена.

use Bitrix\Main\Config\Option;

Option::set('sale', 'pulse/exchange/sale/query_at', (string) time());

Значение читается строкой; для сравнения ниже приводится к целому временем Unix.

Почтовое событие

Создайте тип события, например MONITOR_REPORT, и шаблон с полями отправителя из системы, произвольным текстом письма и заголовком через плейсхолдеры #SUBJECT#, #MESSAGE#, #USER_MAIL#, #DEFAULT_EMAIL_FROM#. Тип тела — HTML, чтобы в уведомление можно было добавить короткий постскриптум курсивом.

Скрипт проверки на D7

Скрипт допускают только из CLI; через браузер его закрывают первой строкой. Подключается ядро через prolog_before.php, подгружаются модуль sale и основные пространства имён. Первая проверка сравнивает сохранённую метку запроса заказов с допустимым отставанием в минутах. Вторая выбирает активные заказы интернет‑магазина за последние сутки, у которых давно не было обновления статуса, и ищет запись изменения с типом импорта из 1С — при отсутствии такой строки заказ попадает в отчёт.

Уведомление не спамит: отдельная опция хранит время последней отправки и не пускает следующую раньше заданного интервала в секундах.

Автоматическое уведомление portal.example

'; private string $optQuery = 'pulse/exchange/sale/query_at'; private string $optCooldown = 'pulse/exchange/sale/notify_at'; public function __construct() { Loader::includeModule('sale'); $this->clock = new \DateTimeImmutable('now'); } public function execute(): void { try { $this->assertFreshQueryMarker(); $this->assertImportsRegistered(); } catch (\Throwable $e) { if ($this->cooldownExpired()) { $this->persistCooldown(); $this->dispatchMail($e->getMessage()); } fwrite(STDOUT, $e->getMessage() . PHP_EOL); } } private function assertFreshQueryMarker(): void { $stored = Option::get('sale', $this->optQuery, '0'); if ($stored === '0') { return; } $lastTs = (int) $stored; $deadline = $this->clock ->modify('-' . max(1, $this->queryLagMinutes) . ' minutes') ->getTimestamp(); if ($deadline - $lastTs > 0) { throw new \RuntimeException( 'Давно не было получения заказов: с ' . date('Y-m-d H:i:s', $lastTs) ); } } private function assertImportsRegistered(): void { $dayBoundary = $this->clock->modify('-24 hours')->format('d.m.Y H:i:s'); $stalePivot = $this->clock ->modify('-' . max(1, $this->confirmLagMinutes) . ' minutes') ->format('d.m.Y H:i:s'); $cursor = OrderTable::getList([ 'filter' => [ '=CANCELED' => 'N', '>DATE_INSERT' => $dayBoundary, '<DATE_UPDATE' => $stalePivot, '=LID' => LANGUAGE_ID, ], 'select' => ['ID', 'DATE_INSERT'], 'order' => ['ID' => 'DESC'], ]); $pending = []; while ($row = $cursor->fetch()) { $hasImport = OrderChangeTable::getList([ 'filter' => [ '=ORDER_ID' => $row['ID'], '=TYPE' => 'ORDER_1C_IMPORT', ], 'select' => ['ID'], 'limit' => 1, ])->fetch(); if (!$hasImport) { $pending[] = $row['ID'] . ' [' . $row['DATE_INSERT'] . ']'; } } if ($pending !== []) { throw new \RuntimeException( 'Не были импортированы в 1С:
' . implode('
', $pending) ); } } private function cooldownExpired(): bool { $last = (int) Option::get('sale', $this->optCooldown, '0'); if ($last === 0) { return true; } return $this->clock->getTimestamp() - $last > $this->repeatCooldownSec; } private function persistCooldown(): void { Option::set('sale', $this->optCooldown, (string) $this->clock->getTimestamp()); } private function dispatchMail(string $bodyHtml): void { Event::sendImmediate([ 'EVENT_NAME' => 'MONITOR_REPORT', 'LID' => LANGUAGE_ID, 'C_FIELDS' => [ 'USER_MAIL' => $this->recipientCsv, 'SUBJECT' => $this->mailSubject, 'MESSAGE' => '

' . $bodyHtml . '

' . $this->footerHtml, ], ]); } } (new SaleBridgePulseMonitor())->execute();

Расписание cron

Запускайте файл каждые десять минут в рабочее окно, чтобы не дергать сайт ночью без необходимости. Путь к PHP и к каталогу проекта замените на свои.

*/10 8-20 * * 1-5 /usr/bin/php /var/www/site/cron/monitor-sale-bridge.php

На что смотреть после внедрения

Убедитесь, что служебный тип события привязан к активному шаблону и адреса в строке получателей не режет корпоративный антиспам. При смене идентификатора сайта обновите константу и фильтр по полю LID. Если в вашей версии торгового модуля тип следов обмена называется иначе, скорректируйте строку фильтра к фактическому коду записи журнала заказов.

Не хотите копаться сами?

Починю за 1-3 дня. Без предоплаты — оплата по результату.

15+ лет опыта с 1С-Битрикс · Без предоплаты · 7 дней гарантии