Мониторинг обмена данными Битрикс

Задача: мониторить состояние обмена сайта на Битрикс с 1С, и при отсутствии обмена отправлять уведомления на почту с темой «Проблема обмена заказами 1С и сайта».

Дорабатываем скрипты обмена

После строчки

bitrix/components/bitrix/catalog.import.1c/component.php
COption::SetOptionString("sale", "last_export_time_".$curPage, time());

вставить

bitrix/components/bitrix/catalog.import.1c/component.php
COption::SetOptionString("sale", "monitor/1c-sales/mod=query/time", time());

Добавляем почтовое событие

Создаем новый тип события «MONITOR_REPORT» и добавляем почтовый шаблон следующего вида:

От кого: #DEFAULT_EMAIL_FROM#
Кому: #USER_MAIL#
Тема: #SUBJECT#
Сообщение (тип HTML): #MESSAGE#

Скрипт мониторинга

cron/monitor-1c.php
if(php_sapi_name() !== 'cli') die('Access denied'); // только по расписанию из CLI
 
$_SERVER["DOCUMENT_ROOT"] = dirname(__DIR__);
 
define("LANGUAGE_ID", "s1"); // ID сайта
define("NO_KEEP_STATISTIC", true);
define("NOT_CHECK_PERMISSIONS", true);
require($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/include/prolog_before.php");
 
// Run
 
class MonitorSales1C {
 
  private $now,
    $delay = 10800, // задержка перед повторной отправкой уведомления (секунды)
    $intervalQuery = 20, // максимальный интервал просрочки обмена (минуты)
    $intervalSales = 20, // максимальный интервал просрочки подтверждения заказов (минуты)
    $notifyMails = 'webmaster@site.ru,admin@site.ru', // Email для уведомлений
    $notifySubj = 'Проблема обмена заказами 1С и сайта', // Тема письма
    $notifyPS = '<p><em>Это сообщение сформировано автоматически с сайта site.ru</em></p>' // Постскриптум
    ;
  public function __construct() {
    CModule::IncludeModule("iblock");
    CModule::IncludeModule("sale");
    $this->now = new DateTime();
  }
 
  public function run() {
    try {
      $this->checkQuery(); // проверка обмена заказами
      $this->checkSales(); // проверка выгрузки подтверждения обработки заказов
    } catch (Exception $e) {
      $text = $e->getMessage();
      if($this->isDelayLeft()) {
        $this->setDelay();
        $this->notify($text);
      }
 
      echo $text.PHP_EOL;
    }
  }
 
  private function checkQuery() {
    $lastTimeStamp = COption::GetOptionString("sale", "monitor/1c-sales/mod=query/time", "0"); // событие, которое проверяем
    if($lastTimeStamp == "0") return;
 
    $diff = $this->calcMaxExceedDate($this->intervalQuery)->getTimestamp() - $lastTimeStamp;
    if($diff > 0) throw new Exception("Давно не было получения заказов: с ".date('Y-m-d H:i:s', $lastTimeStamp));
  }
 
  private function checkSales() {
    $dayAgo = clone $this->now;
    $dayAgo->modify("-24 hour");
 
    $orderFilter  = array(
      'CANCELED' => 'N',
      '>DATE_INSERT' => $dayAgo->format("d.m.Y H:i:s"), // ищем все заказы за день
      '<DATE_UPDATE' => $this->calcMaxExceedDate($this->intervalSales)->format("d.m.Y H:i:s"), // отбрасываем за последние N минут
      'LID' => LANGUAGE_ID
    );
 
    $ordersList = CSaleOrder::GetList(array("ID" => "DESC"), $orderFilter, false, false, array("ID", "DATE_INSERT"));
 
    if($ordersList->SelectedRowsCount() == 0) return;
 
    $notImportedOrders = array();
 
    while($order = $ordersList->Fetch()) {
      $historyFilter = array("ORDER_ID" => $order['ID'], "TYPE" => "ORDER_1C_IMPORT");
      $historyList = CSaleOrderChange::GetList(array("ID" => "DESC"), $historyFilter, false, false, array("ID"));
 
      if($historyList->SelectedRowsCount() == 0) {
        $notImportedOrders[] = $order['ID'].' ['.$order['DATE_INSERT'].']';
      }
 
    }
 
    if(count($notImportedOrders) > 0) throw new Exception("Не были импортированы в 1С следующие заказы: <br />".implode('<br />', $notImportedOrders), 1);
 
  }
 
  private function calcMaxExceedDate($minutes) {
    $date = new DateTime();
    $date->modify("-".(int)$minutes." minute"); // <= максимальное время просрочки
    return $date;
  }
 
  private function notify($text) {
    echo 'Send notify...'.PHP_EOL;
    $arEventFields = array(
      'USER_MAIL' => $this->notifyMails,
      'SUBJECT' => $this->notifySubj,
      'MESSAGE' => '<p><b>'.$text.'</b></p>'.$this->notifyPS
    );
 
    CEvent::SendImmediate('MONITOR_REPORT', LANGUAGE_ID, $arEventFields, 'N');
  }
 
  private function isDelayLeft() {
    $lastTimeStamp = COption::GetOptionString("sale", "monitor/1c-sales/delay", 0);
    if($lastTimeStamp == 0) return true;
    $diff = $this->now->getTimestamp() - $lastTimeStamp;
    return $diff > $this->delay;
  }
 
  private function setDelay() {
    COption::SetOptionString("sale", "monitor/1c-sales/delay", $this->now->getTimestamp());
  }
 
 
}
 
$MonitorSales1C = new MonitorSales1C();
$MonitorSales1C->run();

Расписание запуска

Скрипт мониторинга состояния выгрузки заказов в 1С следует повесить на расписание по крону, например:

crontab.txt
# мониторинг обмена заказами: каждые 10 минут, начиная с 8-00 и до 20-00 по рабочим дням
*/10 8-20 * * 1-5 /usr/bin/php /path-to-site/cron/monitor-1c.php