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