Files
PnvPanel/backend/src/PnvPanel.Domain/Support/SupportTicket.cs
T
Leonid Pershin b5630b2685
CI / Backend (build + test) (push) Successful in 1m18s
CI / Frontend (lint + typecheck + build) (push) Successful in 31s
Implement support ticket system with role request and bug report functionalities
- 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.
2026-07-14 06:49:05 +03:00

95 lines
3.7 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
}
}