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.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
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;
|
||||
@@ -9,6 +10,8 @@ using PnvPanel.Domain.Support;
|
||||
|
||||
namespace PnvPanel.Application.Admin.Support;
|
||||
|
||||
/// <summary>Проверка статуса + Close() — под AdvisoryLock (по Id тикета), см.
|
||||
/// ApproveRoleRequestCommandHandler для полного обоснования.</summary>
|
||||
public sealed class RejectRoleRequestCommandHandler(
|
||||
IAppDbContext dbContext,
|
||||
IRealtimeNotifier notifier,
|
||||
@@ -24,23 +27,36 @@ public sealed class RejectRoleRequestCommandHandler(
|
||||
if (currentUser.UserId is not { } adminId)
|
||||
return Result.Failure(AuthErrors.Unauthorized);
|
||||
|
||||
var ticket = await dbContext.SupportTickets.FirstOrDefaultAsync(
|
||||
t => t.Id == command.TicketId,
|
||||
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 (ticket is null)
|
||||
return Result.Failure(SupportErrors.NotFound);
|
||||
if (!claimed.IsSuccess)
|
||||
return Result.Failure(claimed.Error);
|
||||
|
||||
if (ticket.Type != TicketType.RoleRequest)
|
||||
return Result.Failure(SupportErrors.NotRoleRequest);
|
||||
|
||||
if (ticket.Status == TicketStatus.Closed)
|
||||
return Result.Failure(SupportErrors.AlreadyClosed);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(command.Reason))
|
||||
dbContext.TicketComments.Add(TicketComment.Create(ticket.Id, adminId, command.Reason));
|
||||
|
||||
ticket.Close();
|
||||
var ticket = claimed.Value;
|
||||
|
||||
dbContext.AuditLogs.Add(
|
||||
AuditLog.Create(
|
||||
|
||||
Reference in New Issue
Block a user