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