ЦВД
29 марта 2024 г. 5 мин

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

На практике часто возникает необходимость организовать коллегиальное принятие решений — например, при утверждении решений совещания, бюджетов или стратегических инициатив. Стандартные механизмы согласования в Directum RX позволяют настроить маршрут, но не поддерживают полноценное голосование с фиксацией позиций «За», «Против», «Воздержался».
Модификация "Голосование" для задачи согласования по регламенту

Задача: коллегиальное принятие решений

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

Требования

  • Голосование должно быть доступно на любом этапе согласования.
  • Участники голосуют по каждому решению: «За», «Против», «Воздержался».
  • Нельзя выбрать несколько вариантов.
  • Результаты агрегируются в протоколе.
  • Голосование — опциональная функция, активируемая при необходимости.

Реализация: ключевые компоненты

  • Настройка вида документа "Протокол совещания"
  • Создание справочника "Матрицы согласования"
  • Доработка задачи согласования
  • Асинхронный обработчик результатов
  • Роль согласования "Участники голосования"
  • Добавление признака "Требуется голосование"

В перекрытый справочник DocumentKind добавляем логическое свойство:

/// <summary>

/// Требуется голосование.

/// </summary>

public virtual bool? NeedVoting { get; set; }

На событии Showing формы — показываем только для вида "Протокол совещания":

public override void Showing(Sungero.Presentation.FormShowingEventArgs e)

{

base.Showing(e);

var isMinutes = _obj.DocumentType?.DocumentTypeGuid == Constants.MeetingMinutesTypeGuid;

_obj.State.Properties.NeedVoting.IsVisible = isMinutes;

}

Коллекция решений в протоколе

  • В карточку Minutes добавляем коллекцию Decisions, где каждое решение содержит:
  • Текст решения
  • Матрицу голосующих
  • Счётчики голосов
  • Результат

public virtual void DecisionsAdded(Sungero.Domain.Shared.CollectionPropertyAddedEventArgs e)

{

var added = e.Added as IMinuteDecision;

added.Number = (_obj.Decisions.Max(d => d.Number) ?? 0) + 1;

added.Result = Resources.NoResults;

}

Кастомный тип этапа "Голосование"

/// <summary>

/// Тип этапа.

/// </summary>

public virtual CustomStageType? CustomStageType { get; set; }

Фильтруем отображение — "Голосование" доступно только для этапов типа "Задание":

public virtual IEnumerable<Enumeration> CustomStageTypeFiltering(IEnumerable<Enumeration> query)

{

if (_obj.StageType != StageType.SimpleAgr || _obj.AllowSendToRework == true)

query = query.Where(q => !Equals(q, CustomStageType.Voting));

return query;

}

Коллекция голосов в задании

В перекрытое задание ApprovalSimpleAssignment добавляем коллекцию Voting, содержащую:

  • Decision — текст
  • VoteFor, VoteAgainst, VoteAbstain — выбор

Обеспечиваем взаоисключающий выбор:

public virtual void VotingVoteAgainstValueInput(Sungero.Presentation.BooleanValueInputEventArgs e)

{

if (e.NewValue == true)

{

_obj.VoteFor = false;

_obj.VoteAbstain = false;

}

}


public virtual void VotingVoteForValueInput(Sungero.Presentation.BooleanValueInputEventArgs e)

{

if (e.NewValue == true)

{

_obj.VoteAgainst = false;

_obj.VoteAbstain = false;

}

}

Проверка голосования перед завершением задания

На событии BeforeComplete убеждаемся, что по всем решениям проголосовано:

public override void BeforeComplete(Sungero.Workflow.Server.BeforeCompleteEventArgs e)

{

base.BeforeComplete(e);

if (_obj.CustomStageType != CustomStageType.Voting)

return;

var emptyVotes = _obj.Voting.Where(v => !v.VoteFor && !v.VoteAgainst && !v.VoteAbstain);

if (emptyVotes.Any())

{

e.AddError("Проголосуйте по всем решениям.");

return;

}

}

Асинхронный обработчик результатов

После завершения задания — агрегируем голоса в протоколе:

public void AddVotingResults(long assignmentId, long minutesId)

{

var assignment = ApprovalSimpleAssignment.GetAll().Where(a => a.Id == assignmentId).FirstOrDefault();

var minutes = Minutes.GetAll().Where(m => m.Id == minutesId).FirstOrDefault();

if (assignment == null || minutes == null) return;

foreach (var vote in assignment.Voting)

{

var decision = minutes.Decisions.FirstOrDefault(d => d.Id == vote.MinutedDecisionId);

if (decision != null)

{

if (vote.VoteFor) decision.VoteFor = (decision.VoteFor ?? 0) + 1;

if (vote.VoteAgainst) decision.VoteAgainst = (decision.VoteAgainst ?? 0) + 1;

if (vote.VoteAbstain) decision.VoteAbstain = (decision.VoteAbstain ?? 0) + 1;

decision.Result = $"За: {decision.VoteFor}, Против: {decision.VoteAgainst}, Воздержались: {decision.VoteAbstain}";

}

}

minutes.Save();

}

Роль согласования "Участники голосования"

Ограничиваем доступ роли только для протоколов:

public override List<IDocumentKind> Filter(List<IDocumentKind> kinds)

{

var query = base.Filter(kinds);

if (_obj.Type == RoleType.MinutesVoters)

query = query.Where(k => k.DocumentType.DocumentTypeGuid == Constants.MeetingMinutesTypeGuid);

return query.ToList();

}

Итог

Модификация позволяет:

  • Голосовать прямо в задаче согласования.
  • Сохранять результаты в протоколе.
  • Гибко настраивать состав голосующих.
  • Интегрировать в любые бизнес-процессы.

Решение реализовано через low-code подход, без изменения стандартных механизмов Directum RX, что упрощает сопровождение и обновление.

Подбор решения за 60 секунд
Ответьте на 4 коротких вопроса, и мы бесплатно подберём для вас оптимальное решение на базе Directum RX