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

Задача: коллегиальное принятие решений
В этой статье — как мы реализовали модификацию для голосования в рамках задачи согласования, сохранив привычный интерфейс и интегрировав результаты прямо в протокол совещания.
Требования
— Голосование должно быть доступно на любом этапе согласования.
— Участники голосуют по каждому решению: «За», «Против», «Воздержался».
— Нельзя выбрать несколько вариантов.
— Результаты агрегируются в протоколе.
— Голосование — опциональная функция, активируемая при необходимости.
Реализация: ключевые компоненты
— Настройка вида документа "Протокол совещания"
— Создание справочника "Матрицы согласования"
— Доработка задачи согласования
— Асинхронный обработчик результатов
— Роль согласования "Участники голосования"
— Добавление признака "Требуется голосование"
В перекрытый справочник 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, что упрощает сопровождение и обновление.
— Бесплатная консультация по сценариям вашей интеграции
— Демонстрация решения в течение 24 часов