using Application.Abstractions.Data; using Application.Abstractions.Messaging; using Domain.Entities.Wallets.ValueObject; using Microsoft.EntityFrameworkCore; namespace Application.Features.Admin.Donation.List.Search; public sealed class Handler(IAppDbContext db) : IQueryHandler { public async Task Handle(Query request, CancellationToken ct) { var query = db.Donation.AsNoTracking().Include(x => x.Sponsor).Include(x => x.Receiver).AsQueryable(); // 키워드 검색 if (!string.IsNullOrWhiteSpace(request.Keyword)) { query = request.Search switch { 1 => query.Where(x => x.Sponsor != null && x.Sponsor.ID.ToString().Contains(request.Keyword)), 2 => query.Where(x => x.Sponsor != null && x.Sponsor.SID.Contains(request.Keyword)), 3 => query.Where(x => x.Sponsor != null && x.Sponsor.Name != null && x.Sponsor.Name.Contains(request.Keyword)), 4 => query.Where(x => x.Receiver != null && x.Receiver.ID.ToString().Contains(request.Keyword)), 5 => query.Where(x => x.Receiver != null && x.Receiver.SID.Contains(request.Keyword)), 6 => query.Where(x => x.Receiver != null && x.Receiver.Name != null && x.Receiver.Name.Contains(request.Keyword)), 7 => query.Where(x => x.SendName.Contains(request.Keyword)), _ => query }; } // 날짜 필터 if (!string.IsNullOrWhiteSpace(request.StartAt) && DateTime.TryParse(request.StartAt, out var startAt)) { query = query.Where(x => x.CreatedAt >= startAt); } if (!string.IsNullOrWhiteSpace(request.EndAt) && DateTime.TryParse(request.EndAt, out var endAt)) { query = query.Where(x => x.CreatedAt <= endAt); } var total = await query.CountAsync(ct); var skip = (request.Page - 1) * request.PerPage; var list = await query .OrderByDescending(x => x.ID) .Skip(skip) .Take(request.PerPage) .Select(x => new { x.ID, x.Amount, x.Message, x.SendName, x.IsTest, x.CreatedAt, x.SponsorMemberID, x.ReceiverMemberID, SponsorID = x.Sponsor != null ? x.Sponsor.ID : 0, SponsorEmail = x.Sponsor != null ? x.Sponsor.Email : string.Empty, SponsorName = x.Sponsor != null ? x.Sponsor.Name : null, SponsorSID = x.Sponsor != null ? x.Sponsor.SID : null, ReceiverID = x.Receiver != null ? x.Receiver.ID : 0, ReceiverEmail = x.Receiver != null ? x.Receiver.Email : string.Empty, ReceiverName = x.Receiver != null ? x.Receiver.Name : null, ReceiverSID = x.Receiver != null ? x.Receiver.SID : null }) .ToListAsync(ct); // 관련 회원의 지갑 잔액 일괄 조회 (행별 N+1 방지) var memberIDs = list.Select(x => x.SponsorMemberID).Concat(list.Select(x => x.ReceiverMemberID)).Distinct().ToArray(); var wallets = await db.Wallet .AsNoTracking() .Where(w => memberIDs.Contains(w.MemberID)) .Include(w => w.Balances) .ToListAsync(ct); var balanceByMember = wallets.ToDictionary( w => w.MemberID, w => new { Total = (long)w.Balances.Where(b => b.Type != WalletBalanceType.Locked).Sum(b => b.Amount.Value), Donation = (long)(w.Balances.FirstOrDefault(b => b.Type == WalletBalanceType.Donation)?.Amount.Value ?? 0) }); var rows = list.Select((x, idx) => new Response.Row { Num = total - skip - idx, ID = x.ID, Amount = x.Amount, Message = x.Message, SendName = x.SendName, IsTest = x.IsTest, Sponsor = new Response.MemberInfo { ID = x.SponsorID, Email = x.SponsorEmail, Name = x.SponsorName, SID = x.SponsorSID }, Receiver = new Response.MemberInfo { ID = x.ReceiverID, Email = x.ReceiverEmail, Name = x.ReceiverName, SID = x.ReceiverSID }, SponsorBalance = balanceByMember.TryGetValue(x.SponsorMemberID, out var sb) ? sb.Total : 0L, ReceiverDonationBalance = balanceByMember.TryGetValue(x.ReceiverMemberID, out var rb) ? rb.Donation : 0L, CreatedAt = x.CreatedAt }).ToList(); return new Response { Total = total, List = rows }; } }