feat: thêm các API, command và query quản trị cho việc quản lý ví và tài khoản điểm.
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
namespace WalletService.API.Application.Commands;
|
||||
|
||||
using MediatR;
|
||||
|
||||
#region Admin Wallet Commands
|
||||
|
||||
/// <summary>
|
||||
/// EN: Command to freeze a wallet (Admin only).
|
||||
/// VI: Command để đóng băng ví (Chỉ Admin).
|
||||
/// </summary>
|
||||
public record AdminFreezeWalletCommand(
|
||||
Guid WalletId,
|
||||
string Reason,
|
||||
Guid AdminId) : IRequest<AdminWalletActionResult>;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Command to unfreeze a wallet (Admin only).
|
||||
/// VI: Command để mở băng ví (Chỉ Admin).
|
||||
/// </summary>
|
||||
public record AdminUnfreezeWalletCommand(
|
||||
Guid WalletId,
|
||||
string Reason,
|
||||
Guid AdminId) : IRequest<AdminWalletActionResult>;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Command to adjust wallet balance (Admin only).
|
||||
/// VI: Command để điều chỉnh số dư ví (Chỉ Admin).
|
||||
/// </summary>
|
||||
public record AdminAdjustBalanceCommand(
|
||||
Guid WalletId,
|
||||
decimal Amount,
|
||||
int CurrencyTypeId,
|
||||
string Reason,
|
||||
Guid AdminId) : IRequest<AdminAdjustBalanceResult>;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Admin Wallet Command Results
|
||||
|
||||
public record AdminWalletActionResult(
|
||||
Guid WalletId,
|
||||
string Status,
|
||||
string ActionBy,
|
||||
DateTime ActionAt);
|
||||
|
||||
public record AdminAdjustBalanceResult(
|
||||
Guid WalletId,
|
||||
decimal PreviousBalance,
|
||||
decimal AdjustmentAmount,
|
||||
decimal NewBalance,
|
||||
string Currency,
|
||||
string Reason,
|
||||
DateTime AdjustedAt);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Admin Points Commands
|
||||
|
||||
/// <summary>
|
||||
/// EN: Command to adjust points (Admin only).
|
||||
/// VI: Command để điều chỉnh điểm (Chỉ Admin).
|
||||
/// </summary>
|
||||
public record AdminAdjustPointsCommand(
|
||||
Guid AccountId,
|
||||
long Points,
|
||||
string Reason,
|
||||
Guid AdminId) : IRequest<AdminAdjustPointsResult>;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Command to grant bonus points (Admin only).
|
||||
/// VI: Command để tặng điểm thưởng (Chỉ Admin).
|
||||
/// </summary>
|
||||
public record AdminGrantBonusCommand(
|
||||
Guid AccountId,
|
||||
long Points,
|
||||
string Reason,
|
||||
int? ExpiryMonths,
|
||||
Guid AdminId) : IRequest<AdminGrantBonusResult>;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Admin Points Command Results
|
||||
|
||||
public record AdminAdjustPointsResult(
|
||||
Guid AccountId,
|
||||
long PreviousPoints,
|
||||
long AdjustmentPoints,
|
||||
long NewPoints,
|
||||
string Reason,
|
||||
DateTime AdjustedAt);
|
||||
|
||||
public record AdminGrantBonusResult(
|
||||
Guid AccountId,
|
||||
long BonusPoints,
|
||||
long NewTotalPoints,
|
||||
DateTime? ExpiresAt,
|
||||
DateTime GrantedAt);
|
||||
|
||||
#endregion
|
||||
@@ -0,0 +1,158 @@
|
||||
namespace WalletService.API.Application.Queries;
|
||||
|
||||
using MediatR;
|
||||
|
||||
#region Admin Wallet Queries
|
||||
|
||||
/// <summary>
|
||||
/// EN: Query to get all wallets with pagination (Admin only).
|
||||
/// VI: Query lấy tất cả ví với phân trang (Chỉ Admin).
|
||||
/// </summary>
|
||||
public record GetAllWalletsQuery(
|
||||
int Page,
|
||||
int PageSize,
|
||||
string? Status = null,
|
||||
string? Currency = null) : IRequest<AdminWalletsListDto>;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Query to get wallet by ID (Admin only).
|
||||
/// VI: Query lấy ví theo ID (Chỉ Admin).
|
||||
/// </summary>
|
||||
public record GetWalletByIdQuery(Guid WalletId) : IRequest<AdminWalletDetailDto?>;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Query to search wallets (Admin only).
|
||||
/// VI: Query tìm kiếm ví (Chỉ Admin).
|
||||
/// </summary>
|
||||
public record SearchWalletsQuery(
|
||||
Guid? UserId = null,
|
||||
Guid? WalletId = null,
|
||||
string? Status = null) : IRequest<List<AdminWalletDetailDto>>;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Query to get wallet statistics (Admin only).
|
||||
/// VI: Query lấy thống kê ví (Chỉ Admin).
|
||||
/// </summary>
|
||||
public record GetWalletStatisticsQuery() : IRequest<WalletStatisticsDto>;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Admin Points Queries
|
||||
|
||||
/// <summary>
|
||||
/// EN: Query to get all point accounts with pagination (Admin only).
|
||||
/// VI: Query lấy tất cả tài khoản điểm với phân trang (Chỉ Admin).
|
||||
/// </summary>
|
||||
public record GetAllPointAccountsQuery(
|
||||
int Page,
|
||||
int PageSize,
|
||||
long? MinPoints = null,
|
||||
long? MaxPoints = null) : IRequest<AdminPointAccountsListDto>;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Query to get point account by ID (Admin only).
|
||||
/// VI: Query lấy tài khoản điểm theo ID (Chỉ Admin).
|
||||
/// </summary>
|
||||
public record GetPointAccountByIdQuery(Guid AccountId) : IRequest<AdminPointAccountDetailDto?>;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Query to search point accounts (Admin only).
|
||||
/// VI: Query tìm kiếm tài khoản điểm (Chỉ Admin).
|
||||
/// </summary>
|
||||
public record SearchPointAccountsQuery(Guid? UserId = null) : IRequest<List<AdminPointAccountDetailDto>>;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Query to get points statistics (Admin only).
|
||||
/// VI: Query lấy thống kê điểm (Chỉ Admin).
|
||||
/// </summary>
|
||||
public record GetPointsStatisticsQuery() : IRequest<PointsStatisticsDto>;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Admin DTOs
|
||||
|
||||
/// <summary>
|
||||
/// EN: DTO for admin wallets list with pagination.
|
||||
/// VI: DTO cho danh sách ví Admin với phân trang.
|
||||
/// </summary>
|
||||
public record AdminWalletsListDto(
|
||||
List<AdminWalletSummaryDto> Wallets,
|
||||
int TotalCount,
|
||||
int Page,
|
||||
int PageSize);
|
||||
|
||||
public record AdminWalletSummaryDto(
|
||||
Guid Id,
|
||||
Guid UserId,
|
||||
string Status,
|
||||
List<BalanceItemDto> Balances,
|
||||
DateTime CreatedAt,
|
||||
DateTime UpdatedAt);
|
||||
|
||||
public record BalanceItemDto(
|
||||
string Currency,
|
||||
decimal Balance);
|
||||
|
||||
public record AdminWalletDetailDto(
|
||||
Guid Id,
|
||||
Guid UserId,
|
||||
string Status,
|
||||
List<BalanceItemDto> Balances,
|
||||
int TransactionCount,
|
||||
DateTime CreatedAt,
|
||||
DateTime UpdatedAt);
|
||||
|
||||
/// <summary>
|
||||
/// EN: DTO for wallet statistics.
|
||||
/// VI: DTO cho thống kê ví.
|
||||
/// </summary>
|
||||
public record WalletStatisticsDto(
|
||||
int TotalWallets,
|
||||
int ActiveWallets,
|
||||
int FrozenWallets,
|
||||
int ClosedWallets,
|
||||
Dictionary<string, decimal> TotalBalanceByCurrency,
|
||||
decimal TotalTransactionsToday,
|
||||
decimal TotalDepositsToday,
|
||||
decimal TotalWithdrawalsToday);
|
||||
|
||||
/// <summary>
|
||||
/// EN: DTO for admin point accounts list with pagination.
|
||||
/// VI: DTO cho danh sách tài khoản điểm Admin với phân trang.
|
||||
/// </summary>
|
||||
public record AdminPointAccountsListDto(
|
||||
List<AdminPointAccountSummaryDto> Accounts,
|
||||
int TotalCount,
|
||||
int Page,
|
||||
int PageSize);
|
||||
|
||||
public record AdminPointAccountSummaryDto(
|
||||
Guid Id,
|
||||
Guid UserId,
|
||||
long TotalPoints,
|
||||
long AvailablePoints,
|
||||
DateTime CreatedAt);
|
||||
|
||||
public record AdminPointAccountDetailDto(
|
||||
Guid Id,
|
||||
Guid UserId,
|
||||
long TotalPoints,
|
||||
long AvailablePoints,
|
||||
int TransactionCount,
|
||||
DateTime CreatedAt,
|
||||
DateTime UpdatedAt);
|
||||
|
||||
/// <summary>
|
||||
/// EN: DTO for points statistics.
|
||||
/// VI: DTO cho thống kê điểm.
|
||||
/// </summary>
|
||||
public record PointsStatisticsDto(
|
||||
int TotalAccounts,
|
||||
long TotalPointsIssued,
|
||||
long TotalPointsAvailable,
|
||||
long TotalPointsSpent,
|
||||
long TotalPointsExpired,
|
||||
long PointsEarnedToday,
|
||||
long PointsSpentToday);
|
||||
|
||||
#endregion
|
||||
@@ -0,0 +1,168 @@
|
||||
namespace WalletService.API.Controllers.Admin;
|
||||
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Swashbuckle.AspNetCore.Annotations;
|
||||
using WalletService.API.Application.Commands;
|
||||
using WalletService.API.Application.Queries;
|
||||
using WalletService.API.Controllers;
|
||||
using WalletService.Domain.AggregatesModel.PointAccountAggregate;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Admin controller for points management operations (Backoffice).
|
||||
/// VI: Controller Admin cho các thao tác quản lý điểm (Backoffice).
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/v1/admin/points")]
|
||||
[Authorize(Roles = "Admin,SuperAdmin")]
|
||||
[SwaggerTag("Admin Points Management / Quản lý điểm Admin")]
|
||||
public class AdminPointsController : ControllerBase
|
||||
{
|
||||
private readonly IMediator _mediator;
|
||||
private readonly IPointAccountRepository _pointAccountRepository;
|
||||
private readonly ILogger<AdminPointsController> _logger;
|
||||
|
||||
public AdminPointsController(
|
||||
IMediator mediator,
|
||||
IPointAccountRepository pointAccountRepository,
|
||||
ILogger<AdminPointsController> logger)
|
||||
{
|
||||
_mediator = mediator;
|
||||
_pointAccountRepository = pointAccountRepository;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Get all point accounts with pagination.
|
||||
/// VI: Lấy tất cả tài khoản điểm với phân trang.
|
||||
/// </summary>
|
||||
[HttpGet]
|
||||
[SwaggerOperation(Summary = "Get all point accounts", Description = "Get all point accounts with pagination (Admin only)")]
|
||||
[SwaggerResponse(200, "Success", typeof(ApiResponse<AdminPointAccountsListDto>))]
|
||||
public async Task<IActionResult> GetAllPointAccounts(
|
||||
[FromQuery] int page = 1,
|
||||
[FromQuery] int pageSize = 20,
|
||||
[FromQuery] long? minPoints = null,
|
||||
[FromQuery] long? maxPoints = null)
|
||||
{
|
||||
var query = new GetAllPointAccountsQuery(page, pageSize, minPoints, maxPoints);
|
||||
var result = await _mediator.Send(query);
|
||||
|
||||
return Ok(new ApiResponse<AdminPointAccountsListDto>(true, "Success", result));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Get point account details by ID.
|
||||
/// VI: Lấy chi tiết tài khoản điểm theo ID.
|
||||
/// </summary>
|
||||
[HttpGet("{accountId:guid}")]
|
||||
[SwaggerOperation(Summary = "Get point account by ID", Description = "Get point account details by ID (Admin only)")]
|
||||
[SwaggerResponse(200, "Success", typeof(ApiResponse<AdminPointAccountDetailDto>))]
|
||||
[SwaggerResponse(404, "Point account not found")]
|
||||
public async Task<IActionResult> GetPointAccountById(Guid accountId)
|
||||
{
|
||||
var query = new GetPointAccountByIdQuery(accountId);
|
||||
var result = await _mediator.Send(query);
|
||||
|
||||
if (result == null)
|
||||
return NotFound(new ApiResponse<object>(false, "Point account not found"));
|
||||
|
||||
return Ok(new ApiResponse<AdminPointAccountDetailDto>(true, "Success", result));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Adjust points (add or subtract).
|
||||
/// VI: Điều chỉnh điểm (cộng hoặc trừ).
|
||||
/// </summary>
|
||||
[HttpPost("{accountId:guid}/adjust")]
|
||||
[SwaggerOperation(Summary = "Adjust points", Description = "Adjust points for a user account (Admin only)")]
|
||||
[SwaggerResponse(200, "Points adjusted")]
|
||||
[SwaggerResponse(400, "Invalid adjustment")]
|
||||
[SwaggerResponse(404, "Point account not found")]
|
||||
public async Task<IActionResult> AdjustPoints(Guid accountId, [FromBody] AdminAdjustPointsRequest request)
|
||||
{
|
||||
var command = new AdminAdjustPointsCommand(
|
||||
accountId,
|
||||
request.Points,
|
||||
request.Reason,
|
||||
GetAdminId());
|
||||
|
||||
var result = await _mediator.Send(command);
|
||||
|
||||
_logger.LogInformation(
|
||||
"Admin {AdminId} adjusted points for account {AccountId} by {Points}. Reason: {Reason}",
|
||||
GetAdminId(), accountId, request.Points, request.Reason);
|
||||
|
||||
return Ok(new ApiResponse<AdminAdjustPointsResult>(true, "Points adjusted successfully", result));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Grant bonus points to user.
|
||||
/// VI: Tặng điểm thưởng cho người dùng.
|
||||
/// </summary>
|
||||
[HttpPost("{accountId:guid}/bonus")]
|
||||
[SwaggerOperation(Summary = "Grant bonus points", Description = "Grant bonus points to user (Admin only)")]
|
||||
[SwaggerResponse(200, "Bonus points granted")]
|
||||
[SwaggerResponse(404, "Point account not found")]
|
||||
public async Task<IActionResult> GrantBonusPoints(Guid accountId, [FromBody] AdminGrantBonusRequest request)
|
||||
{
|
||||
var command = new AdminGrantBonusCommand(
|
||||
accountId,
|
||||
request.Points,
|
||||
request.Reason,
|
||||
request.ExpiryMonths,
|
||||
GetAdminId());
|
||||
|
||||
var result = await _mediator.Send(command);
|
||||
|
||||
_logger.LogInformation(
|
||||
"Admin {AdminId} granted {Points} bonus points to account {AccountId}. Reason: {Reason}",
|
||||
GetAdminId(), request.Points, accountId, request.Reason);
|
||||
|
||||
return Ok(new ApiResponse<AdminGrantBonusResult>(true, "Bonus points granted successfully", result));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Get points statistics.
|
||||
/// VI: Lấy thống kê điểm.
|
||||
/// </summary>
|
||||
[HttpGet("statistics")]
|
||||
[SwaggerOperation(Summary = "Get statistics", Description = "Get points statistics (Admin only)")]
|
||||
[SwaggerResponse(200, "Success", typeof(ApiResponse<PointsStatisticsDto>))]
|
||||
public async Task<IActionResult> GetStatistics()
|
||||
{
|
||||
var query = new GetPointsStatisticsQuery();
|
||||
var result = await _mediator.Send(query);
|
||||
|
||||
return Ok(new ApiResponse<PointsStatisticsDto>(true, "Success", result));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Search point accounts by user ID.
|
||||
/// VI: Tìm kiếm tài khoản điểm theo user ID.
|
||||
/// </summary>
|
||||
[HttpGet("search")]
|
||||
[SwaggerOperation(Summary = "Search point accounts", Description = "Search point accounts by user ID (Admin only)")]
|
||||
[SwaggerResponse(200, "Success")]
|
||||
public async Task<IActionResult> SearchPointAccounts([FromQuery] Guid? userId = null)
|
||||
{
|
||||
var query = new SearchPointAccountsQuery(userId);
|
||||
var result = await _mediator.Send(query);
|
||||
|
||||
return Ok(new ApiResponse<List<AdminPointAccountDetailDto>>(true, "Success", result));
|
||||
}
|
||||
|
||||
private Guid GetAdminId()
|
||||
{
|
||||
var userIdClaim = User.FindFirst("sub")?.Value ?? User.FindFirst("id")?.Value;
|
||||
return Guid.TryParse(userIdClaim, out var userId) ? userId : Guid.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
#region Admin Request/Response DTOs
|
||||
|
||||
public record AdminAdjustPointsRequest(long Points, string Reason);
|
||||
public record AdminGrantBonusRequest(long Points, string Reason, int? ExpiryMonths = 12);
|
||||
|
||||
#endregion
|
||||
@@ -0,0 +1,190 @@
|
||||
namespace WalletService.API.Controllers.Admin;
|
||||
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Swashbuckle.AspNetCore.Annotations;
|
||||
using WalletService.API.Application.Commands;
|
||||
using WalletService.API.Application.Queries;
|
||||
using WalletService.API.Controllers;
|
||||
using WalletService.Domain.AggregatesModel.WalletAggregate;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Admin controller for wallet management operations (Backoffice).
|
||||
/// VI: Controller Admin cho các thao tác quản lý ví (Backoffice).
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/v1/admin/wallets")]
|
||||
[Authorize(Roles = "Admin,SuperAdmin")]
|
||||
[SwaggerTag("Admin Wallet Management / Quản lý ví Admin")]
|
||||
public class AdminWalletsController : ControllerBase
|
||||
{
|
||||
private readonly IMediator _mediator;
|
||||
private readonly IWalletRepository _walletRepository;
|
||||
private readonly ILogger<AdminWalletsController> _logger;
|
||||
|
||||
public AdminWalletsController(
|
||||
IMediator mediator,
|
||||
IWalletRepository walletRepository,
|
||||
ILogger<AdminWalletsController> logger)
|
||||
{
|
||||
_mediator = mediator;
|
||||
_walletRepository = walletRepository;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Get all wallets with pagination.
|
||||
/// VI: Lấy tất cả ví với phân trang.
|
||||
/// </summary>
|
||||
[HttpGet]
|
||||
[SwaggerOperation(Summary = "Get all wallets", Description = "Get all wallets with pagination (Admin only)")]
|
||||
[SwaggerResponse(200, "Success", typeof(ApiResponse<AdminWalletsListDto>))]
|
||||
public async Task<IActionResult> GetAllWallets(
|
||||
[FromQuery] int page = 1,
|
||||
[FromQuery] int pageSize = 20,
|
||||
[FromQuery] string? status = null,
|
||||
[FromQuery] string? currency = null)
|
||||
{
|
||||
var query = new GetAllWalletsQuery(page, pageSize, status, currency);
|
||||
var result = await _mediator.Send(query);
|
||||
|
||||
return Ok(new ApiResponse<AdminWalletsListDto>(true, "Success", result));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Get wallet details by ID.
|
||||
/// VI: Lấy chi tiết ví theo ID.
|
||||
/// </summary>
|
||||
[HttpGet("{walletId:guid}")]
|
||||
[SwaggerOperation(Summary = "Get wallet by ID", Description = "Get wallet details by wallet ID (Admin only)")]
|
||||
[SwaggerResponse(200, "Success", typeof(ApiResponse<AdminWalletDetailDto>))]
|
||||
[SwaggerResponse(404, "Wallet not found")]
|
||||
public async Task<IActionResult> GetWalletById(Guid walletId)
|
||||
{
|
||||
var query = new GetWalletByIdQuery(walletId);
|
||||
var result = await _mediator.Send(query);
|
||||
|
||||
if (result == null)
|
||||
return NotFound(new ApiResponse<object>(false, "Wallet not found"));
|
||||
|
||||
return Ok(new ApiResponse<AdminWalletDetailDto>(true, "Success", result));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Freeze wallet.
|
||||
/// VI: Đóng băng ví.
|
||||
/// </summary>
|
||||
[HttpPost("{walletId:guid}/freeze")]
|
||||
[SwaggerOperation(Summary = "Freeze wallet", Description = "Freeze wallet to prevent transactions (Admin only)")]
|
||||
[SwaggerResponse(200, "Wallet frozen")]
|
||||
[SwaggerResponse(404, "Wallet not found")]
|
||||
public async Task<IActionResult> FreezeWallet(Guid walletId, [FromBody] AdminActionRequest request)
|
||||
{
|
||||
var command = new AdminFreezeWalletCommand(walletId, request.Reason, GetAdminId());
|
||||
var result = await _mediator.Send(command);
|
||||
|
||||
_logger.LogInformation(
|
||||
"Admin {AdminId} froze wallet {WalletId}. Reason: {Reason}",
|
||||
GetAdminId(), walletId, request.Reason);
|
||||
|
||||
return Ok(new ApiResponse<AdminWalletActionResult>(true, "Wallet frozen successfully", result));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Unfreeze wallet.
|
||||
/// VI: Mở băng ví.
|
||||
/// </summary>
|
||||
[HttpPost("{walletId:guid}/unfreeze")]
|
||||
[SwaggerOperation(Summary = "Unfreeze wallet", Description = "Unfreeze wallet to allow transactions (Admin only)")]
|
||||
[SwaggerResponse(200, "Wallet unfrozen")]
|
||||
[SwaggerResponse(404, "Wallet not found")]
|
||||
public async Task<IActionResult> UnfreezeWallet(Guid walletId, [FromBody] AdminActionRequest request)
|
||||
{
|
||||
var command = new AdminUnfreezeWalletCommand(walletId, request.Reason, GetAdminId());
|
||||
var result = await _mediator.Send(command);
|
||||
|
||||
_logger.LogInformation(
|
||||
"Admin {AdminId} unfroze wallet {WalletId}. Reason: {Reason}",
|
||||
GetAdminId(), walletId, request.Reason);
|
||||
|
||||
return Ok(new ApiResponse<AdminWalletActionResult>(true, "Wallet unfrozen successfully", result));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Adjust wallet balance (credit/debit).
|
||||
/// VI: Điều chỉnh số dư ví (cộng/trừ).
|
||||
/// </summary>
|
||||
[HttpPost("{walletId:guid}/adjust")]
|
||||
[SwaggerOperation(Summary = "Adjust balance", Description = "Adjust wallet balance (Admin only)")]
|
||||
[SwaggerResponse(200, "Balance adjusted")]
|
||||
[SwaggerResponse(400, "Invalid adjustment")]
|
||||
[SwaggerResponse(404, "Wallet not found")]
|
||||
public async Task<IActionResult> AdjustBalance(Guid walletId, [FromBody] AdminAdjustBalanceRequest request)
|
||||
{
|
||||
var command = new AdminAdjustBalanceCommand(
|
||||
walletId,
|
||||
request.Amount,
|
||||
request.CurrencyTypeId,
|
||||
request.Reason,
|
||||
GetAdminId());
|
||||
|
||||
var result = await _mediator.Send(command);
|
||||
|
||||
_logger.LogInformation(
|
||||
"Admin {AdminId} adjusted wallet {WalletId} by {Amount}. Reason: {Reason}",
|
||||
GetAdminId(), walletId, request.Amount, request.Reason);
|
||||
|
||||
return Ok(new ApiResponse<AdminAdjustBalanceResult>(true, "Balance adjusted successfully", result));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Get wallet statistics.
|
||||
/// VI: Lấy thống kê ví.
|
||||
/// </summary>
|
||||
[HttpGet("statistics")]
|
||||
[SwaggerOperation(Summary = "Get statistics", Description = "Get wallet statistics (Admin only)")]
|
||||
[SwaggerResponse(200, "Success", typeof(ApiResponse<WalletStatisticsDto>))]
|
||||
public async Task<IActionResult> GetStatistics()
|
||||
{
|
||||
var query = new GetWalletStatisticsQuery();
|
||||
var result = await _mediator.Send(query);
|
||||
|
||||
return Ok(new ApiResponse<WalletStatisticsDto>(true, "Success", result));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Search wallets by user ID or wallet ID.
|
||||
/// VI: Tìm kiếm ví theo user ID hoặc wallet ID.
|
||||
/// </summary>
|
||||
[HttpGet("search")]
|
||||
[SwaggerOperation(Summary = "Search wallets", Description = "Search wallets by user ID or wallet ID (Admin only)")]
|
||||
[SwaggerResponse(200, "Success")]
|
||||
public async Task<IActionResult> SearchWallets(
|
||||
[FromQuery] Guid? userId = null,
|
||||
[FromQuery] Guid? walletId = null,
|
||||
[FromQuery] string? status = null)
|
||||
{
|
||||
var query = new SearchWalletsQuery(userId, walletId, status);
|
||||
var result = await _mediator.Send(query);
|
||||
|
||||
return Ok(new ApiResponse<List<AdminWalletDetailDto>>(true, "Success", result));
|
||||
}
|
||||
|
||||
private Guid GetAdminId()
|
||||
{
|
||||
var userIdClaim = User.FindFirst("sub")?.Value ?? User.FindFirst("id")?.Value;
|
||||
return Guid.TryParse(userIdClaim, out var userId) ? userId : Guid.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
#region Admin Request/Response DTOs
|
||||
|
||||
public record AdminActionRequest(string Reason);
|
||||
|
||||
public record AdminAdjustBalanceRequest(
|
||||
decimal Amount,
|
||||
int CurrencyTypeId,
|
||||
string Reason);
|
||||
|
||||
#endregion
|
||||
Reference in New Issue
Block a user