Handler.cs 4.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. using Application.Abstractions.Data;
  2. using Application.Abstractions.Messaging;
  3. using Application.Features.Api.Studio.Helpers;
  4. using Microsoft.EntityFrameworkCore;
  5. using SharedKernel.Results;
  6. namespace Application.Features.Api.Studio.Wallet.GetRevenue;
  7. internal sealed class Handler(IAppDbContext db) : IQueryHandler<Query, Result<Response>>
  8. {
  9. public async Task<Result<Response>> Handle(Query request, CancellationToken ct)
  10. {
  11. var (start, end) = PeriodHelper.GetRange(request.Period);
  12. var baseQuery = db.Donation.AsNoTracking()
  13. .Where(d => d.ReceiverMemberID == request.MemberID && !d.IsTest && d.CreatedAt >= start && d.CreatedAt <= end);
  14. // Summary
  15. var total = await baseQuery.CountAsync(ct);
  16. var grossSum = total > 0 ? await baseQuery.SumAsync(d => d.Amount, ct) : 0;
  17. var feeSum = total > 0 ? await baseQuery.SumAsync(d => d.FeeAmount, ct) : 0;
  18. var netSum = total > 0 ? await baseQuery.SumAsync(d => d.NetAmount, ct) : 0;
  19. // 누적 판매 수수료 (같은 기간 — 채널 보상 입금)
  20. var totalStoreCommission = await db.WalletTransaction.AsNoTracking()
  21. .Where(t => t.Wallet!.MemberID == request.MemberID
  22. && t.BalanceType == Domain.Entities.Wallets.ValueObject.WalletBalanceType.StoreRevenue
  23. && t.TxType == Domain.Entities.Wallets.ValueObject.WalletTransactionType.OrderChannelReward
  24. && t.CreatedAt >= start && t.CreatedAt <= end)
  25. .SumAsync(t => (int)t.Amount.Value, ct);
  26. // API 결제 보고 수수료 — 기간 내 확정 입금분
  27. var totalApiCommission = await db.WalletTransaction.AsNoTracking()
  28. .Where(t => t.Wallet!.MemberID == request.MemberID
  29. && t.BalanceType == Domain.Entities.Wallets.ValueObject.WalletBalanceType.StoreRevenue
  30. && t.TxType == Domain.Entities.Wallets.ValueObject.WalletTransactionType.ApiCommissionEarned
  31. && t.CreatedAt >= start && t.CreatedAt <= end)
  32. .SumAsync(t => (int?)t.Amount.Value, ct) ?? 0;
  33. // 보류 중(미확정) 수수료 — 기간 무관 실시간 잔여 전체. 확정 시 지갑 입금, 14일 내 취소 시 소멸
  34. var pendingQuery = db.ApiPurchase.AsNoTracking()
  35. .Where(c => c.CreditedMemberID == request.MemberID && c.Status == Domain.Entities.Developers.ValueObject.ApiPurchaseStatus.Pending);
  36. var pendingApiCommission = await pendingQuery.SumAsync(c => (int?)c.CommissionAmount, ct) ?? 0;
  37. var pendingList = await pendingQuery
  38. .OrderBy(c => c.ConfirmDueAt)
  39. .Take(50)
  40. .Select(c => new PendingCommissionItem(
  41. c.ID,
  42. c.Game!.KorName,
  43. c.CommissionAmount,
  44. c.ConfirmDueAt,
  45. c.CreatedAt
  46. ))
  47. .ToListAsync(ct);
  48. var summary = new RevenueSummary(grossSum, feeSum, netSum, totalStoreCommission, totalApiCommission, pendingApiCommission);
  49. // Paged list
  50. var list = await baseQuery
  51. .OrderByDescending(d => d.CreatedAt)
  52. .Skip((request.Page - 1) * request.PerPage)
  53. .Take(request.PerPage)
  54. .Select(d => new RevenueItem(
  55. d.ID,
  56. d.SendName,
  57. d.Sponsor != null ? d.Sponsor.SID : null,
  58. d.Amount,
  59. d.FeeAmount,
  60. d.NetAmount,
  61. d.CrewSessionID != null ? "crew_donation" : "donation",
  62. d.CrewMember != null ? d.CrewMember.Nickname : null,
  63. d.CreatedAt
  64. ))
  65. .ToListAsync(ct);
  66. // Chart data — daily grouping
  67. var chartRaw = await baseQuery
  68. .GroupBy(d => d.CreatedAt.Date)
  69. .Select(g => new
  70. {
  71. Date = g.Key,
  72. GrossAmount = g.Sum(d => d.Amount),
  73. PlatformFee = g.Sum(d => d.FeeAmount),
  74. NetAmount = g.Sum(d => d.NetAmount)
  75. })
  76. .OrderBy(x => x.Date)
  77. .ToListAsync(ct);
  78. var chartData = chartRaw.Select(c => new ChartItem(c.Date.ToString("MM/dd"), c.GrossAmount, c.PlatformFee, c.NetAmount)).ToList();
  79. return Result.Success(new Response(total, summary, list, chartData, pendingList));
  80. }
  81. }