| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129 |
- using Application.Abstractions.Data;
- using Application.Abstractions.Messaging;
- using Domain.Entities.Store.ValueObject;
- using Microsoft.EntityFrameworkCore;
- using SharedKernel.Results;
- namespace Application.Features.Admin.Store.Order.Get;
- public sealed class Handler(IAppDbContext db) : IQueryHandler<Query, Result<Response>>
- {
- public async Task<Result<Response>> Handle(Query request, CancellationToken ct)
- {
- var order = await db.Order
- .AsNoTracking()
- .Include(o => o.Member)
- .Include(o => o.Channel)
- .Include(o => o.Items).ThenInclude(i => i.Product)
- .Include(o => o.Items).ThenInclude(i => i.IssuedCouponCode)
- .Include(o => o.Refunds)
- .Where(o => o.ID == request.OrderID)
- .FirstOrDefaultAsync(ct);
- if (order is null)
- {
- return Result.Failure<Response>(Error.NotFound("Order.NotFound", "주문을 찾을 수 없습니다."));
- }
- var shipment = await db.Shipment
- .AsNoTracking()
- .Where(s => s.OrderID == order.ID)
- .Select(s => new Response.ShipmentInfo(
- s.ID,
- s.Status,
- s.Carrier,
- s.TrackingNumber,
- s.ShippedAt,
- s.DeliveredAt
- ))
- .FirstOrDefaultAsync(ct);
- // OrderItem 별 발급 쿠폰 코드 일괄 조회 (N+1 방지).
- // MemberInventory 에 OrderItemID 별 Quantity 만큼의 행이 있음. CouponCode FK 조인.
- var orderItemIds = order.Items.Select(i => i.ID).ToList();
- var inventoryRows = await db.MemberInventory.AsNoTracking().Where(m => orderItemIds.Contains(m.OrderItemID) && m.CouponCode != null).OrderBy(m => m.OrderItemID).ThenBy(m => m.ID).Select(m => new
- {
- m.OrderItemID,
- CouponCodeID = m.CouponCode!.ID,
- Code = m.CouponCode.Code,
- m.CouponCode.UsedAt,
- IsUsed = m.CouponCode.Status == CouponCodeStatus.Used
- }).ToListAsync(ct);
- // 환불 요청에 이미 포함된 CouponCodeID → OrderRefund.ID 매핑 (체크박스 disable 용)
- var orderRefundIds = order.Refunds.Select(r => r.ID).ToList();
- var refundedCouponMap = await db.OrderRefundItem.AsNoTracking()
- .Where(x => orderRefundIds.Contains(x.OrderRefundID))
- .Select(x => new { x.CouponCodeID, x.OrderRefundID })
- .ToDictionaryAsync(x => x.CouponCodeID, x => (int?)x.OrderRefundID, ct);
- var couponMap = inventoryRows.GroupBy(x => x.OrderItemID).ToDictionary(g => g.Key, g => (IReadOnlyList<Response.CouponDetail>)g.Select(x => new Response.CouponDetail(
- CouponCodeID: x.CouponCodeID,
- Code: x.Code,
- UsedAt: x.UsedAt,
- IsUsed: x.IsUsed,
- RefundRequestID: refundedCouponMap.GetValueOrDefault(x.CouponCodeID)
- )).ToList());
- var items = order.Items.Select(i => new Response.ItemRow(
- ID: i.ID,
- ProductID: i.ProductID,
- ProductName: i.Product?.Name ?? "-",
- TypeText: i.Product?.Type == ProductType.Digital ? "쿠폰" : "실물",
- Quantity: i.Quantity,
- UnitPrice: i.UnitPrice,
- LineTotal: i.UnitPrice * i.Quantity,
- IssuedCouponCodeID: i.IssuedCouponCodeID,
- IssuedCouponCode: i.IssuedCouponCode?.Code,
- CouponCodes: couponMap.GetValueOrDefault(i.ID, [])
- )).ToList();
- // 환불 이력의 OrderRefundItem (포함 쿠폰) 일괄 조회.
- var refundItemRows = await db.OrderRefundItem.AsNoTracking()
- .Include(x => x.CouponCode)
- .Where(x => orderRefundIds.Contains(x.OrderRefundID))
- .Select(x => new
- {
- x.OrderRefundID,
- Code = x.CouponCode!.Code,
- x.PostRefundAction
- })
- .ToListAsync(ct);
- var refundItemMap = refundItemRows.GroupBy(x => x.OrderRefundID).ToDictionary(g => g.Key, g => (IReadOnlyList<Response.RefundCoupon>)g.Select(x => new Response.RefundCoupon(x.Code, x.PostRefundAction)).ToList());
- var refunds = order.Refunds.OrderByDescending(r => r.RequestedAt).Select(r => new Response.RefundRow(
- ID: r.ID,
- Type: r.Type,
- Status: r.Status,
- Amount: r.Amount,
- Reason: r.Reason,
- AdminMemo: r.AdminMemo,
- RequestedAt: r.RequestedAt,
- ResolvedAt: r.ResolvedAt,
- Coupons: refundItemMap.GetValueOrDefault(r.ID, [])
- )).ToList();
- return Result.Success(new Response(
- ID: order.ID,
- OrderNumber: order.OrderNumber,
- MemberID: order.MemberID,
- MemberEmail: order.Member?.Email ?? "-",
- ChannelID: order.ChannelID,
- ChannelName: order.Channel?.Name,
- TotalAmount: order.TotalAmount,
- PlatformFeeAmount: order.PlatformFeeAmount,
- GameRevenueAmount: order.GameRevenueAmount,
- ChannelRewardAmount: order.ChannelRewardAmount,
- Status: order.Status,
- CancelReason: order.CancelReason,
- RefundReason: order.RefundReason,
- CreatedAt: order.CreatedAt,
- PaidAt: order.PaidAt,
- Items: items,
- Refunds: refunds,
- Shipment: shipment
- ));
- }
- }
|