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