Handler.cs 2.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657
  1. using Application.Abstractions.Data;
  2. using Application.Abstractions.Messaging;
  3. using Microsoft.EntityFrameworkCore;
  4. namespace Application.Features.Api.Attendance.GetCalendar;
  5. internal sealed class Handler(IAppDbContext db) : IQueryHandler<Query, Response>
  6. {
  7. public async Task<Response> Handle(Query request, CancellationToken ct)
  8. {
  9. // 로컬(서버 KST) 기준 월 범위를 UTC 로 환산
  10. var monthStartLocal = new DateTime(request.Year, request.Month, 1, 0, 0, 0, DateTimeKind.Local);
  11. var startUtc = monthStartLocal.ToUniversalTime();
  12. var endUtc = monthStartLocal.AddMonths(1).ToUniversalTime();
  13. // 해당 월 출석 레코드 조회
  14. var records = await db.Attendance.AsNoTracking()
  15. .Where(x => x.CreatedAt >= startUtc && x.CreatedAt < endUtc)
  16. .Select(x => new { x.MemberID, x.CreatedAt, x.ConsecutiveDays })
  17. .ToListAsync(ct);
  18. // EF Core 는 Unspecified 로 반환하므로 UTC 명시 후 로컬 변환
  19. var localRecords = records.Select(r => new {
  20. r.MemberID,
  21. CreatedAt = DateTime.SpecifyKind(r.CreatedAt, DateTimeKind.Utc).ToLocalTime(),
  22. r.ConsecutiveDays
  23. }).ToList();
  24. // KST 달력일(yyyy-MM-dd) 문자열 기준으로 집계 (UTC 직렬화에 의한 하루 밀림 방지)
  25. var dailyCounts = localRecords
  26. .GroupBy(x => x.CreatedAt.ToString("yyyy-MM-dd"))
  27. .ToDictionary(g => g.Key, g => g.Count());
  28. var myRecords = request.MemberID is > 0
  29. ? localRecords.Where(x => x.MemberID == request.MemberID.Value).OrderBy(x => x.CreatedAt).ToList()
  30. : [];
  31. var daysInMonth = DateTime.DaysInMonth(request.Year, request.Month);
  32. var days = new List<CalendarDay>(daysInMonth);
  33. for (var day = 1; day <= daysInMonth; day++)
  34. {
  35. var dateStr = $"{request.Year:D4}-{request.Month:D2}-{day:D2}";
  36. var myRecord = myRecords.FirstOrDefault(r => r.CreatedAt.ToString("yyyy-MM-dd") == dateStr);
  37. var totalCount = dailyCounts.GetValueOrDefault(dateStr);
  38. days.Add(new CalendarDay(
  39. dateStr,
  40. myRecord?.CreatedAt,
  41. myRecord?.ConsecutiveDays ?? 0,
  42. totalCount
  43. ));
  44. }
  45. return new Response(days);
  46. }
  47. }