Параллельное выполнение одинаковых блоков-заданий по визированию, если заранее не известно их количество и число ролей-исполнителей

Опубликовано:
23 августа в 07:55
  • 8

Введение

Точкой старта послужила одна из моих задач, где нужно было реализовать параллельное визирование в блоках-заданиях, если заранее не известно число блоков и число ролей, где ответ должен дать один из участников каждой роли.

Как известно, в типовых маршрутах для блоков-заданий можно назначать в качестве исполнителей роль, группу, пользователя, список пользователей, а вот назначить список ролей или список групп пользователей нельзя. То есть флажок "Прервать выполнение" в свойстве "Результаты выполнения" мне не подходит по той причине, что когда один из них выполнит действие в задании, у всех остальных задания прервутся, а мне нужно, чтобы хотя бы один участник каждой роли дал свой ответ.

Наиболее подходящим вариантом решения оказался - сделать программную отправку подзадач по другому ТМ (одна роль - 1 задача) и после завершения собрать результаты согласования.

Практически необходимо сделать пять шагов:

  1. Создать правила получения ролей при помощи пользовательского справочника
  2. Создать типовой маршрут для подзадач
  3. Отправить подзадачи визирующим по другому типовому маршруту (в маршруте будет один блок визирования документа) для каждой роли.Записать подзадачи в параметр типового маршрута. Сделать это можно, например, в блоке-сценарии.
  4. Разместить в схеме блок-мониторинг, задача которого будет ожидание завершения созданных на шаге 1 подзадач.
  5. После блока-мониторинга поставить блок, задача которого будет обработка результатов выполнения.
  6. Удаление активных подзадач в случае прекращения ведущей задачи

Итак, приступим.

Шаг 1. Создание правил получения ролей при помощи пользовательского справочника

Управлять ролями в типовом маршруте было решено при помощи пользовательского справочника "Матрица ролей".

Записи в нём - это правила назначения ролей. В каждой записи я указал следующие реквизиты:

  • Типовой маршрут (обязательный параметр)
  • Реквизит 1
  • Реквизит 2
  • Реквизит 3
  • ...
  • Реквизит N
  • Таблица ролей

Получилось примерно следующее:

Пользовательские реквизиты я закрасил, так как у вас они могут быть своими.

Шаг 2. Создание типового маршрута для подзадач

Создаем типовой маршрут с одним блоком задание. У вас может быть свой блок-задания - под ваши требования. Я назвал маршрут - "Визирование договорного документа".

Шаг 3. Отправка подзадач визирующим по другому типовому маршруту и запись подзадач в параметр типового маршрута

Располагаем на маршруте блок-сценарий, который будет отправлять подзадачи визирующим по другому типовому маршруту (в маршруте будет один блок визирования документа) для каждой роли.

В вычисления добавляем:

  
  // Список параметров типового маршрута
  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
  

, где

  • SightingPerfs - Список наименований визирующих ролей - Коллекция строк
  • DocForApproval - Согласуемый документ - Электронный документ
  • Initiator - Инициатор - Пользователь
  • SightingRoles - Визирующие роли - Коллекция ролей
  • SentSubTasksByRoles - Отправленные подзадачи для визирующих ролей - Коллекция задач
  • CountSightingRoles - CountSightingRoles - Число визирующих ролей - Целое число - 0

Шаг 4. Размещение и настройка блока Мониторинг

Обязательно добавляем на схему блок Мониторинг и блоки-условия по вашему усмотрению:

Проверить есть ли визирующие роли можно, например, так:


  // Список параметров типового маршрута
  Params = Object.WorkFlowParams
    
  // Визирующие роли
  SightingRoles = Params.ValueByName('SightingRoles').Value
   
  // Проверяет, есть ли визирующие пользователи
  Res = Assigned(SightingRoles)
  
  // Запоминаем результат в параметрах ТМ
  Params.ValueByName('SightingRolesPresent').Value = Res
  
  // Результат выполнения блока-условия
  Result = Res
  

В блоке-мониторинг настраиваем так, чтобы он ожидал завершения созданных подзадач (Тип мониторинга => Список зависимостей):

В событиях блока ничего прописывать дополнительно не надо.

Шаг 5. Обработка результатов выполнения

Тут зависит от вашей задачи. В моем случае была проверка завизировали ли роли электронный документ. Я решил проверять в вычислениях блока-условия:


  // Список параметров типового маршрута
  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
  

, где

  • SightingRolesPresent - Визирующие роли присутствуют - Логическое значение
  • SentSubTasksByRoles - Отправленные подзадачи для визирующих ролей - Коллекция задач
  • ExpAnswerSighting - Ожидаемый ответ визирующих - Строка
  • CountSightingRoles - Число визирующих ролей - Целое число - 0
  • RolesAgreed - Роли согласовали - Логическое значение

Шаг 6. Обработка подзадач в случае прекращения ведущей задачи

На этом шаге добавляем прекращение активных подзадач при попытке прекращения главной задачи, тем самым защитив сами подзадачи от случайного или злонамеренного прекращения.

В событие "Прекращение" ведущей задачи добавляем следующее:

 
  // Список параметров типового маршрута
  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
  

, где

  • SentSubTasksByRoles - Отправленные подзадачи для визирующих ролей - Коллекция задач

Подводя итоги

Цели задачи достигнуты, хотя и не быстрыми решениями "из коробки".

Отдельное спасибо Юлии Литвинюк за подсказку и общий принцип решения моего вопроса.

Остальным - спасибо за прочтение этой статьи.

7
Подписаться

Комментарии

Хотелось бы добавить, что для снижения нагрузки на Workflow (если такая потребность есть) можно вместо блока мониторинга использовать блок Пауза, появившийся в DIRECTUM 5.5. Однако, в таком случае необходимо добавлять в ТМ для подзадач проверку, что нет других подзадач от ведущей задачи по этому ТМ, и снятие с паузы ведущей задачи.

Александр, тип мониторинга = "Список зависимостей" не нагружает Workflow, т.к. в этом случае нет периодического выполнения с заданным интервалом. Тип мониторинга = "Список зависимостей" работает по другому:

  1. При обработке службой Workflow блока мониторинга в таблицу SBWorkflowDependencies добавляется список зависимых друг от друга задач по блоку мониторинг.
  2. Записи удаляются из таблицы при завершении, прекращении, удалении задачи с блоком мониторинг и задач, от которых она зависит.

Подробнее можно почитать в этой статье.

Фёдор, спасибо за полезный материал  В будущем при аналогичных вопросах будем ссылку на эту статью отправлять 

Юлия Литвинюк, странно, насколько я помню, при решении одной задачи было недостаточно использовать Мониторинг со списком зависимостей, помог тогда только блок Пауза, поэтому и написал. Возможно, были еще факторы, которые в совокупности улучшили ситуацию, а у меня в голове отложилась только замена блока. ¯\_(ツ)_/¯

Фёдор, для пущей стройности добавьте ещё события проверки наличия активных подзадач при попытке прекращения главной задачи и защитите сами подзадачи от случайного или злонамеренного прекращения.

Федор, еще одно небольшое улучшение кода (уж больно мне такое глазки режет ). Вместо

  // Определяем результат согласования
  If Res
    Params.ValueByName('RolesAgreed').Value = True
  Else
    Params.ValueByName('RolesAgreed').Value = False
  EndIf

проще писать

  // Определяем результат согласования
  Params.ValueByName('RolesAgreed').Value = Res

Такая же конструкция встречается в проверке есть ли визирующие роли 

Юлия, согласен ) Поправил 

Артем, спасибо за совет.

Сначала не совсем его понял, т.к. если инициатор для ведущей задачи и подзадач один и тот же то система сама "из коробки" предлагает прекратить подзадачи. Но тут есть минус - у него в исходящих накапливаются отправленные задачи и подзадачи, что может быть не совсем логично для пользователя и вводить его в заблуждение. В случае, если инициатором подзадач выступает служебный пользователь, то диалоговое окно не появляется и прекращается только ведущая задача. Вот тут то и потребуется эта проверка и программное прекращение подзадач при необходимости.

UPD: Обновил статью.

Фёдор Сироткин: обновлено 04.09.2019 в 14:18
Авторизуйтесь, чтобы написать комментарий