Построение регламента в разработанных задаче и справочнике (или документе)

25 7

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

Поэтому для удобства работы пользователя и ускорения поиска нужной информации имеет смысл настроить вкладку "Регламент" самой задачи и вкладку "Задачи" для записи справочника (для отображения блоков выполнения заданий). Данная вкладка будет содержать Контрол состояния, который будет содержать все необходимые данные (можно настроить под свою задачу).

Примечание: далее описывается построение регламента для справочника, но построение аналогично построению для документа.

Было принято решение, что будет происходить работа со справочником, а не документом, так как для работы не требуется текст документа. Пример карточки записи такого справочника:

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

Построение модели в задаче

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

Настройка вкладки "Регламент" в задаче

Для настройки отображения схемы в задаче, вам необходимо выполнить следующие шаги:

  1. Создать вкладку "Регламент".
  2. Добавить Группу контролов на созданную вкладку.
  3. Добавить Контрол состояния в Группу контролов.
  4. Создать функцию для формирования результата для отображения.

Разработка StateView для отображения схемы

В результате создана функция GetApprovalVacantPostTaskState(), которая будет отвечать за отображение схемы в Контроле состояния, но перед основной разработкой, правильнее будет указать функцию GetStateViewVacantPost, которую ищет для отрисовки справочник. Итак, в разработанной задаче в разделе серверных функций укажите:

    // <summary>
    /// Построить модель состояния.
    /// </summary>
    /// <param name="databook">Справочник.</param>
    /// <returns>Схема модели состояния.</returns>
    public Sungero.Core.StateView GetStateViewVacantPost(IProcedureFillVacantPostsDatabook databook)
    {
      if (_obj.ProcedureFillVacantPostsDatabook.Equals(databook))
        return this.GetApprovalVacantPostTaskState();
      else
        return StateView.Create();
    }

Если наша задача имеет ссылку на справочник, который был передан, то вызывается функция для построения модели - GetApprovalVacantPostTaskState.

И универсальный вариант для отображения модели будет представлен ниже:

    /// <summary>
    /// Регламент задачи согласования
    /// </summary>
    [Remote(IsPure = true)]
    public StateView GetApprovalVacantPostTaskState()
    {
      var stateView = StateView.Create();
      StateBlock blockAssignment;
      
      //Получение всех зависимых заданий
      var assignments = Sungero.Workflow.Assignments.GetAll()
        .Where(a => Equals(a.Task, _obj))
        .OrderBy(y => y.Created);
      
      //Блок о действии
      Sungero.Docflow.PublicFunctions.OfficialDocument
        .AddUserActionBlock(stateView,
                            _obj.Author,
                            _obj.Started.HasValue ?
                            Sungero.Docflow.ApprovalTasks.Resources.StateViewDocumentSentForApproval :
                            Sungero.Docflow.ApprovalTasks.Resources.StateViewTaskDrawCreated,
                            _obj.Created.Value,
                            _obj, 
                            string.Empty,
                            _obj.Author);
      
      // Добавить основной блок для задачи.
      var taskBlock = this.AddTaskBlock(stateView);
      
      //Добавление блока по заданию
      foreach (var assignment in assignments)
      {
        blockAssignment = AddBlockAssignment(stateView, true, taskBlock, assignment, false);
      }
      
      return stateView;
    }

Блок о действии включает информацию о том, кто задачу создал и когда - эта информация высвечивается над блоком задачи.

Основным блоком является блок задачи (вызов AddTaskBlock), а уже ниже вложенными отображаются подблоки заданий (вызов AddBlockAssignment).

Блок задачи строится следующим образом:

    /// <summary>
    /// Добавить основной блок задачи согласования.
    /// </summary>
    /// <param name="stateView">Схема представления.</param>
    /// <returns>Добавленный блок.</returns>
    private StateBlock AddTaskBlock(StateView stateView)
    {
      var taskBlock = stateView.AddBlock();
      
      var isDraft = _obj.Status == Sungero.Workflow.Task.Status.Draft;
      var headerStyle = Sungero.Docflow.PublicFunctions.Module.CreateHeaderStyle(isDraft);
      var labelStyle = Sungero.Docflow.PublicFunctions.Module.CreateStyle(false, isDraft, false);
      
      taskBlock.Entity = _obj;
      taskBlock.AssignIcon(Sungero.Docflow.OfficialDocuments.Info.Actions.SendForFreeApproval, StateBlockIconSize.Large);
      taskBlock.IsExpanded = _obj.Status == Sungero.Workflow.Task.Status.InProcess;
      taskBlock.AddLabel(_obj.Subject, headerStyle);
      
      var status = string.Empty;
      if (_obj.Status == Sungero.Workflow.Task.Status.InProcess)
        status = Sungero.Docflow.ApprovalTasks.Resources.StateViewInProcess;
      else if (_obj.Status == Sungero.Workflow.Task.Status.Completed)
        status = Sungero.Docflow.ApprovalTasks.Resources.StateViewCompleted;
      else if (_obj.Status == Sungero.Workflow.Task.Status.Aborted)
        status = Sungero.Docflow.ApprovalTasks.Resources.StateViewAborted;
      else if (_obj.Status == Sungero.Workflow.Task.Status.Suspended)
        status = Sungero.Docflow.ApprovalTasks.Resources.StateViewSuspended;
      else if (_obj.Status == Sungero.Workflow.Task.Status.Draft)
        status = Sungero.Docflow.ApprovalTasks.Resources.StateViewDraft;
      
      var taskText = _obj.ActiveText;
      if (taskText != string.Empty)
      {
        taskBlock.AddLineBreak();
        taskBlock.AddLabel("_______________________", labelStyle);
        taskBlock.AddLineBreak();
        taskBlock.AddLabel(taskText);
      }
      
      Sungero.Docflow.PublicFunctions.Module.AddInfoToRightContent(taskBlock, status, labelStyle);
      
      return taskBlock;
    }

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

Далее к основному блоку задачи добавляем подблоки с информацией по заданиям:

/// <summary>
    /// Добавление блока
    /// </summary>
    /// <param name="stateView">Модель</param>
    /// <param name="isShowBorder">Линия блока</param>
    /// <param name="mainBlock">Главный блок</param>
    /// <param name="assignment">Сущность (задание)</param>
    /// <param name="isExpanded">Признак того, что дочерние блоки развернуты</param>
    /// <returns></returns>
    [Public]
    public static Sungero.Core.StateBlock AddBlockAssignment(StateView stateView, bool isShowBorder, Sungero.Core.StateBlock mainBlock, Sungero.Workflow.IAssignment assignment, bool isExpanded)
    {
      StateBlockLabelStyle style = StateBlockLabelStyle.Create();
      StateBlockLabelStyle styleHeader = StateBlockLabelStyle.Create();
      StateBlockLabelStyle styleBoottom = StateBlockLabelStyle.Create();
      styleBoottom.Color = Colors.Common.Gray;
      StateBlock block;
      
      if (mainBlock != null)
        block = mainBlock.AddChildBlock();
      else
        block = stateView.AddBlock();
      
      block.AddLabel(assignment.Subject, styleHeader);
      
      string position = Sungero.Company.Employees.Is(assignment.Performer) &&
        Sungero.Company.Employees.As(assignment.Performer).JobTitle != null ?
        " (" + Sungero.Company.Employees.As(assignment.Performer).JobTitle.Name + ")":
        string.Empty;
      
      string label = string.Format("{0, -1}{1}", assignment.Performer.Name, position);
      
      if (assignment.Deadline.HasValue)
        label = SC.VariousProcessesModule.ApprovalBookingRequestTasks.Resources.DeadlineFormat(label, assignment.Deadline.Value.ToShortDateString());
      
      //Создано
      if (assignment.Created.HasValue)
      {
        block.AddLineBreak();
        block.AddLabel(SC.VariousProcessesModule.ApprovalBookingRequestTasks.Resources.CreatedFormat(assignment.Created.Value), styleBoottom);
      }
      //Участник с должностью
      block.AddLineBreak();
      block.AddLabel(SC.VariousProcessesModule.ApprovalBookingRequestTasks.Resources.ToWhomFormat(label), styleBoottom);      
      
      block.ShowBorder = isShowBorder;
      block.Entity = assignment;
      block.IsExpanded = isExpanded;      
      
      if (assignment.Status == Sungero.Workflow.Task.Status.InProcess)
      {
        style.Color = Colors.Common.Gray;
      }
      
      if (assignment.ActiveText != null)
      {
        block.AddLineBreak();
        block.AddLabel(SC.VariousProcessesModule.ApprovalBookingRequestTasks.Resources.CommentFormat(assignment.ActiveText), style);
      }
            
      if (assignment.Status == Sungero.Workflow.Task.Status.InProcess)
      {
        block.BorderColor = Colors.Common.DarkBlue;
      }
      if (assignment.Status == Sungero.Workflow.Task.Status.InProcess &&
          assignment.Deadline.HasValue &&
          assignment.Deadline < Calendar.Now)
      {
        style.Color = Colors.Common.Red;
        styleBoottom.Color = Colors.Common.Red;
        styleHeader.Color = Colors.Common.Red;
        block.Background = Colors.FromRgb(255, 200, 200);
      }
      
      //Статус
      var column = block.AddContent();
      if (assignment.Result != null)
        column.AddLabel(assignment.Info.Properties.Result.GetLocalizedValue(assignment.Result), style);
      else
        column.AddLabel(assignment.Info.Properties.Status.GetLocalizedValue(assignment.Status.Value), style);
      
      block.AssignIcon(StateBlockIconType.OfEntity, StateBlockIconSize.Large);
      
      return block;
    }    

При формировании блока задания указывается дополнительный текст под заголовком: время создания, исполнитель, комментарий при наличии, а в правой колонке отображается, или результат выполнения задания, или статус (если результата нет), также, если идет просрочка, то блок выделяется красным.

Схема во вкладке будет выглядеть следующим образом:

Если у задания идет просрочка:

Построение модели в заданиях задачи

Для отображения модели в заданиях разработанной задачи достаточно создать вкладку, добавить Контрол состояния, создать функцию (см. пункт "Настройка вкладки "Регламент" в задаче"), и вызвать основную функцию для отрисовки, которая прописана в задаче:

    /// <summary>
    /// Вызов модели из основной задачи
    /// </summary>
    [Remote]
    public StateView GetAcquaintanceVacantPostAssignmentState()
    {
      var stateView = StateView.Create();
      var task = ApprovalVacantPostTasks.As(_obj.Task);
      if (task != null)
        stateView = Functions.ApprovalVacantPostTask.GetApprovalVacantPostTaskState(task);
      return stateView;
    }

Общая последовательность построения модели для документа OfficialDocument

Вкладка Tasks (Задачи) во всех документах, которые наследуются от OfficialDocument, строится следующим образом:

  1. Переполучение электронного документа для отображения, когда документ еще не сохранен (после смены типа) в GetStateViewXml.
  2. Построение модели состояния документа в GetStateView с передачей документа. Одноименная функция должна быть разработана в задаче ApprovalVacantPostTask (при построении задач в документе происходит обращение к одноименной функции в задаче).
  3. Поиск всех задач, в которые вложен этот документ в AddTasksViews.
  4. Добавление информации о найденной задаче (из пункта 3) в AddTaskViewXml.

Настройка отображения в справочнике

Чтобы ваши задачи отображались в базовой вкладке Tasks (Задачи) какого-либо документа, настройка в документе не требуется, так как достаточно прописать в вашей ЗАДАЧЕ  функцию GetStateView с передачей документа (см. пункт "Разработка StateView для отображения схемы").

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

Создадим вкладку "Задачи", добавим Группу контролов, Контрол состояния и создадим функцию (см. пункт "Настройка вкладки "Регламент" в задаче"). Функция GetProcedureFillVacantPostsDatabookState() будет создана в серверном разделе и вся последующая разработка будет там.

Функция, которую мы только создали, имеет следующий вид:

    /// <summary>
    /// Отображение задач по согласованию записи справочника.
    /// </summary>
    /// <returns>История согласования.</returns>
    [Remote(IsPure = true)]
    public Sungero.Core.StateView GetProcedureFillVacantPostsDatabookState()
    {
      // Переполучить справочник для отображения ПО, когда справочник еще не сохранен.
      var databook = ProcedureFillVacantPostsDatabooks.GetAll(a => a.Id == _obj.Id).FirstOrDefault();
      if (databook != null)
        return GetStateView(databook);
      
      return GetStateView(_obj);
    }

Если вакансия найдена, то для нее выполнится построение Контрола состояния (если задач для отображения найдено не будет, то будет отображаться текст по умолчанию):

    /// <summary>
    /// Построить модель состояния справочника.
    /// </summary>
    /// <param name="databook">Справочник.</param>
    /// <returns>Схема модели состояния.</returns>
    /// <remarks>По идее, одноименная функция ожидается у всех сущностей, которым нужно представление состояния.</remarks>
    [Public]
    public static Sungero.Core.StateView GetStateView(IProcedureFillVacantPostsDatabook databook)
    {
      var stateView = StateView.Create();
      stateView.AddDefaultLabel(Sungero.Docflow.OfficialDocuments.Resources.StateViewDefault);
      AddTasksViews(stateView, databook);
      stateView.IsPrintable = true;
      return stateView;
    }

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

В функции AddTasksViews идет поиск задач для отображения во вкладке (можно было сделать поиск по всем задачам и проверять свойство, но так как требуется отображать схему только для выбранных задач, то tasks вычисляется так, как указано ниже):

    /// <summary>
    /// Добавить информацию о задачах, в которые вложен справочник.
    /// </summary>
    /// <param name="stateView">Схема представления.</param>
    /// <param name="databook">Справочник.</param>
    private static void AddTasksViews(StateView stateView, IProcedureFillVacantPostsDatabook databook)
    {
      var tasks = ApprovalVacantPostTasks.GetAll()
        .Where(task => task.ProcedureFillVacantPostsDatabook.Equals(databook))
        .OrderBy(task => task.Created)
        .ToList();
      
      foreach (var task in tasks)
      {
        if (stateView.Blocks.Any(b => b.HasEntity(task)))
          continue;
        
        AddTaskViewXml(stateView, task, databook);
      }
    }

Построение регламента для найденной задачи:

    /// <summary>
    /// Построение модели задачи, в которую вложен справочник.
    /// </summary>
    /// <param name="stateView">Схема представления.</param>
    /// <param name="task">Задача.</param>
    /// <param name="databook">Справочник.</param>
    private static void AddTaskViewXml(StateView stateView, Sungero.Workflow.ITask task, IProcedureFillVacantPostsDatabook databook)
    {
      // Добавить предметное отображения для прикладных задач.
      var taskStateView = Sungero.Docflow.PublicFunctions.Module.GetServerEntityFunctionResult(task, "GetStateViewVacantPost", new List<object>() { databook });
      if (taskStateView != null)
      {
        // Избавиться от дублирующих блоков, если таковые были.
        List<StateBlock> blockWhiteList = new List<StateBlock>() { };
        
        foreach (var block in ((StateView)taskStateView).Blocks)
        {
          if (block.Entity == null || !stateView.Blocks.Any(b => b.HasEntity(block.Entity)))
            blockWhiteList.Add(block);
        }
        
        foreach (var block in blockWhiteList)
          stateView.AddBlock(block);
      }
    }

В функцию AddTaskViewXml передается задача и некоторая сущность (обычно это документ, но в данном случае справочник), далее в этой задаче происходит поиск функции с наименованием GetStateView, которая принимает конкретно эту сущность и, если такая функция найдена, то она возвращает StateView модель, которая далее и строится во вкладке.

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

Павел Логинов

Расскажите пожалуйста что делают функции
SC.VariousProcessesModule.ApprovalBookingRequestTasks.Resources.DeadlineFormat(label, assignment.Deadline.Value.ToShortDateString())
SC.VariousProcessesModule.ApprovalBookingRequestTasks.Resources.CreatedFormat(assignment.Created.Value)

SC.VariousProcessesModule.ApprovalBookingRequestTasks.Resources.ToWhomFormat(label)


 

Павел Логинов

Еще не хватает обработки подзадач и данное решение не показывает в контроле состояния отправленные уведомления

Mikhail Popkov

Поясните плиз происхождение абривиатуры SC if (assignment.Deadline.HasValue) label = SC.VariousProcessesModule.ApprovalBookingRequestTasks?

Дмитрий Рудко

Присоединяюсь к вопросу Павла.

https://club.directum.ru/post/319040#doc320925

Дарья Овчаренко

Павел, нужно было мне подробнее расписать. Deadline имеет значение "{0}. Срок: {1}" и используется затем в ToWhomFormat, у которого значение "Кому: {0}" - при использовании в блоке добавляется подпись формата: "Кому: <ФИО исполнителя> (<Должность>). Срок: <Срок из задания в формате даты без времени>".
Created имеет значение "Создано: {0}" - проставляется до строки "Кому"

Дарья Овчаренко

Павел, спасибо за замечание

Дарья Овчаренко

Mikhail, SC - код компании, подставляется автоматически при создании объекта в системе, VariousProcessesModule - созданный модуль

Авторизуйтесь, чтобы написать комментарий