У нас сеть из семи магазинов. Заказы на самовывоз прилетают, но смотреть 1С каждые пять минут - так себе удовольствие. Менеджер в головном офисе обработал заказ, выставил статус “собрать” - а магазин узнает об этом только если сам зайдёт и проверит, либо менеджер должен ему сообщить.
При этом в компании уже давно работает корпоративный VK WorkSpace / VK Teams. Все магазины сидят в рабочих чатах, никаких сторонних мессенджеров.
И одни админ магазина спрашивает:
“Можно, чтобы когда заказ ставят на сборку, нам приходило уведомление. Чтобы мы не караулили 1С?”
Смысл простой:
- Клиент оформил заказ →
- Менеджер проверяет →
- Ставит статус P (собрать) →
- 1C и Битрикс синхронизируется →
- Битрикс понимает “ага, это самовывоз, магазин №X” →
- Смотрит UF_CHATID этого магазина →
- Отправляет сообщение в VK Teams через бота →
- Сотрудник сразу идет собирать. И всё. Никаких пропущенных заказов, никаких “ой, мы не заметили”, никаких обновлений 1С каждые 2 минуты.
1. Событие, на котором всё держится
Вешаем обработчик на: `
OnSaleOrderSavedЭто одно из самых стабильных событий в Bitrix - оно срабатывает каждый раз, когда заказ сохраняется, независимо от того, меняли ли статус через:
- 1С;
- Админку Bitrix;
- API;
- Интеграцию. Это критично, потому что в процессе участвуют 1С → Битрикс, и мы должны ловить, изменения, которые приходят “снаружи”, а не только через админку.
2. Определяем, что статус реально изменился
Что бы не слать уведомления при каждом сохранении заказа, мы сравниваем:
- старый статус (из
getOriginalValues()) - новый статус (
getField("STATUS_ID")) Обрабатываем только если статус реально изменился и только если он стал: - P - собрать заказ
- B - заказ отменён Это защищает от дублей и мусорных триггеров.
3. Привязка к магазину через доставку “Самовывоз”
Из коллекции отгрузок (ShipmentColletction) извлекаем:
- доставку заказа
- store ID
- а далее - магазин из каталога (
CCatalogStore) В магазин (склад) добавляем пользовательское поле:
UF_CHATIDВ него будем записывать электронную почту магазина. Это номер чата в VK Teams, куда нужно отправлять уведомления. Такая связь “Магазин - Чат” универсальна, масштабируема и не требует хардкода.
4. Формируем данные для уведомления
Из заказа собираем:
- номер заказа
- ФИО клиента
- сумму
- комментарий менеджера
- состав заказа (название + количество)
- название магазина Это превращаем в форматированный HTML-текст для VK Teams Пример для статуса “собрать”:
⚠️ Соберите заказ №12345
────────────────────
Клиент: Иван Иванов
Сумма: 3 250 ₽
Магазин: Магазин №X
────────────────────
🧾 Состав заказа:
[Фен - 1 шт]
[Шампунь - 2 шт]5. Отправка уведомления в VK Teams
VK WorkSpace (MyTeam) предоставляет простой REST API:
/bot/v1/messages/sendTextМы передаём параметры:
- токен бота
- chatid
- текст сообщения
- parseMode = HTML Происходит обычный GET-запрос через cURL. Сообщение появляется мгновенно - сотрудники магазина видят как не прочитанное в трее.
6. Пример интеграционного обработчика для статусов заказов
Вот минимальный рабочий пример класса, который:
- ловит изменения статуса,
- проверяет, что это самовывоз,
- находит магазин,
- читает UF_CHATID
- формирует сообщение,
- отправляет его в VK Teams.
<?php
namespace Company\EventHandler;
use Bitrix\Main\Loader;
use Bitrix\Sale;
class SendOrderToStore
{
public static function SendOrderNotifyToStore(\Bitrix\Main\Event $event)
{
$order = $event->getParameter("ENTITY");
$isNew = $event->getParameter("IS_NEW");
if ($isNew) return;
$statusNew = $order->getField("STATUS_ID");
$fieldValues = $order->getFields()->getOriginalValues();
$statusOld = $fieldValues['STATUS_ID'] ?? null;
$logFile = $_SERVER['DOCUMENT_ROOT'] . '/vk_event.log';
file_put_contents($logFile, date('[Y-m-d H:i:s]') . " → Order {$order->getId()} {$statusOld} → {$statusNew}\n", FILE_APPEND);
if (!in_array($statusNew, ['P', 'B']) || $statusOld === $statusNew) return;
self::processStoreNotification($order->getId(), $statusNew);
}
protected static function processStoreNotification($orderId, $statusNew)
{
Loader::includeModule('sale');
Loader::includeModule('catalog');
$order = Sale\Order::load($orderId);
if (!$order) return;
$propertyCollection = $order->getPropertyCollection();
$buyerName = 'Клиент';
foreach ($propertyCollection as $property) {
$propCode = $property->getField('CODE');
$propValue = trim($property->getValue());
if (in_array($propCode, ['FIO', 'CONTACT_NAME', 'NAME', 'FULL_NAME']) && $propValue) {
$buyerName = $propValue;
break;
}
}
$price = number_format($order->getPrice(), 0, '', ' ');
$managerComment = trim($order->getField('COMMENTS'));
$shipmentCollection = $order->getShipmentCollection();
foreach ($shipmentCollection as $shipment) {
if ($shipment->isSystem()) continue;
$deliveryService = $shipment->getDelivery();
if (!$deliveryService || stripos($deliveryService->getName(), 'Самовывоз') === false) continue;
$storeId = $shipment->getStoreId();
if (!$storeId) continue;
$store = \CCatalogStore::GetList([], ['ID' => $storeId], false, false, ['ID','TITLE','UF_CHATID'])->Fetch();
if (!$store || !$store['UF_CHATID']) continue;
$chatId = trim($store['UF_CHATID']);
$storeTitle = $store['TITLE'];
$basket = $order->getBasket();
$itemsList = [];
foreach ($basket as $item) {
$name = $item->getField('NAME');
$quantity = (float)$item->getQuantity();
$itemsList[] = "[{$name} - {$quantity} шт]";
}
$itemsText = implode("\n", $itemsList);
$commentText = $managerComment ? "\nКомментарий: {$managerComment}" : '';
if ($statusNew === 'P') {
$text = "⚠️ Соберите заказ №<b>{$orderId}</b>\n".
"────────────────────\n".
" Клиент: {$buyerName}\n".
" Сумма: <b>{$price} ₽</b>\n".
" Магазин: {$storeTitle}{$commentText}\n".
"────────────────────\n".
"🧾 Состав заказа:\n{$itemsText}";
} elseif ($statusNew === 'B') {
$text = "❌ Заказ: №<b>{$orderId}</b> отменён — расформируйте заказ\n".
"────────────────────\n".
"Клиент: {$buyerName}\n".
"Сумма: <b>{$price} ₽</b>\n".
"Магазин: {$storeTitle}{$commentText}\n".
"────────────────────\n".
"🧾 Состав заказа:\n{$itemsText}";
}
self::sendVkMessage($chatId, $text);
}
}
protected static function sendVkMessage($chatId, $text)
{
$token = getenv('MYTEAM_BOT_TOKEN');
if (!$token) return;
$url = 'https://myteam.mail.ru/bot/v1/messages/sendText';
$params = http_build_query([
'token' => $token,
'chatId' => $chatId,
'text' => $text,
'parseMode' => 'HTML'
]);
$curl = curl_init("{$url}?{$params}");
curl_setopt_array($curl, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
]);
$response = curl_exec($curl);
curl_close($curl);
}
}В итоге всё стало чуть проще…