feat: Bổ sung các chức năng quản trị viên để quản lý ví và tài khoản điểm, bao gồm các lệnh điều chỉnh và truy vấn.
This commit is contained in:
@@ -301,7 +301,7 @@ services:
|
||||
start_period: 40s
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.wallet-service.rule=PathPrefix(`/api/v1/wallets`) || PathPrefix(`/api/v1/points`)"
|
||||
- "traefik.http.routers.wallet-service.rule=PathPrefix(`/api/v1/wallets`) || PathPrefix(`/api/v1/points`) || PathPrefix(`/api/v1/admin/wallets`) || PathPrefix(`/api/v1/admin/points`)"
|
||||
- "traefik.http.routers.wallet-service.entrypoints=web"
|
||||
- "traefik.http.services.wallet-service.loadbalancer.server.port=8080"
|
||||
- "traefik.http.services.wallet-service.loadbalancer.healthcheck.path=/health/live"
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
namespace WalletService.API.Application.Commands;
|
||||
|
||||
using MediatR;
|
||||
using WalletService.Domain.AggregatesModel.PointAccountAggregate;
|
||||
using WalletService.Domain.Exceptions;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Handler for AdminAdjustPointsCommand.
|
||||
/// VI: Handler cho AdminAdjustPointsCommand.
|
||||
/// </summary>
|
||||
public class AdminAdjustPointsCommandHandler : IRequestHandler<AdminAdjustPointsCommand, AdminAdjustPointsResult>
|
||||
{
|
||||
private readonly IPointAccountRepository _pointAccountRepository;
|
||||
private readonly ILogger<AdminAdjustPointsCommandHandler> _logger;
|
||||
|
||||
public AdminAdjustPointsCommandHandler(
|
||||
IPointAccountRepository pointAccountRepository,
|
||||
ILogger<AdminAdjustPointsCommandHandler> logger)
|
||||
{
|
||||
_pointAccountRepository = pointAccountRepository;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<AdminAdjustPointsResult> Handle(
|
||||
AdminAdjustPointsCommand request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var account = await _pointAccountRepository.GetByIdAsync(request.AccountId)
|
||||
?? throw new WalletDomainException($"Point account {request.AccountId} not found");
|
||||
|
||||
var previousPoints = account.AvailablePoints;
|
||||
|
||||
// EN: Adjust points directly using domain method
|
||||
// VI: Điều chỉnh điểm trực tiếp bằng phương thức domain
|
||||
account.AdjustPoints(request.Points, $"Admin Adjustment: {request.Reason}");
|
||||
|
||||
_pointAccountRepository.Update(account);
|
||||
await _pointAccountRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken);
|
||||
|
||||
_logger.LogInformation(
|
||||
"Point account {AccountId} adjusted by Admin {AdminId}. Points: {Points}. Reason: {Reason}",
|
||||
request.AccountId, request.AdminId, request.Points, request.Reason);
|
||||
|
||||
return new AdminAdjustPointsResult(
|
||||
account.Id,
|
||||
previousPoints,
|
||||
request.Points,
|
||||
account.AvailablePoints,
|
||||
request.Reason,
|
||||
DateTime.UtcNow);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Handler for AdminGrantBonusCommand.
|
||||
/// VI: Handler cho AdminGrantBonusCommand.
|
||||
/// </summary>
|
||||
public class AdminGrantBonusCommandHandler : IRequestHandler<AdminGrantBonusCommand, AdminGrantBonusResult>
|
||||
{
|
||||
private readonly IPointAccountRepository _pointAccountRepository;
|
||||
private readonly ILogger<AdminGrantBonusCommandHandler> _logger;
|
||||
|
||||
public AdminGrantBonusCommandHandler(
|
||||
IPointAccountRepository pointAccountRepository,
|
||||
ILogger<AdminGrantBonusCommandHandler> logger)
|
||||
{
|
||||
_pointAccountRepository = pointAccountRepository;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<AdminGrantBonusResult> Handle(
|
||||
AdminGrantBonusCommand request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var account = await _pointAccountRepository.GetByIdAsync(request.AccountId)
|
||||
?? throw new WalletDomainException($"Point account {request.AccountId} not found");
|
||||
|
||||
var expiryMonths = request.ExpiryMonths ?? 12;
|
||||
var expiresAt = DateTime.UtcNow.AddMonths(expiryMonths);
|
||||
|
||||
// EN: Earn bonus points with expiry
|
||||
// VI: Tích điểm thưởng với thời hạn
|
||||
account.AddBonusPoints(request.Points, "AdminBonus", $"Admin Bonus: {request.Reason}", expiresAt);
|
||||
|
||||
_pointAccountRepository.Update(account);
|
||||
await _pointAccountRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken);
|
||||
|
||||
_logger.LogInformation(
|
||||
"Point account {AccountId} granted {Points} bonus points by Admin {AdminId}. Reason: {Reason}",
|
||||
request.AccountId, request.Points, request.AdminId, request.Reason);
|
||||
|
||||
return new AdminGrantBonusResult(
|
||||
account.Id,
|
||||
request.Points,
|
||||
account.AvailablePoints,
|
||||
expiresAt,
|
||||
DateTime.UtcNow);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
namespace WalletService.API.Application.Commands;
|
||||
|
||||
using MediatR;
|
||||
using WalletService.Domain.AggregatesModel.WalletAggregate;
|
||||
using WalletService.Domain.Exceptions;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Handler for AdminFreezeWalletCommand.
|
||||
/// VI: Handler cho AdminFreezeWalletCommand.
|
||||
/// </summary>
|
||||
public class AdminFreezeWalletCommandHandler : IRequestHandler<AdminFreezeWalletCommand, AdminWalletActionResult>
|
||||
{
|
||||
private readonly IWalletRepository _walletRepository;
|
||||
private readonly ILogger<AdminFreezeWalletCommandHandler> _logger;
|
||||
|
||||
public AdminFreezeWalletCommandHandler(
|
||||
IWalletRepository walletRepository,
|
||||
ILogger<AdminFreezeWalletCommandHandler> logger)
|
||||
{
|
||||
_walletRepository = walletRepository;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<AdminWalletActionResult> Handle(
|
||||
AdminFreezeWalletCommand request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var wallet = await _walletRepository.GetByIdAsync(request.WalletId)
|
||||
?? throw new WalletDomainException($"Wallet {request.WalletId} not found");
|
||||
|
||||
wallet.Freeze();
|
||||
|
||||
_walletRepository.Update(wallet);
|
||||
await _walletRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken);
|
||||
|
||||
_logger.LogInformation(
|
||||
"Wallet {WalletId} frozen by Admin {AdminId}. Reason: {Reason}",
|
||||
request.WalletId, request.AdminId, request.Reason);
|
||||
|
||||
return new AdminWalletActionResult(
|
||||
wallet.Id,
|
||||
wallet.Status.Name,
|
||||
request.AdminId.ToString(),
|
||||
DateTime.UtcNow);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Handler for AdminUnfreezeWalletCommand.
|
||||
/// VI: Handler cho AdminUnfreezeWalletCommand.
|
||||
/// </summary>
|
||||
public class AdminUnfreezeWalletCommandHandler : IRequestHandler<AdminUnfreezeWalletCommand, AdminWalletActionResult>
|
||||
{
|
||||
private readonly IWalletRepository _walletRepository;
|
||||
private readonly ILogger<AdminUnfreezeWalletCommandHandler> _logger;
|
||||
|
||||
public AdminUnfreezeWalletCommandHandler(
|
||||
IWalletRepository walletRepository,
|
||||
ILogger<AdminUnfreezeWalletCommandHandler> logger)
|
||||
{
|
||||
_walletRepository = walletRepository;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<AdminWalletActionResult> Handle(
|
||||
AdminUnfreezeWalletCommand request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var wallet = await _walletRepository.GetByIdAsync(request.WalletId)
|
||||
?? throw new WalletDomainException($"Wallet {request.WalletId} not found");
|
||||
|
||||
wallet.Unfreeze();
|
||||
|
||||
_walletRepository.Update(wallet);
|
||||
await _walletRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken);
|
||||
|
||||
_logger.LogInformation(
|
||||
"Wallet {WalletId} unfrozen by Admin {AdminId}. Reason: {Reason}",
|
||||
request.WalletId, request.AdminId, request.Reason);
|
||||
|
||||
return new AdminWalletActionResult(
|
||||
wallet.Id,
|
||||
wallet.Status.Name,
|
||||
request.AdminId.ToString(),
|
||||
DateTime.UtcNow);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Handler for AdminAdjustBalanceCommand.
|
||||
/// VI: Handler cho AdminAdjustBalanceCommand.
|
||||
/// </summary>
|
||||
public class AdminAdjustBalanceCommandHandler : IRequestHandler<AdminAdjustBalanceCommand, AdminAdjustBalanceResult>
|
||||
{
|
||||
private readonly IWalletRepository _walletRepository;
|
||||
private readonly ILogger<AdminAdjustBalanceCommandHandler> _logger;
|
||||
|
||||
public AdminAdjustBalanceCommandHandler(
|
||||
IWalletRepository walletRepository,
|
||||
ILogger<AdminAdjustBalanceCommandHandler> logger)
|
||||
{
|
||||
_walletRepository = walletRepository;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<AdminAdjustBalanceResult> Handle(
|
||||
AdminAdjustBalanceCommand request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var wallet = await _walletRepository.GetByIdAsync(request.WalletId)
|
||||
?? throw new WalletDomainException($"Wallet {request.WalletId} not found");
|
||||
|
||||
var currencyType = Domain.SeedWork.Enumeration.FromValue<CurrencyType>(request.CurrencyTypeId);
|
||||
var previousBalance = wallet.GetBalance(currencyType);
|
||||
|
||||
// EN: Positive amount = credit, negative = debit
|
||||
// VI: Số dương = cộng, số âm = trừ
|
||||
if (request.Amount > 0)
|
||||
{
|
||||
wallet.Deposit(request.Amount, currencyType, $"Admin Adjustment: {request.Reason}");
|
||||
}
|
||||
else if (request.Amount < 0)
|
||||
{
|
||||
wallet.Withdraw(Math.Abs(request.Amount), currencyType, $"Admin Adjustment: {request.Reason}");
|
||||
}
|
||||
|
||||
_walletRepository.Update(wallet);
|
||||
await _walletRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken);
|
||||
|
||||
var newBalance = wallet.GetBalance(currencyType);
|
||||
|
||||
_logger.LogInformation(
|
||||
"Wallet {WalletId} balance adjusted by Admin {AdminId}. Amount: {Amount} {Currency}. Reason: {Reason}",
|
||||
request.WalletId, request.AdminId, request.Amount, currencyType.Name, request.Reason);
|
||||
|
||||
return new AdminAdjustBalanceResult(
|
||||
wallet.Id,
|
||||
previousBalance,
|
||||
request.Amount,
|
||||
newBalance,
|
||||
currencyType.Name,
|
||||
request.Reason,
|
||||
DateTime.UtcNow);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
namespace WalletService.API.Application.Queries;
|
||||
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using WalletService.Domain.AggregatesModel.PointAccountAggregate;
|
||||
using WalletService.Infrastructure;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Handler for GetAllPointAccountsQuery (Admin).
|
||||
/// VI: Handler cho GetAllPointAccountsQuery (Admin).
|
||||
/// </summary>
|
||||
public class GetAllPointAccountsQueryHandler : IRequestHandler<GetAllPointAccountsQuery, AdminPointAccountsListDto>
|
||||
{
|
||||
private readonly WalletServiceContext _context;
|
||||
|
||||
public GetAllPointAccountsQueryHandler(WalletServiceContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<AdminPointAccountsListDto> Handle(
|
||||
GetAllPointAccountsQuery request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var query = _context.PointAccounts.AsQueryable();
|
||||
|
||||
// EN: Filter by points range if provided
|
||||
// VI: Lọc theo khoảng điểm nếu có
|
||||
if (request.MinPoints.HasValue)
|
||||
query = query.Where(p => p.AvailablePoints >= request.MinPoints.Value);
|
||||
|
||||
if (request.MaxPoints.HasValue)
|
||||
query = query.Where(p => p.AvailablePoints <= request.MaxPoints.Value);
|
||||
|
||||
var totalCount = await query.CountAsync(cancellationToken);
|
||||
|
||||
var accounts = await query
|
||||
.OrderByDescending(p => p.TotalPoints)
|
||||
.Skip((request.Page - 1) * request.PageSize)
|
||||
.Take(request.PageSize)
|
||||
.Select(p => new AdminPointAccountSummaryDto(
|
||||
p.Id,
|
||||
p.UserId,
|
||||
p.TotalPoints,
|
||||
p.AvailablePoints,
|
||||
p.CreatedAt))
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
return new AdminPointAccountsListDto(accounts, totalCount, request.Page, request.PageSize);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Handler for GetPointAccountByIdQuery (Admin).
|
||||
/// VI: Handler cho GetPointAccountByIdQuery (Admin).
|
||||
/// </summary>
|
||||
public class GetPointAccountByIdQueryHandler : IRequestHandler<GetPointAccountByIdQuery, AdminPointAccountDetailDto?>
|
||||
{
|
||||
private readonly WalletServiceContext _context;
|
||||
|
||||
public GetPointAccountByIdQueryHandler(WalletServiceContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<AdminPointAccountDetailDto?> Handle(
|
||||
GetPointAccountByIdQuery request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var account = await _context.PointAccounts
|
||||
.Include(p => p.Transactions)
|
||||
.FirstOrDefaultAsync(p => p.Id == request.AccountId, cancellationToken);
|
||||
|
||||
if (account == null) return null;
|
||||
|
||||
return new AdminPointAccountDetailDto(
|
||||
account.Id,
|
||||
account.UserId,
|
||||
account.TotalPoints,
|
||||
account.AvailablePoints,
|
||||
account.Transactions.Count,
|
||||
account.CreatedAt,
|
||||
account.UpdatedAt);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Handler for SearchPointAccountsQuery (Admin).
|
||||
/// VI: Handler cho SearchPointAccountsQuery (Admin).
|
||||
/// </summary>
|
||||
public class SearchPointAccountsQueryHandler : IRequestHandler<SearchPointAccountsQuery, List<AdminPointAccountDetailDto>>
|
||||
{
|
||||
private readonly WalletServiceContext _context;
|
||||
|
||||
public SearchPointAccountsQueryHandler(WalletServiceContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<List<AdminPointAccountDetailDto>> Handle(
|
||||
SearchPointAccountsQuery request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var query = _context.PointAccounts
|
||||
.Include(p => p.Transactions)
|
||||
.AsQueryable();
|
||||
|
||||
if (request.UserId.HasValue)
|
||||
query = query.Where(p => p.UserId == request.UserId.Value);
|
||||
|
||||
var accounts = await query
|
||||
.Take(50)
|
||||
.Select(p => new AdminPointAccountDetailDto(
|
||||
p.Id,
|
||||
p.UserId,
|
||||
p.TotalPoints,
|
||||
p.AvailablePoints,
|
||||
p.Transactions.Count,
|
||||
p.CreatedAt,
|
||||
p.UpdatedAt))
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
return accounts;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Handler for GetPointsStatisticsQuery (Admin).
|
||||
/// VI: Handler cho GetPointsStatisticsQuery (Admin).
|
||||
/// </summary>
|
||||
public class GetPointsStatisticsQueryHandler : IRequestHandler<GetPointsStatisticsQuery, PointsStatisticsDto>
|
||||
{
|
||||
private readonly WalletServiceContext _context;
|
||||
|
||||
public GetPointsStatisticsQueryHandler(WalletServiceContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<PointsStatisticsDto> Handle(
|
||||
GetPointsStatisticsQuery request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var today = DateTime.UtcNow.Date;
|
||||
|
||||
var totalAccounts = await _context.PointAccounts.CountAsync(cancellationToken);
|
||||
|
||||
var totalPointsIssued = await _context.PointAccounts
|
||||
.SumAsync(p => p.TotalPoints, cancellationToken);
|
||||
|
||||
var totalPointsAvailable = await _context.PointAccounts
|
||||
.SumAsync(p => p.AvailablePoints, cancellationToken);
|
||||
|
||||
// EN: Calculate spent and expired from transactions
|
||||
// VI: Tính điểm đã tiêu và hết hạn từ giao dịch
|
||||
var totalPointsSpent = await _context.PointTransactions
|
||||
.Where(t => t.TypeId == PointTransactionType.Spend.Id)
|
||||
.SumAsync(t => t.Points, cancellationToken);
|
||||
|
||||
var totalPointsExpired = await _context.PointTransactions
|
||||
.Where(t => t.TypeId == PointTransactionType.Expire.Id)
|
||||
.SumAsync(t => t.Points, cancellationToken);
|
||||
|
||||
var pointsEarnedToday = await _context.PointTransactions
|
||||
.Where(t => t.CreatedAt >= today && t.TypeId == PointTransactionType.Earn.Id)
|
||||
.SumAsync(t => t.Points, cancellationToken);
|
||||
|
||||
var pointsSpentToday = await _context.PointTransactions
|
||||
.Where(t => t.CreatedAt >= today && t.TypeId == PointTransactionType.Spend.Id)
|
||||
.SumAsync(t => t.Points, cancellationToken);
|
||||
|
||||
return new PointsStatisticsDto(
|
||||
totalAccounts,
|
||||
totalPointsIssued,
|
||||
totalPointsAvailable,
|
||||
totalPointsSpent,
|
||||
totalPointsExpired,
|
||||
pointsEarnedToday,
|
||||
pointsSpentToday);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
namespace WalletService.API.Application.Queries;
|
||||
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using WalletService.Domain.AggregatesModel.WalletAggregate;
|
||||
using WalletService.Infrastructure;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Handler for GetAllWalletsQuery (Admin).
|
||||
/// VI: Handler cho GetAllWalletsQuery (Admin).
|
||||
/// </summary>
|
||||
public class GetAllWalletsQueryHandler : IRequestHandler<GetAllWalletsQuery, AdminWalletsListDto>
|
||||
{
|
||||
private readonly WalletServiceContext _context;
|
||||
|
||||
public GetAllWalletsQueryHandler(WalletServiceContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<AdminWalletsListDto> Handle(
|
||||
GetAllWalletsQuery request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var query = _context.Wallets
|
||||
.Include(w => w.Balances)
|
||||
.AsQueryable();
|
||||
|
||||
// EN: Filter by status if provided
|
||||
// VI: Lọc theo trạng thái nếu có
|
||||
if (!string.IsNullOrEmpty(request.Status))
|
||||
{
|
||||
var status = WalletStatus.FromDisplayName<WalletStatus>(request.Status);
|
||||
if (status != null)
|
||||
query = query.Where(w => w.StatusId == status.Id);
|
||||
}
|
||||
|
||||
var totalCount = await query.CountAsync(cancellationToken);
|
||||
|
||||
var wallets = await query
|
||||
.OrderByDescending(w => w.CreatedAt)
|
||||
.Skip((request.Page - 1) * request.PageSize)
|
||||
.Take(request.PageSize)
|
||||
.Select(w => new AdminWalletSummaryDto(
|
||||
w.Id,
|
||||
w.UserId,
|
||||
WalletStatus.FromValue<WalletStatus>(w.StatusId).Name,
|
||||
w.Balances.Select(b => new BalanceItemDto(
|
||||
CurrencyType.FromValue<CurrencyType>(b.CurrencyTypeId).Name,
|
||||
b.Balance)).ToList(),
|
||||
w.CreatedAt,
|
||||
w.UpdatedAt))
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
return new AdminWalletsListDto(wallets, totalCount, request.Page, request.PageSize);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Handler for GetWalletByIdQuery (Admin).
|
||||
/// VI: Handler cho GetWalletByIdQuery (Admin).
|
||||
/// </summary>
|
||||
public class GetWalletByIdQueryHandler : IRequestHandler<GetWalletByIdQuery, AdminWalletDetailDto?>
|
||||
{
|
||||
private readonly WalletServiceContext _context;
|
||||
|
||||
public GetWalletByIdQueryHandler(WalletServiceContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<AdminWalletDetailDto?> Handle(
|
||||
GetWalletByIdQuery request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var wallet = await _context.Wallets
|
||||
.Include(w => w.Balances)
|
||||
.Include(w => w.Transactions)
|
||||
.FirstOrDefaultAsync(w => w.Id == request.WalletId, cancellationToken);
|
||||
|
||||
if (wallet == null) return null;
|
||||
|
||||
return new AdminWalletDetailDto(
|
||||
wallet.Id,
|
||||
wallet.UserId,
|
||||
WalletStatus.FromValue<WalletStatus>(wallet.StatusId).Name,
|
||||
wallet.Balances.Select(b => new BalanceItemDto(
|
||||
CurrencyType.FromValue<CurrencyType>(b.CurrencyTypeId).Name,
|
||||
b.Balance)).ToList(),
|
||||
wallet.Transactions.Count,
|
||||
wallet.CreatedAt,
|
||||
wallet.UpdatedAt);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Handler for SearchWalletsQuery (Admin).
|
||||
/// VI: Handler cho SearchWalletsQuery (Admin).
|
||||
/// </summary>
|
||||
public class SearchWalletsQueryHandler : IRequestHandler<SearchWalletsQuery, List<AdminWalletDetailDto>>
|
||||
{
|
||||
private readonly WalletServiceContext _context;
|
||||
|
||||
public SearchWalletsQueryHandler(WalletServiceContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<List<AdminWalletDetailDto>> Handle(
|
||||
SearchWalletsQuery request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var query = _context.Wallets
|
||||
.Include(w => w.Balances)
|
||||
.Include(w => w.Transactions)
|
||||
.AsQueryable();
|
||||
|
||||
if (request.UserId.HasValue)
|
||||
query = query.Where(w => w.UserId == request.UserId.Value);
|
||||
|
||||
if (request.WalletId.HasValue)
|
||||
query = query.Where(w => w.Id == request.WalletId.Value);
|
||||
|
||||
if (!string.IsNullOrEmpty(request.Status))
|
||||
{
|
||||
var status = WalletStatus.FromDisplayName<WalletStatus>(request.Status);
|
||||
if (status != null)
|
||||
query = query.Where(w => w.StatusId == status.Id);
|
||||
}
|
||||
|
||||
var wallets = await query
|
||||
.Take(50) // Limit results
|
||||
.Select(w => new AdminWalletDetailDto(
|
||||
w.Id,
|
||||
w.UserId,
|
||||
WalletStatus.FromValue<WalletStatus>(w.StatusId).Name,
|
||||
w.Balances.Select(b => new BalanceItemDto(
|
||||
CurrencyType.FromValue<CurrencyType>(b.CurrencyTypeId).Name,
|
||||
b.Balance)).ToList(),
|
||||
w.Transactions.Count,
|
||||
w.CreatedAt,
|
||||
w.UpdatedAt))
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
return wallets;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Handler for GetWalletStatisticsQuery (Admin).
|
||||
/// VI: Handler cho GetWalletStatisticsQuery (Admin).
|
||||
/// </summary>
|
||||
public class GetWalletStatisticsQueryHandler : IRequestHandler<GetWalletStatisticsQuery, WalletStatisticsDto>
|
||||
{
|
||||
private readonly WalletServiceContext _context;
|
||||
|
||||
public GetWalletStatisticsQueryHandler(WalletServiceContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<WalletStatisticsDto> Handle(
|
||||
GetWalletStatisticsQuery request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var today = DateTime.UtcNow.Date;
|
||||
|
||||
var totalWallets = await _context.Wallets.CountAsync(cancellationToken);
|
||||
var activeWallets = await _context.Wallets
|
||||
.CountAsync(w => w.StatusId == WalletStatus.Active.Id, cancellationToken);
|
||||
var frozenWallets = await _context.Wallets
|
||||
.CountAsync(w => w.StatusId == WalletStatus.Frozen.Id, cancellationToken);
|
||||
var closedWallets = await _context.Wallets
|
||||
.CountAsync(w => w.StatusId == WalletStatus.Closed.Id, cancellationToken);
|
||||
|
||||
// EN: Total balance by currency
|
||||
// VI: Tổng số dư theo loại tiền tệ
|
||||
var balancesByCurrency = await _context.WalletItems
|
||||
.GroupBy(wi => wi.CurrencyTypeId)
|
||||
.Select(g => new { CurrencyId = g.Key, Total = g.Sum(wi => wi.Balance) })
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
var totalBalanceByCurrency = balancesByCurrency.ToDictionary(
|
||||
b => CurrencyType.FromValue<CurrencyType>(b.CurrencyId).Name,
|
||||
b => b.Total);
|
||||
|
||||
// EN: Today's transactions
|
||||
// VI: Giao dịch hôm nay
|
||||
var todayTransactions = await _context.WalletTransactions
|
||||
.Where(t => t.CreatedAt >= today)
|
||||
.SumAsync(t => t.Amount.Amount, cancellationToken);
|
||||
|
||||
var todayDeposits = await _context.WalletTransactions
|
||||
.Where(t => t.CreatedAt >= today && t.TypeId == TransactionType.Credit.Id)
|
||||
.SumAsync(t => t.Amount.Amount, cancellationToken);
|
||||
|
||||
var todayWithdrawals = await _context.WalletTransactions
|
||||
.Where(t => t.CreatedAt >= today && t.TypeId == TransactionType.Debit.Id)
|
||||
.SumAsync(t => t.Amount.Amount, cancellationToken);
|
||||
|
||||
return new WalletStatisticsDto(
|
||||
totalWallets,
|
||||
activeWallets,
|
||||
frozenWallets,
|
||||
closedWallets,
|
||||
totalBalanceByCurrency,
|
||||
todayTransactions,
|
||||
todayDeposits,
|
||||
todayWithdrawals);
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,12 @@ using WalletService.Domain.SeedWork;
|
||||
/// </summary>
|
||||
public interface IPointAccountRepository : IRepository<PointAccount>
|
||||
{
|
||||
/// <summary>
|
||||
/// EN: Get point account by ID
|
||||
/// VI: Lấy tài khoản điểm theo ID
|
||||
/// </summary>
|
||||
Task<PointAccount?> GetByIdAsync(Guid accountId);
|
||||
|
||||
/// <summary>
|
||||
/// EN: Get point account by user ID
|
||||
/// VI: Lấy tài khoản điểm theo ID người dùng
|
||||
|
||||
@@ -8,6 +8,12 @@ using WalletService.Domain.SeedWork;
|
||||
/// </summary>
|
||||
public interface IWalletRepository : IRepository<Wallet>
|
||||
{
|
||||
/// <summary>
|
||||
/// EN: Get wallet by ID
|
||||
/// VI: Lấy ví theo ID
|
||||
/// </summary>
|
||||
Task<Wallet?> GetByIdAsync(Guid walletId);
|
||||
|
||||
/// <summary>
|
||||
/// EN: Get wallet by user ID
|
||||
/// VI: Lấy ví theo ID người dùng
|
||||
|
||||
@@ -19,6 +19,12 @@ public class PointAccountRepository : IPointAccountRepository
|
||||
_context = context ?? throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
public async Task<PointAccount?> GetByIdAsync(Guid accountId)
|
||||
{
|
||||
return await _context.PointAccounts
|
||||
.FirstOrDefaultAsync(p => p.Id == accountId);
|
||||
}
|
||||
|
||||
public async Task<PointAccount?> GetByUserIdAsync(Guid userId)
|
||||
{
|
||||
return await _context.PointAccounts
|
||||
|
||||
@@ -19,9 +19,17 @@ public class WalletRepository : IWalletRepository
|
||||
_context = context ?? throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
public async Task<Wallet?> GetByIdAsync(Guid walletId)
|
||||
{
|
||||
return await _context.Wallets
|
||||
.Include(w => w.Balances)
|
||||
.FirstOrDefaultAsync(w => w.Id == walletId);
|
||||
}
|
||||
|
||||
public async Task<Wallet?> GetByUserIdAsync(Guid userId)
|
||||
{
|
||||
return await _context.Wallets
|
||||
.Include(w => w.Balances)
|
||||
.FirstOrDefaultAsync(w => w.UserId == userId);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user