Модификация "Голосование" для задачи согласования по регламенту

30 2

Цель: Добавить возможность голосования в Directum RХ по протоколу совещания.


Задача

В задачу согласования по регламенту настроить этап «Задание» с возможность голосования по пунктам решений, добавленных в карточке протокола совещания. Отображение состояния и статистики голосования в соответствующей строке решения.

Требуемые модификации:

1.    В карточку вида документа добавить чек-бокс «Требуется голосование».
2.    Создать справочник «Матрицы согласования», содержащий перечень голосующих.
3.    В карточку протокола совещания добавить вкладку «Решения». Коллекция решений имеет автоматическую нумерацию с возможностью внесения текста решения, выбора матрицы, отображения состояния результатов голосования.
4.    В карточку этапа «Задание» добавить чек-бокс «Голосование».
5.    В карточку задания добавить коллекцию, заполняемую текстами решений из протокола совещания с возможностью голосовать «За», «Против», «Воздержался».
6.    В карточке задачи на согласование по регламенту отображать закрытое поле, содержащее список голосующих.
7.    Доработать схему задачи на согласование для заполнения коллекции решений в задании.
8.    Написать асинхронный обработчик передачи результатов голосования из задания в карточку протокола совещания.
9.    Создать роль согласования «Участники голосования», для выбора ее в этапе задания.
Модификации должны учитывать ограничения по типу сущности: проверки соответствия настроек вида документа и типа этапа, видимости полей, фильтрацию выбора, обработку запуска задач, блокировки сущностей.

Код и Постфикс компании: centrvd

Имя решение: Solution

Имя модуля: SolutionDatabook

Вид документа

Перекрыть справочник видов документов (DocumentKind), добавить логическое свойство «Требуется голосование» (NeedVoitingcentrvd). MeetingMinutesTypeGuid – константа для хранения GUID’a базового типа Minutes.

На событии «Показ формы» написать отображение, только для типа «Протокол совещания»

public override void Showing(Sungero.Presentation.FormShowingEventArgs e)
{
  base.Showing(e);
  
  //Видимость чекбокса Требуется голосование только для протоколов совещаний.
  _obj.State.Properties.NeedVoitingcentrvd.IsVisible = _obj.DocumentType != null ? _obj.DocumentType.DocumentTypeGuid.Equals(SolutionDataBook.PublicConstants.Module.MeetingMinutesTypeGuid.ToString()) : false;
}

При смене типа необходимо очищать значение:

public override void DocumentTypeValueInput(Sungero.Docflow.Client.DocumentKindDocumentTypeValueInputEventArgs e)
{
  base.DocumentTypeValueInput(e);

  if (!Equals(e.NewValue, e.OldValue))
  {
  _obj.NeedVoitingcentrvd = false;
  }
}

Матрица голосования

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

  • Name (наименование матрицы голосования).
  • Approvers (коллекцию голосующих (ссылка на Employee) с множественным выбором).

Выдать права всем пользователям на чтение. Создавать матрицы смогут Администраторы:

/// <summary>
/// Выдача прав на справочник Матрицы голосования.
/// </summary>
public static void GrantRightsOnApprovalMatrix()
{
  var allUsers = Roles.AllUsers;
  centrvd.SolutionDataBook.ApprovalMatrices.AccessRights.Grant(allUsers, DefaultAccessRightsTypes.Read);
  centrvd.SolutionDataBook.ApprovalMatrices.AccessRights.Save();
}

Протокол совещания

Перекрыть протокол совещания (Minutes). Добавить вкладку «Решения» (Decisions) в карточку протокола. Во вкладку добавить коллекцию (Decisionscentrvd) записей по решениям со свойствами:

  • ID (скрытое поле)
  • Number (порядковый номер решения)
  • Decision (текст решения)
  • Matrix (ссылка за запись справочника ApprovalMatrix)
  • Votings (текстовое представление коротких имен голосующих, формируется при изменении Матрицы)
  • Result (авто формируемый текст результатов голосования)
  • VoteFor (скрытое поле, содержащее количество проголосовавших «За»)
  • VoteAgains (скрытое поле, содержащее количество проголосовавших «Против»)
  • VoteAbstain (скрытое поле, содержащее количество проголосовавших «Воздержался»)
  • VotersEmployeesIds (скрытое поле, содержащее ID голосующих)

На «Показе формы» добавить условие видимости и блокировки вкладки, если есть запущенные задачи:

public override void Showing(Sungero.Presentation.FormShowingEventArgs e)
{
  base.Showing(e);
  _obj.State.Pages.Decisions.IsVisible = centrvd.Solution.DocumentKinds.As(_obj.DocumentKind).NeedVoitingcentrvd.GetValueOrDefault();
  _obj.State.Properties.Decisionscentrvd.IsEnabled = !Functions.Minutes.AnyVoitingMinutesTasks(_obj);
}

Добавим разделяемую функцию AnyVoitingMinutesTasks():

/// <summary>
/// Есть ли активные задачи с этапом голосование по протоколу с видом, требующим голосование.
/// </summary>
public virtual bool AnyVoitingMinutesTasks()
{
  var documentsGroupGuid = Sungero.Docflow.PublicConstants.Module.TaskMainGroup.ApprovalTask;
  var votingStage = centrvd.Solution.ApprovalStage.CustomStageTypecentrvd.Voting;
  
  var minutesTasks = centrvd.Solution.ApprovalTasks.GetAll()
.Where(t => t.Status == Sungero.Docflow.ApprovalTask.Status.InProcess)
.Where(t => t.AttachmentDetails.Any(g => g.GroupId == documentsGroupGuid && _obj.Id == g.AttachmentId));

  var hasVotingStage = false;
  foreach (var task in minutesTasks)
  {
  if (Functions.ApprovalTask.HasCustomStage(task, votingStage))
    {
      hasVotingStage = true;
      break;
    }
  }

  return centrvd.Solution.DocumentKinds.As(_obj.DocumentKind).NeedVoitingcentrvd.GetValueOrDefault() && hasVotingStage;
}

* Функцию HasCustomStage() добавим позже в задаче на согласование.

При добавлении в коллекцию прописать автоматическую нумерацию, т.к. пользователи не могут менять порядковый номер. По желанию можно добавить текст «Не проводилось» в результаты голосования.

public virtual void DecisionscentrvdAdded(Sungero.Domain.Shared.CollectionPropertyAddedEventArgs e)
{
  _added.Number = (_obj.Decisionscentrvd.Max(a => a.Number) ?? 0) + 1;
  _added.Result = centrvd.Solution.Minuteses.Resources.NoResults;
}

При удалении из коллекции записи, переписывать порядковые номера решений:

public virtual void DecisionscentrvdDeleted(Sungero.Domain.Shared.CollectionPropertyDeletedEventArgs e)
{
  int minNumber = 1;
  foreach (var decision in _obj.Decisionscentrvd)
    decision.Number = minNumber++;
}

Чтобы заполнялось поле Голосующие (Voitings) и служебное поле VotersEmployeesIds (необходимое в схеме задачи при создании заданий) добавим код на событии «Изменение значения свойства» у поля Matrix

public virtual void DecisionscentrvdMatrixChanged(centrvd.Solution.Shared.MinutesDecisionscentrvdMatrixChangedEventArgs e)
{
  if (!Equals(e.NewValue, e.OldValue))
  {
    _obj.Voitings = e.NewValue != null ? e.NewValue.Approvers.Select(a => a.Approver.Person.ShortName).Aggregate((x,y) => $"{x}, {y}") : string.Empty;
    _obj.VotersEmployeesIds = e.NewValue != null ? string.Join(",", e.NewValue.Approvers.Select(a => a.Approver.Id.ToString())) : string.Empty;
  }
}

Этап согласования

Для этапа необходима дополнительная настройка, которая бы учитывала какого типа задания создавать. Перекроем справочник ApprovalStage. Создадим свойство-перечисление «Тип этапа» (CustomStageTypecentrvd). Добавляем значение перечисления Votingcentrvd с отображаемыми именами Voting/Голосование».

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

Добавьте фильтрацию на выбор из перечисления CustomStageTypecentrvd, чтобы «Голосование» не отображалось для этапов, кроме «Задание» или без «Разрешения отправки на доработку»:

public virtual IEnumerable<Enumeration> CustomStageTypecentrvdFiltering(IEnumerable<Enumeration> query)
{
  if (_obj.StageType != StageType.SimpleAgr || _obj.AllowSendToRework.GetValueOrDefault())
    query = query.Where(q => !Equals(q, CustomStageTypecentrvd.Voting));
  return query;
}

Простое задание (без доработки)

Чтобы настроить карточку задания (в зависимости от настроек этапа), перекроем задание ApprovalSimpleAssignment и добавим свойства:

  • Перечисление CustomStageTypecentrvd с точно такими же значениями, как в ApprovalStage: Votingcentrvd с отображаемыми именами «Voting», «Голосование». Его выносить на карточку не нужно, т.к. имеет только служебную необходимость. Позже это значение будет передаваться в схеме задачи.
  • Коллекцию Voitingcentrvd, содержащую:
    • ID (скрытое поле)
    • Number (порядковый номер) (не редактируемый)
    • Decision (текст решения) (не редактируемый)
    • VoteFor (логическое поле для голосования «За»)
    • VoteAgainst (логическое поле для голосования «Против»)
    • VoteAbstain (логическое поле для голосования «Воздержался»)
    • MinutedDecisionId (скрытое поле для хранения ID записи в таблице решений протокола)

Чтобы коллекция отображалась только, если задание с типом «Голосование» в событие «Показ формы» добавим код:

public override void Showing(Sungero.Presentation.FormShowingEventArgs e)
{
  base.Showing(e);
  _obj.State.Properties.Voitingcentrvd.IsVisible = _obj.CustomStageTypecentrvd == centrvd.Solution.ApprovalSimpleAssignment.CustomStageTypecentrvd.Voting;
}

Чтобы нельзя было поставить галочки в несколько полей по решению, заполним обработчик «Изменение контрола» для каждого логического свойства коллекции:

public virtual void VoitingcentrvdVoteAgainstValueInput(Sungero.Presentation.BooleanValueInputEventArgs e)
{
  if (e.NewValue == true)
  {
    _obj.VoteFor = false;
    _obj.VoteAbstain = false;
  }
}

public virtual void VoitingcentrvdVoteAbstainValueInput(Sungero.Presentation.BooleanValueInputEventArgs e)
{
  if (e.NewValue == true)
  {
    _obj.VoteFor = false;
    _obj.VoteAgainst = false;
  }
}

public virtual void VoitingcentrvdVoteForValueInput(Sungero.Presentation.BooleanValueInputEventArgs e)
{
  if (e.NewValue == true)
  {
    _obj.VoteAgainst = false;
    _obj.VoteAbstain = false;
  }
}

*Логические null поля в коллекциях выглядят как false, т.е. вместо «-» выглядят как пустой квадрат, поэтому нет необходимости заполнять их false значениями с точки зрения пользователя.

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

partial class ApprovalSimpleAssignmentAnyChildEntityCollectionActions
{
  public override void DeleteChildEntity(Sungero.Domain.Client.ExecuteChildCollectionActionArgs e)
  {
    base.DeleteChildEntity(e);
  }

  public override bool CanDeleteChildEntity(Sungero.Domain.Client.CanExecuteChildCollectionActionArgs e)
  {
    var assignment = centrvd.Solution.ApprovalSimpleAssignments.As(e.RootEntity);
    var isVoiting = assignment != null ? assignment.CustomStageTypecentrvd == centrvd.Solution.ApprovalSimpleAssignment.CustomStageTypecentrvd.Voting : false;
    return base.CanDeleteChildEntity(e) && !isVoiting;
  }
}

partial class ApprovalSimpleAssignmentAnyChildEntityActions
{
  public override void CopyChildEntity(Sungero.Domain.Client.ExecuteChildCollectionActionArgs e)
  {
    base.CopyChildEntity(e);
  }

  public override bool CanCopyChildEntity(Sungero.Domain.Client.CanExecuteChildCollectionActionArgs e)
  {
    var assignment = centrvd.Solution.ApprovalSimpleAssignments.As(e.RootEntity);
    var isVoiting = assignment != null ? assignment.CustomStageTypecentrvd == centrvd.Solution.ApprovalSimpleAssignment.CustomStageTypecentrvd.Voting : false;
    return base.CanCopyChildEntity(e) && !isVoiting;
  }

  public override void AddChildEntity(Sungero.Domain.Client.ExecuteChildCollectionActionArgs e)
  {
    base.AddChildEntity(e);
  }

  public override bool CanAddChildEntity(Sungero.Domain.Client.CanExecuteChildCollectionActionArgs e)
  {
    var assignment = centrvd.Solution.ApprovalSimpleAssignments.As(e.RootEntity);
    var isVoiting = assignment != null ? assignment.CustomStageTypecentrvd == centrvd.Solution.ApprovalSimpleAssignment.CustomStageTypecentrvd.Voting : false;
    return base.CanAddChildEntity(e) && !isVoiting;
  }
}

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

public override void BeforeComplete(Sungero.Workflow.Server.BeforeCompleteEventArgs e)
{
  base.BeforeComplete(e);
  
  var isVoiting = _obj.CustomStageTypecentrvd == centrvd.Solution.ApprovalSimpleAssignment.CustomStageTypecentrvd.Voting;

  if (isVoiting && _obj.Voitingcentrvd.Any(d => d.VoteAbstain.GetValueOrDefault()
   && d.VoteAgainst.GetValueOrDefault()
   && d.VoteFor.GetValueOrDefault()))
    e.AddError(centrvd.Solution.ApprovalSimpleAssignments.Resources.ErrorVoteAllDecisions);
}

Задача на согласование. Форма

Чтобы в задаче на согласование ApprovalTask дополнительно отображалось поле Голосующие:

  • Добавим свойство-коллекцию Voterscentrvd с полями ID, Voter (ссылка на Employee)
  • На форму задачи добавим группу контролов Voterscentrvd в группу Header с типом «Панель с заголовком без границ». Отображаемое имя Votes/Голосующие.
  • На форму задачи в группу контролов Voterscentrvd добавим контрол Voterscentrvd с типом «Редактор для множественного выбора из списка».
  • Разместить группу до/перед группой «Согласующие»

В разделяемые функции ApprolaTask добавим логическую функцию HasCustomStage(), которая определяет, есть ли в задаче этап модифицированный этап, в нашем случае - голосование.

/// <summary>
///Есть ли в схеме задачи этап с типом Рублево.
/// </summary>
/// <param name="typeValue">Тип этапа Рублево.</param>
/// <returns>True, если этап есть. False, если этап отсутствует.</returns>
[Public]
public bool HasCustomStage(Sungero.Core.Enumeration typeValue)
{
  if (_obj.ApprovalRule != null)
    {
    foreach (var stage in _obj.ApprovalRule.Stages)
      {
      var customStage = centrvd.Solution.ApprovalStages.As(stage.Stage);
      if (customStage != null && customStage.CustomStageTypecentrvd == typeValue)
        return true;
      }
    }
  return false;
}

В разделяемые функции ApprolaTask добавим функцию FillVoters(), которая будет заполнять поле «Голосующие» из выбранной матрицы протокола:

/// <summary>
///Заполнить голосующих.
/// </summary>
public void FillVoters()
{
  var document = _obj.DocumentGroup.OfficialDocuments.FirstOrDefault();
  var minutes = centrvd.Solution.Minuteses.As(document);
  var isVoitingKind = minutes != null ? centrvd.Solution.DocumentKinds.As(minutes.DocumentKind).NeedVoitingcentrvd.GetValueOrDefault() : false;
  var votingStage = centrvd.Solution.ApprovalStage.CustomStageTypecentrvd.Voting;

  if (isVoitingKind && this.HasCustomStage(votingStage) && minutes.Decisionscentrvd.Any())
  {
    _obj.ApprovalMatrixcentrvd = minutes.Decisionscentrvd.FirstOrDefault().Matrix;
    foreach (var employee in _obj.ApprovalMatrixcentrvd.Approvers)
    {
      var voter = _obj.Voterscentrvd.AddNew();
      voter.Voter = employee.Approver;
    }
  }
}

На событие «Изменение значения свойства» регламента добавим код отображения созданной группы:

public override void ApprovalRuleChanged(Sungero.Docflow.Shared.ApprovalTaskApprovalRuleChangedEventArgs e)
{
  base.ApprovalRuleChanged(e);
  Functions.ApprovalTask.FillVoters(_obj);
}

Пропишем отображение поля Голосующие, перекрыв базовую функцию SetVisibleProperties():

public override void SetVisibleProperties(Sungero.Docflow.Structures.ApprovalTask.RefreshParameters refreshParameters)
{
  base.SetVisibleProperties(refreshParameters);
  
  var document = _obj.DocumentGroup.OfficialDocuments.FirstOrDefault();
  var minutes = centrvd.Solution.Minuteses.As(document);
  var isVotingKind = minutes != null ? centrvd.Solution.DocumentKinds.As(minutes.DocumentKind).NeedVoitingcentrvd.GetValueOrDefault() : false;
  var votingStage = centrvd.Solution.ApprovalStage.CustomStageTypecentrvd.Voting;
  bool isVoting = isVotingKind && this.HasCustomStage(votingStage);
  _obj.State.Properties.Voterscentrvd.IsVisible = isVoting;
}

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

public override void Start(Sungero.Domain.Client.ExecuteActionArgs e)
{
  var document = _obj.DocumentGroup.OfficialDocuments.FirstOrDefault();

  //Проверка на наличие активных задач по протоколу, содержащих этап голосование.
  var minutes = centrvd.Solution.Minuteses.As(document);
  var votingStage = centrvd.Solution.ApprovalStage.CustomStageTypecentrvd.Voting;
  var hasVotingStage = Functions.ApprovalTask.HasCustomStage(_obj, votingStage);
  
  if (minutes != null && Functions.Minutes.AnyVoitingMinutesTasks(minutes) && hasVotingStage)
  {
    Dialogs.ShowMessage(centrvd.Solution.ApprovalTasks.Resources.ErrorHasVotingTaskInProcess);
    return;
  }
  
  //Проверка на наличие голосующих перед запуском задачи на голосование.
  if (minutes != null && hasVotingStage && !_obj.Voterscentrvd.Any())
  {
    Dialogs.ShowMessage(centrvd.Solution.ApprovalTasks.Resources.ErrorFillDesicionTab);
    return;
  }
  base.Start(e);
}

Необходимо предусмотреть обнуление результатов в протоколе и заполнение поля «Результаты» значением «Запущено голосование» при старте (рестарте) задачи, поэтому на событие «До старта»:

public override void BeforeStart(Sungero.Workflow.Server.BeforeStartEventArgs e)
{
  base.BeforeStart(e);

  var document = _obj.DocumentGroup.OfficialDocuments.FirstOrDefault();
  //Для протокола совещания с типом голосование очищаем результаты голосования.
  var minutes = centrvd.Solution.Minuteses.As(document);
  var isVoitingKind = minutes != null ? centrvd.Solution.DocumentKinds.As(minutes.DocumentKind).NeedVoitingcentrvd.GetValueOrDefault() : false;
  var votingStage = centrvd.Solution.ApprovalStage.CustomStageTypecentrvd.Voting;

  if (isVoitingKind && Functions.ApprovalTask.HasCustomStage(_obj, votingStage))
  {
    foreach (var decision in minutes.Decisionscentrvd)
    {
      decision.VoteAbstain = null;
      decision.VoteAgainst = null;
      decision.VoteFor = null;
      decision.Result = centrvd.Solution.Minuteses.Resources.VotingStarted;
    }
  }
}

Задача на согласование. Схема

Теперь в задаче на согласование ApprovalTask необходимо передать тип из настроек этапа в задание. Перекроем ApprovalTask, найдем в схеме блок «Задание» ApprovalSimpleAssignment. Переходим на «Старт задания» и находим этап простого задания и передаем значение CustomStageTypecentrvd:

public override void StartAssignment30(Sungero.Docflow.IApprovalSimpleAssignment assignment, Sungero.Docflow.Server.ApprovalSimpleAssignmentArguments e)
{
  base.StartAssignment30(assignment, e);

  var stage = _obj.ApprovalRule.Stages
.Where(s => s.Stage != null)
.Where(s => s.Stage.StageType == Sungero.Docflow.ApprovalStage.StageType.SimpleAgr)
.FirstOrDefault(s => s.Number == _obj.StageNumber);
  
  if (stage != null)
  {
    var CustomStage = centrvd.Solution.ApprovalStages.As(stage.Stage);
    var CustomAssignment = centrvd.Solution.ApprovalSimpleAssignments.As(assignment);

    if(CustomStage.CustomStageTypecentrvd == centrvd.Solution.ApprovalStage.CustomStageTypecentrvd.Voting)
      CustomAssignment.CustomStageTypecentrvd = centrvd.Solution.ApprovalSimpleAssignment.CustomStageTypecentrvd.Voting;

    //Заполнить таблицу для задания с типом этапа Голосование для Протокола совещания.
    var minutes = _obj.DocumentGroup.OfficialDocuments.FirstOrDefault() != null ? centrvd.Solution.Minuteses.As(_obj.DocumentGroup.OfficialDocuments.FirstOrDefault()): null;

    if(minutes != null && CustomStage.CustomStageTypecentrvd == centrvd.Solution.ApprovalStage.CustomStageTypecentrvd.Voting)
    {
      var performerId = Sungero.Company.Employees.As(assignment.Performer) != null ? Sungero.Company.Employees.As(assignment.Performer).Id.ToString() : string.Empty;
      int number = 1;

      foreach (var decision in minutes.Decisionscentrvd.Where(d => d.VotersEmployeesIds.Contains(performerId)))
      {
        var voiting = CustomAssignment.Voitingcentrvd.AddNew();
        voiting.Number = number++;
        voiting.Decision = decision.Decision;
        voiting.MinutedDecisionId = decision.Id; //Пробрасываем ИД строки из таблицы решений Протокола.
      }
    }
  
  }
}

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

public override void CompleteAssignment30(Sungero.Docflow.IApprovalSimpleAssignment assignment, Sungero.Docflow.Server.ApprovalSimpleAssignmentArguments e)
{
  base.CompleteAssignment30(assignment, e);

  var CustomAssignment = centrvd.Solution.ApprovalSimpleAssignments.As(assignment);
  
  //Передача результатов голосования в протокол.
  if (CustomAssignment != null
  && CustomAssignment.CustomStageTypecentrvd == centrvd.Solution.ApprovalSimpleAssignment.CustomStageTypecentrvd.Voting
  && centrvd.Solution.Minuteses.Is(_obj.DocumentGroup.OfficialDocuments.FirstOrDefault())
  && assignment.Result == centrvd.Solution.ApprovalSimpleAssignment.Result.Complete)
  {
    var minutesId = CustomAssignment.DocumentGroup.OfficialDocuments.FirstOrDefault().Id;
    var asyncAddVoitingResults = centrvd.SolutionDataBook.AsyncHandlers.AddVoitingResults.Create();
    asyncAddVoitingResults.AssignmentID = assignment.Id;
    asyncAddVoitingResults.MinutesID = minutesId;
    asyncAddVoitingResults.ExecuteAsync();
  }
}

Асинхронный обработчик

//Пересчитать результаты голосования в протоколе совещания.
public virtual void AddVoitingResults(centrvd.SolutionDataBook.Server.AsyncHandlerInvokeArgs.AddVoitingResultsInvokeArgs args)
{
  if (args.RetryIteration > 100)
  {
    Logger.DebugFormat("AddVoitingResults: превышено количество попыток (100) пересчета голосования по протоколу id - {0} из задания id - {1}. Обратитесь к администратору.",     args.MinutesID, args.AssignmentID);
    args.Retry = false;
    return;
  }
  Logger.DebugFormat("AddVoitingResults: запуск пересчета голосования по протоколу id - {0} из задания id - {1}.", args.MinutesID, args.AssignmentID);
  long minutesId = args.MinutesID;
  long voitingAssignmnetId = args.AssignmentID;
  var minutes = centrvd.Solution.Minuteses.GetAll().Where(m => m.Id == minutesId).FirstOrDefault();
  var voitingAssignmnet = centrvd.Solution.ApprovalSimpleAssignments.GetAll().Where(a => a.Id == voitingAssignmnetId).FirstOrDefault();
  
  if (minutes == null)
  {
    Logger.DebugFormat("AddVoitingResults: Не найден протокол совещания id - {0}.", minutesId);
    return;
  }

  if (voitingAssignmnet == null)
  {
    Logger.DebugFormat("AddVoitingResults: Не найдено задание голосования id - {0}.", voitingAssignmnetId);
    return;
  }

  if (!Locks.TryLock(minutes))
  {
    Logger.DebugFormat("AddVoitingResults: заблокирован протокол совещания id - {0}.", minutesId);
args.Retry = true;
    return;
  }

  foreach (var vote in voitingAssignmnet.Voitingcentrvd)
  {
    var decision = minutes.Decisionscentrvd.Where(d => d.Id == vote.MinutedDecisionId).FirstOrDefault();
    if (decision != null)
    {
      if (vote.VoteFor.GetValueOrDefault())
        decision.VoteFor = decision.VoteFor == null ? 1 : decision.VoteFor++;
      if (vote.VoteAbstain.GetValueOrDefault())
        decision.VoteAbstain = decision.VoteAbstain == null ? 1 : decision.VoteAbstain++;
      if (vote.VoteAgainst.GetValueOrDefault())
        decision.VoteAgainst = decision.VoteAgainst == null ? 1 : decision.VoteAgainst++;
  
      decision.Result = centrvd.SolutionDataBook.Resources.ResultsFormat(decision.VoteFor ?? 0, decision.VoteAgainst ?? 0, decision.VoteAbstain ?? 0);
    }
  }

  minutes.Save();
  Locks.Unlock(minutes);
  Logger.DebugFormat("AddVoitingResults: выполнен пересчет голосования по протоколу id - {0} из задания id - {1}.", args.MinutesID, args.AssignmentID);
}

Роль согласования

Создадим свой справочник CustomApprovalRole на основе Sungero.Docflow.ApprovalRoleBase.

Добавим в перечисление Type имя MinutesVoters со значениями Voters/Голосующие.

Чтобы при согласовании протокола для новой роли вычислялись голосующие, заполненные в поле «Матрица» на вкладке «Решения» карточки протокола, переопределим функцию GetRolePerformers(). Для этого в серверных функциях созданного типа справочника PurchaseApprovalRole напишим:

///Получить список исполнителей из роли.
/// <param name="task">Задача.</param>
/// <returns>Список исполнителей.</returns>
[Remote(IsPure = true), Public]
public override List<Sungero.Company.IEmployee> GetRolePerformers(Sungero.Docflow.IApprovalTask task)
{
  //result используется для возврата списка исполнителей.
  var result = new List<Sungero.Company.IEmployee>();
  
  //Роль Участники голосования.
  if (_obj.Type == SolutionDataBook.CustomApprovalRole.Type.MinutesVoters)
  {
    var document = task.DocumentGroup.OfficialDocuments.FirstOrDefault();
    var minutes = document != null ? centrvd.Solution.Minuteses.As(document) : null;

    if (minutes != null)
    {
      foreach (var decision in minutes.Decisionscentrvd)
        result.AddRange(decision.Matrix.Approvers.Select(a => a.Approver));
    }
    return result;
  }
  return base.GetRolePerformers(task);
}

В этом же справочнике ограничим доступность роли «Голосующие» только для типа «Протоколы совещаний» перекрыв разделяемую функцию Filter():

public override List<Sungero.Docflow.IDocumentKind> Filter(List<Sungero.Docflow.IDocumentKind> kinds)
{
  var query = base.Filter(kinds);
  //Роль Участники голосовани доступна только для Протоколов совещаний.
  if (_obj.Type == centrvd.SolutionDataBook.CustomApprovalRole.Type.MinutesVoters)
    query = query.Where(k => k.DocumentType.DocumentTypeGuid == "bb4780ff-b2c3-4044-a390-e9e110791bf6").ToList();
  return query;
}

Чтобы правильно определялись исполнители этапа голосования, в перекрытом справочнике ApprovalStage создадим серверную функцию GetStageRecipients():

/// <summary>
/// Получить исполнителей этапа без раскрытия групп и ролей.
/// </summary>
/// <param name="task">Задача.</param>
/// <param name="additionalApprovers">Доп.согласующие.</param>
/// <returns>Исполнители.</returns>
[Remote(IsPure = true), Public]
public override List<IRecipient> GetStageRecipients(Sungero.Docflow.IApprovalTask task,
List<IRecipient> additionalApprovers)
{
  var recipients = base.GetStageRecipients(task, additionalApprovers);
  //Роль Участники голосования.
  var votersRole = _obj.ApprovalRoles
.Where(x => x.ApprovalRole.Type == centrvd.SolutionDataBook.CustomApprovalRole.Type.MinutesVoters)
.Select(x => centrvd.SolutionDataBook.CustomApprovalRoles.As(x.ApprovalRole)).Where(x => x != null)
.SingleOrDefault();

  if (votersRole != null)
recipients.AddRange(centrvd.SolutionDataBook.PublicFunctions.CustomApprovalRole.Remote.GetRolePerformers(votersRole, task));
  return recipients;
}

Ограничим доступность роли только для типа «Простое задание» (без доработки), перекрыв разделяемую функцию GetPossibleRoles() в этом же справочнике:

public override List<Enumeration?> GetPossibleRoles()
{
  var baseRoles = base.GetPossibleRoles();
  //Использовать роль Участники голосования в этапах.
  if (_obj.StageType == Sungero.Docflow.ApprovalStage.StageType.SimpleAgr)
baseRoles.Add(centrvd.SolutionDataBook.CustomApprovalRole.Type.MinutesVoters);
  return baseRoles;
}

Инициализация

Создадим роль через функции инициализации модуля:

/// <summary>
/// Обработчик события инициализации.
/// </summary>
public override void Initializing(Sungero.Domain.ModuleInitializingEventArgs e)
{
  CreateCustomApprovalRole(SolutionDataBook.CustomApprovalRole.Type.MinutesVoters, centrvd.SolutionDataBook.Resources.MinutesVotersRoleName);
}
/// <summary>
/// Создание роли согласования для ролей справочника CustomApprovalRole.
/// </summary>
public static void CreateCustomApprovalRole(Enumeration roleType, string description)
{
  var role = CustomApprovalRoles.GetAll().Where(r => Equals(r.Type, roleType) && r.Status == Sungero.Docflow.ApprovalRole.Status.Active).FirstOrDefault();
  
// Проверяет наличие роли.
  if (role == null)
  {
    role = CustomApprovalRoles.Create();
    role.Type = roleType;
  }
  role.Description = description;
  role.Save();
  
  InitializationLogger.Debug(String.Format("Создана роль '{0}'", description));
}

 

Михаил Лопинцев

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

Добрый день.

А как это голосование будет отображаться в мобильных приложениях? И будет ли?

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