using Application.Abstractions.Data; using Application.Abstractions.Messaging; using Microsoft.EntityFrameworkCore; using SharedKernel.Results; namespace Application.Features.Api.Stocks.GetMeetingCalendar; internal sealed class Handler(IAppDbContext db) : IQueryHandler> { private const ushort MaxPerPage = 200; public async Task> Handle(Query request, CancellationToken ct) { var page = request.Page < 1 ? 1 : request.Page; var perPage = request.PerPage is 0 or > MaxPerPage ? (ushort)30 : request.PerPage; var query = db.GeneralMeeting.AsNoTracking(); if (request.From.HasValue) { query = query.Where(c => c.GmetDt >= request.From.Value); } if (request.To.HasValue) { query = query.Where(c => c.GmetDt <= request.To.Value); } var total = await query.CountAsync(ct); var meetings = await query .OrderByDescending(c => c.GmetDt) .ThenBy(c => c.IssucoCustno) .ThenBy(c => c.GmetKacd) .Skip((page - 1) * perPage) .Take(perPage) .Select(c => new { c.IssucoCustno, c.RgtStdDt, c.GmetDt, c.GmetTtm, c.GmetKacd, c.GmetPlace, c.EvoteYn, c.DferYn }) .ToListAsync(ct); if (meetings.Count == 0) { return new Response { Total = total, List = [] }; } var custnos = meetings.Select(c => c.IssucoCustno).Distinct().ToList(); var stdDts = meetings.Select(c => c.RgtStdDt).Distinct().ToList(); // 종목명·단축코드 (IssucoCustno 조인 — 한 회사에 여러 종목이면 대표 1개) var stockRows = await db.Stock.AsNoTracking() .Where(c => c.IssucoCustno != null && custnos.Contains(c.IssucoCustno.Value)) .Select(c => new { Custno = c.IssucoCustno!.Value, c.Name, c.Code }) .ToListAsync(ct); var stockByCustno = stockRows.GroupBy(c => c.Custno).ToDictionary(g => g.Key, g => g.First()); // 안건수 집계 (회사, 기준일) 단위 var agendaCounts = await db.MeetingAgenda.AsNoTracking() .Where(c => custnos.Contains(c.IssucoCustno) && stdDts.Contains(c.RgtStdDt)) .GroupBy(c => new { c.IssucoCustno, c.RgtStdDt }) .Select(g => new { g.Key.IssucoCustno, g.Key.RgtStdDt, Count = g.Count() }) .ToListAsync(ct); var agendaLookup = agendaCounts.ToDictionary(c => (c.IssucoCustno, c.RgtStdDt), c => c.Count); var list = meetings.Select(c => { stockByCustno.TryGetValue(c.IssucoCustno, out var stock); agendaLookup.TryGetValue((c.IssucoCustno, c.RgtStdDt), out var agendaCount); return new Response.Row { IssucoCustno = c.IssucoCustno, Name = stock?.Name, Code = stock?.Code, RgtStdDt = c.RgtStdDt, GmetDt = c.GmetDt, GmetTtm = c.GmetTtm, MeetingKind = c.GmetKacd, Place = c.GmetPlace, Evote = c.EvoteYn, Deferred = c.DferYn, AgendaCount = agendaCount }; }).ToList(); return new Response { Total = total, List = list }; } }