| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657 |
- using Application.Abstractions.Data;
- using Application.Abstractions.Messaging;
- using Microsoft.EntityFrameworkCore;
- namespace Application.Features.Api.Attendance.GetCalendar;
- internal sealed class Handler(IAppDbContext db) : IQueryHandler<Query, Response>
- {
- public async Task<Response> Handle(Query request, CancellationToken ct)
- {
- // 로컬(서버 KST) 기준 월 범위를 UTC 로 환산
- var monthStartLocal = new DateTime(request.Year, request.Month, 1, 0, 0, 0, DateTimeKind.Local);
- var startUtc = monthStartLocal.ToUniversalTime();
- var endUtc = monthStartLocal.AddMonths(1).ToUniversalTime();
- // 해당 월 출석 레코드 조회
- var records = await db.Attendance.AsNoTracking()
- .Where(x => x.CreatedAt >= startUtc && x.CreatedAt < endUtc)
- .Select(x => new { x.MemberID, x.CreatedAt, x.ConsecutiveDays })
- .ToListAsync(ct);
- // EF Core 는 Unspecified 로 반환하므로 UTC 명시 후 로컬 변환
- var localRecords = records.Select(r => new {
- r.MemberID,
- CreatedAt = DateTime.SpecifyKind(r.CreatedAt, DateTimeKind.Utc).ToLocalTime(),
- r.ConsecutiveDays
- }).ToList();
- // KST 달력일(yyyy-MM-dd) 문자열 기준으로 집계 (UTC 직렬화에 의한 하루 밀림 방지)
- var dailyCounts = localRecords
- .GroupBy(x => x.CreatedAt.ToString("yyyy-MM-dd"))
- .ToDictionary(g => g.Key, g => g.Count());
- var myRecords = request.MemberID is > 0
- ? localRecords.Where(x => x.MemberID == request.MemberID.Value).OrderBy(x => x.CreatedAt).ToList()
- : [];
- var daysInMonth = DateTime.DaysInMonth(request.Year, request.Month);
- var days = new List<CalendarDay>(daysInMonth);
- for (var day = 1; day <= daysInMonth; day++)
- {
- var dateStr = $"{request.Year:D4}-{request.Month:D2}-{day:D2}";
- var myRecord = myRecords.FirstOrDefault(r => r.CreatedAt.ToString("yyyy-MM-dd") == dateStr);
- var totalCount = dailyCounts.GetValueOrDefault(dateStr);
- days.Add(new CalendarDay(
- dateStr,
- myRecord?.CreatedAt,
- myRecord?.ConsecutiveDays ?? 0,
- totalCount
- ));
- }
- return new Response(days);
- }
- }
|