157 lines
4.8 KiB
C#
157 lines
4.8 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
|
|
namespace Knot.Modules.Messaging.Domain;
|
|
|
|
public class PollMessage : Message
|
|
{
|
|
public override string Type => "poll";
|
|
public override string? Content { get; protected set; } // Вопрос опроса
|
|
|
|
public bool IsAnonymous { get; protected set; }
|
|
public bool AllowMultipleAnswers { get; protected set; }
|
|
public bool IsClosed { get; protected set; }
|
|
public DateTime? ExpiresAt { get; protected set; }
|
|
|
|
private List<PollOption> _options = new();
|
|
public IReadOnlyCollection<PollOption> Options => _options.AsReadOnly();
|
|
|
|
private List<PollVote> _votes = new();
|
|
public IReadOnlyCollection<PollVote> Votes => _votes.AsReadOnly();
|
|
|
|
// Инфраструктурный конструктор
|
|
private PollMessage() : base() { }
|
|
|
|
public PollMessage(
|
|
Guid id,
|
|
Guid chatId,
|
|
Guid senderId,
|
|
string question,
|
|
List<string> options,
|
|
bool isAnonymous,
|
|
bool allowMultipleAnswers,
|
|
DateTime? expiresAt,
|
|
Guid? replyToId,
|
|
Guid? forwardedFromId,
|
|
DateTime createdAt,
|
|
bool isImported)
|
|
: base(id, chatId, senderId, replyToId, forwardedFromId, createdAt, isImported)
|
|
{
|
|
Content = question;
|
|
IsAnonymous = isAnonymous;
|
|
AllowMultipleAnswers = allowMultipleAnswers;
|
|
ExpiresAt = expiresAt;
|
|
IsClosed = false;
|
|
|
|
foreach (var optionText in options)
|
|
{
|
|
_options.Add(new PollOption(Guid.NewGuid(), Id, optionText));
|
|
}
|
|
|
|
if (!isImported)
|
|
{
|
|
// Можно добавить специфичное событие для опроса
|
|
// RaiseDomainEvent(new PollCreatedDomainEvent(Id, ChatId, SenderId, question));
|
|
}
|
|
}
|
|
|
|
public void Vote(Guid userId, List<Guid> optionIds)
|
|
{
|
|
if (IsClosed) throw new InvalidOperationException("Poll is closed");
|
|
if (ExpiresAt.HasValue && DateTime.UtcNow > ExpiresAt.Value)
|
|
{
|
|
Close();
|
|
throw new InvalidOperationException("Poll has expired");
|
|
}
|
|
|
|
if (optionIds == null || !optionIds.Any()) throw new ArgumentException("At least one option must be selected");
|
|
if (!AllowMultipleAnswers && optionIds.Count > 1) throw new ArgumentException("Multiple answers are not allowed");
|
|
|
|
// Проверяем существование опций
|
|
foreach (var oid in optionIds)
|
|
{
|
|
if (!_options.Any(o => o.Id == oid)) throw new ArgumentException($"Option {oid} not found");
|
|
}
|
|
|
|
// Удаляем старые голоса пользователя
|
|
_votes.RemoveAll(v => v.UserId == userId);
|
|
|
|
// Добавляем новые голоса
|
|
foreach (var oid in optionIds)
|
|
{
|
|
_votes.Add(new PollVote(Id, userId, oid, DateTime.UtcNow));
|
|
}
|
|
|
|
// Обновляем статистику в опциях
|
|
RecalculateResults();
|
|
}
|
|
|
|
public void Close()
|
|
{
|
|
IsClosed = true;
|
|
}
|
|
|
|
private void RecalculateResults()
|
|
{
|
|
int totalVotesCount = _votes.Select(v => v.UserId).Distinct().Count();
|
|
|
|
foreach (var option in _options)
|
|
{
|
|
int optionVotes = _votes.Count(v => v.OptionId == option.Id);
|
|
double percentage = totalVotesCount > 0 ? (double)optionVotes / totalVotesCount * 100 : 0;
|
|
option.UpdateResults(optionVotes, (int)Math.Round(percentage));
|
|
}
|
|
}
|
|
|
|
public override void Delete()
|
|
{
|
|
// При удалении опроса можно очистить голоса или оставить для истории (зависит от политики)
|
|
base.Delete();
|
|
}
|
|
}
|
|
|
|
public class PollOption
|
|
{
|
|
public Guid Id { get; private set; }
|
|
public Guid PollId { get; private set; }
|
|
public string Text { get; private set; }
|
|
|
|
// Денормализованные данные для быстрой отдачи
|
|
public int VotesCount { get; private set; }
|
|
public int Percentage { get; private set; }
|
|
|
|
private PollOption() { } // EF
|
|
|
|
public PollOption(Guid id, Guid pollId, string text)
|
|
{
|
|
Id = id;
|
|
PollId = pollId;
|
|
Text = text;
|
|
}
|
|
|
|
public void UpdateResults(int count, int percentage)
|
|
{
|
|
VotesCount = count;
|
|
Percentage = percentage;
|
|
}
|
|
}
|
|
|
|
public class PollVote
|
|
{
|
|
public Guid PollId { get; private set; }
|
|
public Guid UserId { get; private set; }
|
|
public Guid OptionId { get; private set; }
|
|
public DateTime VotedAt { get; private set; }
|
|
|
|
private PollVote() { } // EF
|
|
|
|
public PollVote(Guid pollId, Guid userId, Guid optionId, DateTime votedAt)
|
|
{
|
|
PollId = pollId;
|
|
UserId = userId;
|
|
OptionId = optionId;
|
|
VotedAt = votedAt;
|
|
}
|
|
}
|