Точкой старта послужила одна из моих задач, где нужно было реализовать параллельное визирование в блоках-заданиях, если заранее не известно число блоков и число ролей, где ответ должен дать один из участников каждой роли.
Как известно, в типовых маршрутах для блоков-заданий можно назначать в качестве исполнителей роль, группу, пользователя, список пользователей, а вот назначить список ролей или список групп пользователей нельзя. То есть флажок "Прервать выполнение" в свойстве "Результаты выполнения" мне не подходит по той причине, что когда один из них выполнит действие в задании, у всех остальных задания прервутся, а мне нужно, чтобы хотя бы один участник каждой роли дал свой ответ.
Наиболее подходящим вариантом решения оказался - сделать программную отправку подзадач по другому ТМ (одна роль - 1 задача) и после завершения собрать результаты согласования.
Практически необходимо сделать пять шагов:
Итак, приступим.
Управлять ролями в типовом маршруте было решено при помощи пользовательского справочника "Матрица ролей".
Записи в нём - это правила назначения ролей. В каждой записи я указал следующие реквизиты:
Получилось примерно следующее:
Пользовательские реквизиты я закрасил, так как у вас они могут быть своими.
Создаем типовой маршрут с одним блоком задание. У вас может быть свой блок-задания - под ваши требования. Я назвал маршрут - "Визирование договорного документа".
Располагаем на маршруте блок-сценарий, который будет отправлять подзадачи визирующим по другому типовому маршруту (в маршруте будет один блок визирования документа) для каждой роли.
В вычисления добавляем:
// Список параметров типового маршрута
Params = Object.WorkFlowParams
// Очистка коллекци отправленных имен ролей
SightingPerfs = Params.ValueByName('SightingPerfs')
IndexRole = 0
While IndexRole < SightingPerfs.Count
// Очистка элемента коллекци отправленных наименований визирующих ролей
SightingPerfs.Values(IndexRole) = Null
// Перебор индексов роли
IndexRole = IndexRole + 1
EndWhile
// Согласуемый документ
DInfo = Params.ValueByName('DocForApproval').Value
// Документ
Document = DInfo.Document
// Инициатор
Initiator = Params.ValueByName('Initiator').Value
// Хотя бы одна роль есть в параметре ТМ?
IndexSightingRole = Params.IndexOfName('SightingRoles')
If IndexSightingRole > -1
// Перебор указанных ролей
IndexSightingRole = 0
SightingRoles = Params.ValueByName('SightingRoles')
While IndexSightingRole < SightingRoles.Count
// Роль IUser
Role = SightingRoles.Values(IndexSightingRole)
// Имя роли
RoleRefName = Role.Name
// Код роли
RoleRefCode = Role.Code
// Добавление ролей в коллекцию
SightingPerfs = Params.ValueByName('SightingPerfs')
If SightingPerfs.IsCollection
SightingPerfs.Values(SightingPerfs.Count) = RoleRefName
EndIf
// Для каждой роли создаём подзадачу по ТМ
// роль передаём в параметр маршрута с 1 блоком-заданием для этой роли
// Создать подзадачу к задаче
Task = Tasks.CreateSubTaskToTask(Object)
// Код типового маршрута из справочника Типовые маршруты.
RouteCode = 'ВДД'
// Загрузить типовой маршрут
Task.LoadStandardRoute(RouteCode)
// Заполнение параметров типового маршрута
// Инициатор
// Task.Author = Initiator.Code
// Визирующая роль
Role = ServiceFactory.GetRoleByCode(RoleRefCode)
indexRole = Task.WorkflowParams.IndexOfName('SightingArbitraryRole')
Task.WorkflowParams.Values(indexRole).Value = Role
// Согласуемый документ
indexDoc = Task.WorkflowParams.IndexOfName('DocForApproval')
Task.WorkflowParams.Values(indexDoc).Value = DInfo
// Установить параметры типового маршрута
Task.SetupStandardRoute(True)
// Стартовать задачу
Task.Start
// Добавление подзадач в коллекцию для мониторинга
SentSubTasksByRoles = Params.ValueByName('SentSubTasksByRoles')
If SentSubTasksByRoles.IsCollection
SentSubTasksByRoles.Values(SentSubTasksByRoles.Count) = Task.Info
EndIf
// Перебор индексов роли
IndexSightingRole = IndexSightingRole + 1
EndWhile
// Запоминаем число ролей, если не запомнили раньше
If Params.ValueByName('CountSightingRoles').Value == 0
Params.ValueByName('CountSightingRoles').Value = SightingPerfs.Count
EndIf
EndIf
Здесь
Обязательно добавляем на схему блок Мониторинг и блоки-условия по вашему усмотрению:
Проверить есть ли визирующие роли можно, например, так:
// Список параметров типового маршрута
Params = Object.WorkFlowParams
// Визирующие роли
SightingRoles = Params.ValueByName('SightingRoles').Value
// Проверяет, есть ли визирующие пользователи
Res = Assigned(SightingRoles)
// Запоминаем результат в параметрах ТМ
Params.ValueByName('SightingRolesPresent').Value = Res
// Результат выполнения блока-условия
Result = Res
В блоке-мониторинг настраиваем так, чтобы он ожидал завершения созданных подзадач (Тип мониторинга => Список зависимостей):
В событиях блока ничего прописывать дополнительно не надо.
Тут зависит от вашей задачи. В моем случае была проверка завизировали ли роли электронный документ. Я решил проверять в вычислениях блока-условия:
// Список параметров типового маршрута
Params = Object.WorkFlowParams
// Присутствие визирующих пользователей
SightingUsersPresent = Params.ValueByName('SightingRolesPresent').Value
// Если визирующие пользователи не присутствуют, то идет дольше по ТМ
If SightingUsersPresent
// Созданные подзадачи
SentSubTasks = Params.ValueByName('SentSubTasksByRoles')
// Ожидаемый ответ визирующих
ExpAnswer = Params.ValueByName('ExpAnswerSighting').Value
// Ожидаемое число согласований
CountSighting = Params.ValueByName('CountSightingRoles').Value
// Определяет все ли визирующие роли дали положительный ответ
Res = CheckAnswersSightingsFromTasks(SentSubTasks; ExpAnswer; CountSighting)
Else
// Визирующих нет, поэтому проходим дальше по ТМ
Res = True
EndIf
// Определяем результат согласования
Params.ValueByName('RolesAgreed').Value = Res
// Результат выполнения блока-условия
Result = Res
, где
На этом шаге добавляем прекращение активных подзадач при попытке прекращения главной задачи, тем самым защитив сами подзадачи от случайного или злонамеренного прекращения.
В событие "Прекращение" ведущей задачи добавляем следующее:
// Список параметров типового маршрута
Params = Object.WorkFlowParams
// Созданная коллекция подзадач
ListSubTasks = Params.ValueByName('SentSubTasksByRoles')
// Индекс текущей подзадачи
Index = 0
// Общее число подзадач
ListSubTasksCount = ListSubTasks.Count
// Перебор подзадач
While Index < ListSubTasksCount
// Информация о подзадаче
TaskInfo = ListSubTasks.Values(Index)
// Текущая подзадача
Task = TaskInfo.Task
// Прекращение задачи, если необходимо
If Not (Task.TaskState == 'Выполнена' Or Task.TaskState == 'Прекращена')
Task.Abort
EndIf
// Перебор индексов подзадач
Index = Index + 1
EndWhile
Здесь:
Цели задачи достигнуты, хотя и не быстрыми решениями "из коробки".
Отдельное спасибо Юлии Литвинюк за подсказку и общий принцип решения моего вопроса.
Остальным - спасибо за прочтение этой статьи.
Хотелось бы добавить, что для снижения нагрузки на Workflow (если такая потребность есть) можно вместо блока мониторинга использовать блок Пауза, появившийся в DIRECTUM 5.5. Однако, в таком случае необходимо добавлять в ТМ для подзадач проверку, что нет других подзадач от ведущей задачи по этому ТМ, и снятие с паузы ведущей задачи.
Александр, тип мониторинга = "Список зависимостей" не нагружает Workflow, т.к. в этом случае нет периодического выполнения с заданным интервалом. Тип мониторинга = "Список зависимостей" работает по другому:
Подробнее можно почитать в этой статье.
Фёдор, спасибо за полезный материал В будущем при аналогичных вопросах будем ссылку на эту статью отправлять
Юлия Литвинюк, странно, насколько я помню, при решении одной задачи было недостаточно использовать Мониторинг со списком зависимостей, помог тогда только блок Пауза, поэтому и написал. Возможно, были еще факторы, которые в совокупности улучшили ситуацию, а у меня в голове отложилась только замена блока. ¯\_(ツ)_/¯
Фёдор, для пущей стройности добавьте ещё события проверки наличия активных подзадач при попытке прекращения главной задачи и защитите сами подзадачи от случайного или злонамеренного прекращения.
Федор, еще одно небольшое улучшение кода (уж больно мне такое глазки режет ). Вместо
проще писать
Такая же конструкция встречается в проверке есть ли визирующие роли
Юлия, согласен ) Поправил
Артем, спасибо за совет.
Сначала не совсем его понял, т.к. если инициатор для ведущей задачи и подзадач один и тот же то система сама "из коробки" предлагает прекратить подзадачи. Но тут есть минус - у него в исходящих накапливаются отправленные задачи и подзадачи, что может быть не совсем логично для пользователя и вводить его в заблуждение. В случае, если инициатором подзадач выступает служебный пользователь, то диалоговое окно не появляется и прекращается только ведущая задача. Вот тут то и потребуется эта проверка и программное прекращение подзадач при необходимости.
UPD: Обновил статью.
Не поняла, зачем такие невероятные сложности - дополнительный ТМ, подзадачи... Есть блок задание, у него можно поставить "Параллельное выполнение=Да". Поскольку исполнителя типа "Список ролей" не существует (а когда я обратилась в техподдержку, меня попытались убедить, что это не нужно и можно обойтись, например, специально создаваемой для ТМ группой пользователей, что, ИМХО, совершенно неприемлемо), я решила проблему так: ставлю в блоке "Задание" исполнителя "Список пользователей", создаю в задаче параметр этого типа, и перед "Заданием" ставлю блок "Сценарий", в вычислении которого сначала в явном виде задаю список ролей, часть из которых добавляется в зависимости от каких-то условий, а потом в цикле по этим ролям вычисляю исполнителей и добавляю их в тот самый параметр типа "Список пользователей". Допустимые результаты задания - "Завизировано" и еще какой-нибудь, например "На доработку". И всё! Блок "Задание" будет выполнен, когда свои задания выполнят ВСЕ исполнители из списка, а исходящих соединений - всего два: "Завизировано" и "Иначе".
Если вот такое - явное, в тексте сценария - заполнение списка наименований ролей почему-то неприемлемо, то можно заполнить как раз через справочник, который предложил Федор. Хотя я не вижу смысла, ибо, если бы был в ТМ доступен исполнитель "Список ролей", то роли тоже задавались бы при настройке ТМ, а не извне.
Авторизуйтесь, чтобы написать комментарий