Files
PnvPanel/backend/src/PnvPanel.Application/Admin/Support/RejectRoleRequestCommandHandler.cs
T
Leonid Pershin e19860ba46
CI / Backend (build + test) (push) Successful in 1m30s
CI / Frontend (lint + typecheck + build) (push) Successful in 33s
Implement billing status notification and enhance user management integration
- Added `NotifyBillingStatusChangedAsync` method to `IRealtimeNotifier` for notifying clients about changes in billing status.
- Updated `BillingConfigResumer` to call the new notification method after modifying billing configurations, ensuring users receive real-time updates.
- Enhanced `ListUsersQueryHandler` to include a `BillingPendingReview` property in `UserSummaryDto`, indicating if a user has a pending payment request awaiting confirmation.
- Refactored various command handlers to utilize `AdvisoryLock` for managing concurrent requests, preventing race conditions in billing operations.
- Updated tests to cover new notification behaviors and ensure proper functionality in billing status management.
2026-07-19 23:22:57 +03:00

83 lines
2.9 KiB
C#

using Microsoft.EntityFrameworkCore;
using PnvPanel.Application.Auth;
using PnvPanel.Application.Common.Concurrency;
using PnvPanel.Application.Common.Interfaces;
using PnvPanel.Application.Common.Messaging;
using PnvPanel.Application.Common.Models;
using PnvPanel.Application.Support;
using PnvPanel.Domain.Audit;
using PnvPanel.Domain.Support;
namespace PnvPanel.Application.Admin.Support;
/// <summary>Проверка статуса + Close() — под AdvisoryLock (по Id тикета), см.
/// ApproveRoleRequestCommandHandler для полного обоснования.</summary>
public sealed class RejectRoleRequestCommandHandler(
IAppDbContext dbContext,
IRealtimeNotifier notifier,
ITelegramNotifier telegramNotifier,
ICurrentUser currentUser
) : ICommandHandler<RejectRoleRequestCommand, Result>
{
public async Task<Result> Handle(
RejectRoleRequestCommand command,
CancellationToken cancellationToken
)
{
if (currentUser.UserId is not { } adminId)
return Result.Failure(AuthErrors.Unauthorized);
var claimed = await AdvisoryLock.RunAsync(
dbContext,
command.TicketId,
async lockedCancellationToken =>
{
var fresh = await dbContext.SupportTickets.FirstOrDefaultAsync(
t => t.Id == command.TicketId,
lockedCancellationToken
);
if (fresh is null)
return Result.Failure<SupportTicket>(SupportErrors.NotFound);
if (fresh.Type != TicketType.RoleRequest)
return Result.Failure<SupportTicket>(SupportErrors.NotRoleRequest);
if (fresh.Status == TicketStatus.Closed)
return Result.Failure<SupportTicket>(SupportErrors.AlreadyClosed);
if (!string.IsNullOrWhiteSpace(command.Reason))
dbContext.TicketComments.Add(TicketComment.Create(fresh.Id, adminId, command.Reason));
fresh.Close();
return Result.Success(fresh);
},
cancellationToken
);
if (!claimed.IsSuccess)
return Result.Failure(claimed.Error);
var ticket = claimed.Value;
dbContext.AuditLogs.Add(
AuditLog.Create(
adminId,
"RoleRequestRejected",
"SupportTicket",
ticket.Id.ToString(),
metadata: null,
AuditSource.Web
)
);
await notifier.NotifyTicketUpdatedAsync(ticket.Id, ticket.UserId, cancellationToken);
await telegramNotifier.NotifyUserAsync(
ticket.UserId,
"❌ Ваша заявка на роль отклонена.",
$"/support?ticket={ticket.Id}",
cancellationToken
);
return Result.Success();
}
}