При разработке механизмов уведомлений в 1С часто возникает задача: нужно отправить письмо контрагенту при создании документа, подождать определенное время (например, 5 дней) и, если ответного действия (подписания) не последовало, отправить повторное напоминание. На первый взгляд задача кажется простой, но неправильный выбор архитектуры хранения данных может привести к серьезным проблемам с производительностью и усложнению кода.
В этой статье мы подробно разберем, как спроектировать структуру данных для такой задачи, почему стоит избегать периодических регистров для очередей и как правильно организовать фоновую обработку.
Рассмотрим типичную ситуацию. У нас есть документ, например, РеализацияТоваровУслуг. Нам необходимо:
Первое, что часто приходит в голову разработчику — использовать периодический регистр сведений, где измерением будет Реализация, а ресурсом — Статус. Логика кажется следующей: в фоновом задании мы будем получать текущий статус через виртуальную таблицу СрезПоследних.
Почему это плохая идея?
Использование виртуальной таблицы СрезПоследних в регулярных регламентных заданиях для построения очередей — это архитектурная ошибка, которая при росте базы приведет к замедлению. Виртуальная таблица каждый раз вычисляет актуальное состояние на момент времени, что ресурсоемко. Кроме того, нам нужно не просто последнее состояние, а состояние с учетом прошедшего времени.
Давайте разберем более эффективные подходы, предложенные опытными разработчиками.
Наиболее производительным и архитектурно верным решением является использование обычного (непериодического) регистра сведений. Он будет выступать в роли оперативной таблицы состояний. Историю изменений (логи) мы будем хранить отдельно, а регистр будет отвечать только за вопрос «Что нужно сделать прямо сейчас?».
Рекомендуемая структура регистра:
ДокументОснование (ссылка на Реализацию) — основной разрез учета.Адресат (опционально, если по одному документу может быть несколько получателей).Статус (Перечисление: ОжидаетОтправки, Отправлено, Ошибка, Завершено).ДатаПоследнейОтправки (Дата) — информативное поле.ПлановаяДатаСледующейОбработки (Дата) — ключевое поле для регламентного задания.КоличествоПопыток (Число) — чтобы избежать бесконечных циклов при ошибках.Логика работы:
При таком подходе мы отказываемся от анализа «прошло ли 5 дней» внутри запроса. Мы вычисляем дату следующего действия в момент записи в регистр.
ОжидаетОтправки и ПлановаяДатаСледующейОбработки = ТекущаяДата().ПлановаяДатаСледующейОбработки <= ТекущаяДата().Отправлено, а ПлановаяДатаСледующейОбработки устанавливаем как ТекущаяДата() + 5 дней.Завершено с пустой датой обработки, чтобы она больше не попадала в выборку.Рассмотрим пример запроса для фонового задания, который будет работать максимально быстро:
ВЫБРАТЬ ПЕРВЫЕ 50
Уведомления.ДокументОснование КАК Документ,
Уведомления.Адресат КАК Адресат,
Уведомления.КоличествоПопыток КАК КоличествоПопыток
ИЗ
РегистрСведений.ОчередьУведомлений КАК Уведомления
ГДЕ
Уведомления.ПлановаяДатаСледующейОбработки <= &ТекущаяДата
И Уведомления.ПлановаяДатаСледующейОбработки <> ДАТАВРЕМЯ(1, 1, 1)
И Уведомления.Статус <> ЗНАЧЕНИЕ(Перечисление.СтатусыУведомлений.Завершено)
ДЛЯ ИЗМЕНЕНИЯ
Обратите внимание на конструкцию ДЛЯ ИЗМЕНЕНИЯ (или использование блокировок), чтобы несколько потоков фонового задания не обрабатывали одну и ту же запись.
В обсуждении справедливо заметили, что регистр сведений не очень удобен для хранения текста ошибок, тела письма и истории переписки. Пытаться впихнуть все это в ресурсы регистра — плохая практика.
Давайте применим принцип разделения ответственности:
ИсходящееПисьмо или ЭлектронноеПисьмоИсходящее (если используется БСП).Когда срабатывает триггер из нашего регистра, мы программно создаем документ письма. В этом документе есть табличные части, поле для текста ошибки, вложения. Ссылку на этот последний созданный документ можно записать обратно в регистр (например, в ресурс ПоследнееСообщение), чтобы иметь быстрый доступ.
Это также решает проблему аудиторского следа. Если вам нужно посмотреть историю («сколько раз мы пинговали клиента?»), вы просто строите отчет по документам-письмам, связанным с реализацией.
Чтобы выборка в фоновом задании работала быстро даже на миллионах записей, необходимо правильно настроить индексы. Если вы используете подход с полем ПлановаяДатаСледующейОбработки, обязательно проиндексируйте это поле (свойство «Индексировать» в метаданных).
Если же вы делаете отбор по статусу, то поле Статус должно участвовать в индексе. Помните золотое правило 1С: если измерение стоит первым (например, ДокументОснование), а отбор вы делаете по ресурсу или второму измерению без указания первого, стандартный кластерный индекс может не сработать эффективно. Поэтому для реквизитов, по которым идет частый отбор в регламентных заданиях (Статус, Дата), лучше установить признак «Индексировать».
Условие «через 5 дней» часто подразумевает 5 рабочих дней. Если вы просто прибавите 5 * 86400 секунд, уведомление может выпасть на воскресенье, и клиент его пропустит или оно затеряется в спаме за выходные.
Для корректного расчета используйте производственный календарь. В конфигурациях на базе БСП есть общие модули для работы с графиками. Примерный алгоритм:
// Пример получения даты через 5 рабочих дней
ДатаСледующейОтправки = КалендарныеГрафики.ПолучитьДатуПоКалендарю(
ПроизводственныйКалендарь,
ТекущаяДата(),
5
);
Необходимо предусмотреть механизм остановки. Если клиент не подписывает документ после 5-го напоминания, возможно, стоит прекратить отправку писем и поставить задачу менеджеру «Позвонить клиенту». Для этого:
КоличествоОтправок.Если КоличествоОтправок >= 5 Тогда Статус = ТребуетЗвонка; Возврат; КонецЕсли;.Стоит упомянуть, что описанная логика «Действие -> Ожидание -> Проверка условия -> Действие» идеально ложится на карту маршрута бизнес-процесса (БП). В платформе 1С есть механизм задач, который позволяет «заморозить» процесс в точке маршрута на определенное время.
Когда стоит использовать БП, а когда регистр?
Для реализации системы повторных уведомлений:
СрезПоследних в пользу плоских таблиц (непериодических регистров).ПлановаяДата для построения эффективных очередей обработки.