- Introduced a new support ticket system allowing users to submit bug reports and role requests. - Implemented endpoints for creating, updating, and managing support tickets, including file attachments. - Enhanced Telegram bot integration to handle role requests directly within the bot, enabling admins to approve or reject requests without accessing the website. - Updated database schema to include support ticket entities and their relationships. - Improved API documentation to reflect new support ticket endpoints and their usage. - Added necessary localization for support ticket features in both Russian and English.
95 lines
3.7 KiB
C#
95 lines
3.7 KiB
C#
using PnvPanel.Domain.Common;
|
||
using PnvPanel.Domain.Exceptions;
|
||
|
||
namespace PnvPanel.Domain.Support;
|
||
|
||
/// <summary>
|
||
/// Обращение в поддержку: баг/предложение (свободная форма) либо заявка на роль (существующая роль
|
||
/// или параметры новой). Текст обращения и переписка — в TicketComment, отдельной таблицей (не
|
||
/// навигационная коллекция — см. конвенцию проекта на плоских сущностях, ср. TrafficSample/VpnConfig).
|
||
/// Для RoleRequest заполнен либо RequestedRoleId, либо Proposed* — гарантируется отдельными фабриками,
|
||
/// а не runtime-проверкой одного универсального конструктора.
|
||
/// </summary>
|
||
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,
|
||
};
|
||
}
|
||
|
||
/// <summary>Решено (в т.ч. заявка на роль одобрена — роль выдаётся оркестрацией на уровне Application).</summary>
|
||
public void Resolve()
|
||
{
|
||
if (Status != TicketStatus.Open)
|
||
throw new DomainException($"Нельзя перевести в Resolved тикет в статусе {Status}.");
|
||
|
||
Status = TicketStatus.Resolved;
|
||
}
|
||
|
||
/// <summary>Финальное состояние — обратного пути нет (в т.ч. заявка на роль отклонена).</summary>
|
||
public void Close()
|
||
{
|
||
if (Status == TicketStatus.Closed)
|
||
throw new DomainException("Тикет уже закрыт.");
|
||
|
||
Status = TicketStatus.Closed;
|
||
}
|
||
|
||
/// <summary>Только из Resolved — Closed финален и не переоткрывается.</summary>
|
||
public void Reopen()
|
||
{
|
||
if (Status != TicketStatus.Resolved)
|
||
throw new DomainException("Переоткрыть можно только решённый тикет.");
|
||
|
||
Status = TicketStatus.Open;
|
||
}
|
||
}
|