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