Как избежать двойной отправки уведомлений при записи в регистр накопления?

Программист 1С v8.3 (Управляемые формы) 1С:Розница Управленческий учет Торговля и дистрибуция
← На главную

При разработке механизмов лояльности, таких как бонусные баллы в 1С, программисты часто сталкиваются с неприятной особенностью: подписка на событие ПриЗаписи регистра накопления срабатывает дважды. Если в этом событии прописана логика отправки СМС или Email покупателю, он получит два одинаковых уведомления, что выглядит непрофессионально и ведет к лишним затратам компании. В этой статье мы подробно разберем, почему платформа 1С ведет себя именно так, и рассмотрим эффективные способы фильтрации лишних событий.

Почему событие срабатывает два раза?

Для начала проанализируем внутренние механизмы платформы 1С. Когда документ (например, Чек ККМ) проводится, система выполняет запись движений по регистрам. Двойной вызов события ПриЗаписи — это не ошибка, а стандартное поведение системы в следующих случаях:

  1. Механизм очистки движений: Перед тем как записать новые данные, платформа 1С часто записывает пустой набор записей по данному регистратору. Это необходимо для того, чтобы «стереть» старые итоги и корректно пересчитать остатки. Таким образом, первый вызов — это запись пустого набора, а второй — запись набора с реальными данными.
  2. Свойство «Записывать» в коллекции движений: Если в модуле проведения документа программно установлен флаг Движения.БонусныеБаллы.Записывать = Истина, и при этом в коде встречается явный вызов метода Движения.БонусныеБаллы.Записать(), событие сработает дважды.
  3. Дополнительные расчеты в конфигурации: В типовых решениях, таких как «1С:Розница», начисление бонусов может инициироваться несколько раз: сначала предварительно при расчете скидок в РМК, а затем окончательно при пробитии чека.

Рассмотрим способы, которые помогут нам оставить только одно, «правильное» срабатывание.

Решение №1: Проверка количества записей в наборе

Это самый простой и эффективный метод отсечь первый вызов, связанный с очисткой движений. Мы проанализируем количество записей в объекте НаборЗаписей, который передается в обработчик события.

Если набор пустой — значит, система просто очищает старые движения, и отправлять уведомление не нужно. Рассмотрим пример кода для подписки на событие:


Процедура ПодпискаПриЗаписиРегистраБонусы(Источник, Отказ, Замещение) Экспорт
    
    // Источник — это НаборЗаписейРегистраНакопления.БонусныеБаллы
    
    // Шаг 1: Проверяем, что это не пустая запись (очистка)
    Если Источник.Количество() = 0 Тогда
        Возврат;
    КонецЕсли;
    
    // Шаг 2: Проверяем режим обмена данными, чтобы не слать СМС при загрузке из РИБ
    Если Источник.ОбменДанными.Загрузка Тогда
        Возврат;
    КонецЕсли;

    // Далее следует логика отправки уведомления...
    
КонецПроцедуры

Используя этот метод, мы игнорируем технический вызов платформы и реагируем только на фактическое появление данных в регистре.

Решение №2: Использование очереди уведомлений (наиболее надежный метод)

Отправка уведомлений напрямую из события регистра — рискованная затея. Дело в том, что событие ПриЗаписи происходит внутри транзакции проведения документа. Если после записи регистра в документе возникнет ошибка (например, не хватит товара на складе), транзакция откатится. При этом запись в регистре исчезнет, а СМС клиенту уже уйдет, так как внешнее действие (вызов API провайдера) отменить невозможно.

Проанализируем архитектурно верный подход: использование вспомогательного регистра «Очередь уведомлений».

  1. В событии ПриЗаписи регистра накопления мы не отправляем СМС, а записываем данные в специальный регистр сведений ОчередьУведомлений.
  2. Если транзакция проведения документа откатится, запись в очереди тоже автоматически исчезнет — уведомление не будет отправлено.
  3. Отправку самих сообщений реализуем через регламентное задание, которое раз в минуту проверяет очередь и рассылает сообщения.

Такой подход решает проблему дублей, экономит деньги на СМС и защищает от ошибок при откате транзакций.

Решение №3: Подписка на событие документа вместо регистра

Если логика бизнес-процесса позволяет, мы можем перенести подписку с регистра накопления на событие документа-регистратора (например, ОбработкаПроведения чека ККМ). В этом случае мы гарантированно получим управление один раз в момент завершения проведения.

Однако здесь важно учитывать, что один и тот же регистр бонусов могут двигать разные типы документов: Чек, Возврат, Корректировка. В таком случае придется создавать подписки на каждый вид документа, что не всегда удобно. Если же вы решите оставить подписку на регистр, обязательно добавьте проверку статуса документа-регистратора:


// Внутри обработчика набора записей регистра
Регистратор = Источник.Отбор.Регистратор.Значение;
Если НЕ (ТипЗнч(Регистратор) = Тип("ДокументСсылка.ЧекККМ") И Регистратор.Проведен) Тогда
    Возврат;
КонецЕсли;

Решение №4: Использование дополнительных свойств набора

Чтобы избежать повторного срабатывания в рамках одной транзакции, мы можем использовать коллекцию ДополнительныеСвойства. Это «карман», который живет вместе с объектом в памяти в течение текущего вызова.

Проанализируем ситуацию: если мы программно вызываем Записать(), а потом платформа вызывает его еще раз автоматически, мы можем пометить набор при первом вызове.


Процедура ПередЗаписью(Источник, Отказ, Замещение) Экспорт
    // Проверяем, не обрабатывали ли мы этот набор ранее в этой цепочке вызовов
    Если Источник.ДополнительныеСвойства.Свойство("УведомлениеУжеОбработано") Тогда
        Возврат;
    КонецЕсли;
    
    // Выполняем действия...
    
    // Ставим отметку
    Источник.ДополнительныеСвойства.Вставить("УведомлениеУжеОбработано", Истина);
КонецПроцедуры

Заключение

Подведем итоги. Двойное срабатывание события ПриЗаписи — это нормальная работа платформы 1С. Для решения проблемы с дублями уведомлений рекомендуется использовать комбинированный подход:

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

← На главную