| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144 |
- using Application.Abstractions.Data;
- using Application.Abstractions.Messaging;
- using Microsoft.EntityFrameworkCore;
- namespace Application.Features.Admin.Donation.Rank.Search;
- public sealed class Handler(IAppDbContext db) : IQueryHandler<Query, Response>
- {
- public async Task<Response> Handle(Query request, CancellationToken ct)
- {
- // 탭 → 기간 범위 (롤링 윈도우)
- var now = DateTime.UtcNow;
- var periodStart = request.Tab switch
- {
- 0 => now.AddHours(-24), // 오늘
- 1 => now.AddHours(-72), // 3일
- 2 => now.AddDays(-7), // 1주일
- 3 => now.AddDays(-30), // 1개월
- 4 => now.AddDays(-90), // 3개월
- 5 => now.AddDays(-180), // 6개월
- 6 => now.AddDays(-365), // 1년
- _ => now.AddHours(-24)
- };
- var donations = db.Donation.AsNoTracking().Where(d => d.CreatedAt >= periodStart && d.CreatedAt <= now);
- // 키워드 검색 (검색 창은 후원 내역과 동일)
- if (!string.IsNullOrWhiteSpace(request.Keyword))
- {
- donations = request.Search switch
- {
- 1 => donations.Where(x => x.Sponsor != null && x.Sponsor.ID.ToString().Contains(request.Keyword)),
- 2 => donations.Where(x => x.Sponsor != null && x.Sponsor.SID.Contains(request.Keyword)),
- 3 => donations.Where(x => x.Sponsor != null && x.Sponsor.Name != null && x.Sponsor.Name.Contains(request.Keyword)),
- 4 => donations.Where(x => x.Receiver != null && x.Receiver.ID.ToString().Contains(request.Keyword)),
- 5 => donations.Where(x => x.Receiver != null && x.Receiver.SID.Contains(request.Keyword)),
- 6 => donations.Where(x => x.Receiver != null && x.Receiver.Name != null && x.Receiver.Name.Contains(request.Keyword)),
- 7 => donations.Where(x => x.SendName.Contains(request.Keyword)),
- _ => donations
- };
- }
- // 기간 추가 필터 (StartAt/EndAt) — 탭 기간 내에서 추가로 좁히기
- if (!string.IsNullOrWhiteSpace(request.StartAt) && DateTime.TryParse(request.StartAt, out var startAt))
- {
- donations = donations.Where(x => x.CreatedAt >= startAt);
- }
- if (!string.IsNullOrWhiteSpace(request.EndAt) && DateTime.TryParse(request.EndAt, out var endAt))
- {
- donations = donations.Where(x => x.CreatedAt <= endAt);
- }
- // 후원자별 집계
- var grouped = donations.GroupBy(d => d.SponsorMemberID).Select(g => new
- {
- SponsorMemberID = g.Key,
- TotalAmount = g.Sum(x => (long)x.Amount),
- DonationCount = g.Count(),
- LastDonatedAt = g.Max(x => (DateTime?)x.CreatedAt)
- });
- // 정렬 + 순위
- grouped = request.Sort switch
- {
- 1 => grouped.OrderByDescending(x => x.TotalAmount).ThenByDescending(x => x.DonationCount),
- 2 => grouped.OrderBy(x => x.TotalAmount).ThenBy(x => x.DonationCount),
- 3 => grouped.OrderByDescending(x => x.DonationCount).ThenByDescending(x => x.TotalAmount),
- 4 => grouped.OrderBy(x => x.DonationCount).ThenBy(x => x.TotalAmount),
- _ => grouped.OrderByDescending(x => x.TotalAmount).ThenByDescending(x => x.DonationCount)
- };
- var total = await grouped.CountAsync(ct);
- var skip = (request.Page - 1) * request.PerPage;
- var paged = await grouped.Skip(skip).Take(request.PerPage).ToListAsync(ct);
- // 마지막 피후원자 lookup — 후원자별 마지막 Donation의 Receiver
- var sponsorIDs = paged.Select(p => p.SponsorMemberID).ToArray();
- var lastReceivers = await donations
- .Where(d => sponsorIDs.Contains(d.SponsorMemberID))
- .GroupBy(d => d.SponsorMemberID)
- .Select(g => new
- {
- SponsorMemberID = g.Key,
- LastDonationID = g.OrderByDescending(x => x.CreatedAt).ThenByDescending(x => x.ID).First().ID
- })
- .ToListAsync(ct);
- var lastDonationIDs = lastReceivers.Select(r => r.LastDonationID).ToArray();
- var lastDonations = await db.Donation.AsNoTracking()
- .Where(d => lastDonationIDs.Contains(d.ID))
- .Include(d => d.Receiver)
- .Select(d => new
- {
- d.ID,
- d.SponsorMemberID,
- ReceiverID = d.Receiver != null ? d.Receiver.ID : (int?)null,
- ReceiverEmail = d.Receiver != null ? d.Receiver.Email : null,
- ReceiverName = d.Receiver != null ? d.Receiver.Name : null
- })
- .ToListAsync(ct);
- var lastBySponsor = lastDonations.ToDictionary(x => x.SponsorMemberID);
- // Sponsor 정보 lookup
- var sponsors = await db.Member.AsNoTracking()
- .Where(m => sponsorIDs.Contains(m.ID))
- .Select(m => new { m.ID, m.Email, m.Name })
- .ToListAsync(ct);
- var sponsorByID = sponsors.ToDictionary(x => x.ID);
- // Rank 번호 — 현재 페이지에서 skip+1 부터 순서대로
- var rows = paged.Select((x, idx) =>
- {
- sponsorByID.TryGetValue(x.SponsorMemberID, out var sponsor);
- lastBySponsor.TryGetValue(x.SponsorMemberID, out var last);
- return new Response.Row
- {
- Num = total - skip - idx,
- Rank = skip + idx + 1,
- SponsorMemberID = x.SponsorMemberID,
- SponsorEmail = sponsor?.Email ?? string.Empty,
- SponsorName = sponsor?.Name,
- TotalAmount = x.TotalAmount,
- DonationCount = x.DonationCount,
- LastReceiverID = last?.ReceiverID,
- LastReceiverEmail = last?.ReceiverEmail,
- LastReceiverName = last?.ReceiverName,
- LastDonatedAt = x.LastDonatedAt
- };
- }).ToList();
- return new Response
- {
- Total = total,
- List = rows
- };
- }
- }
|