using PnvPanel.Domain.Common;
using PnvPanel.Domain.Exceptions;
namespace PnvPanel.Domain.Support;
///
/// Обращение в поддержку: баг/предложение (свободная форма) либо заявка на роль (существующая роль
/// или параметры новой). Текст обращения и переписка — в TicketComment, отдельной таблицей (не
/// навигационная коллекция — см. конвенцию проекта на плоских сущностях, ср. TrafficSample/VpnConfig).
/// Для RoleRequest заполнен либо RequestedRoleId, либо Proposed* — гарантируется отдельными фабриками,
/// а не runtime-проверкой одного универсального конструктора.
///
public sealed class SupportTicket : Entity
{
public Guid UserId { get; private set; }
public TicketType Type { get; private set; }
public TicketStatus Status { get; private set; }
public Guid? RequestedRoleId { get; private set; }
public string? ProposedRoleName { get; private set; }
public int? ProposedMaxConfigs { get; private set; }
public int? ProposedMaxIpLimit { get; private set; }
public DateTimeOffset CreatedAt { get; private set; }
private SupportTicket()
{
}
public static SupportTicket CreateBugReport(Guid userId)
{
return new SupportTicket
{
Id = Guid.NewGuid(),
UserId = userId,
Type = TicketType.BugReport,
Status = TicketStatus.Open,
CreatedAt = DateTimeOffset.UtcNow,
};
}
public static SupportTicket CreateRoleRequestForExistingRole(Guid userId, Guid roleId)
{
return new SupportTicket
{
Id = Guid.NewGuid(),
UserId = userId,
Type = TicketType.RoleRequest,
Status = TicketStatus.Open,
RequestedRoleId = roleId,
CreatedAt = DateTimeOffset.UtcNow,
};
}
public static SupportTicket CreateRoleRequestForNewRole(Guid userId, string name, int maxConfigs, int maxIpLimit)
{
return new SupportTicket
{
Id = Guid.NewGuid(),
UserId = userId,
Type = TicketType.RoleRequest,
Status = TicketStatus.Open,
ProposedRoleName = name,
ProposedMaxConfigs = maxConfigs,
ProposedMaxIpLimit = maxIpLimit,
CreatedAt = DateTimeOffset.UtcNow,
};
}
/// Решено (в т.ч. заявка на роль одобрена — роль выдаётся оркестрацией на уровне Application).
public void Resolve()
{
if (Status != TicketStatus.Open)
throw new DomainException($"Нельзя перевести в Resolved тикет в статусе {Status}.");
Status = TicketStatus.Resolved;
}
/// Финальное состояние — обратного пути нет (в т.ч. заявка на роль отклонена).
public void Close()
{
if (Status == TicketStatus.Closed)
throw new DomainException("Тикет уже закрыт.");
Status = TicketStatus.Closed;
}
/// Только из Resolved — Closed финален и не переоткрывается.
public void Reopen()
{
if (Status != TicketStatus.Resolved)
throw new DomainException("Переоткрыть можно только решённый тикет.");
Status = TicketStatus.Open;
}
}