using Application.Abstractions.Data; using Application.Abstractions.Messaging; using Microsoft.EntityFrameworkCore; using SharedKernel.Results; namespace Application.Features.Api.Stocks.GetStockDividends; internal sealed class Handler(IAppDbContext db) : IQueryHandler> { private const ushort MaxPerPage = 200; public async Task> Handle(Query request, CancellationToken ct) { var input = request.CodeOrIsin?.Trim(); if (string.IsNullOrEmpty(input)) { return Result.Failure(Error.Problem("Stock.CodeRequired", "종목 코드가 필요합니다.")); } // 단축코드(6자리) 우선 → Stock 조인으로 ISIN·IssucoCustno 확보. 미매칭이면 입력을 ISIN 으로 간주. var stock = await db.Stock.AsNoTracking() .Where(c => c.Code == input || c.ISIN == input) .Select(c => new { c.ISIN, c.IssucoCustno }) .FirstOrDefaultAsync(ct); var isin = stock?.ISIN ?? input; var issucoCustno = stock?.IssucoCustno; var page = request.Page < 1 ? 1 : request.Page; var perPage = request.PerPage is 0 or > MaxPerPage ? (ushort)30 : request.PerPage; var query = db.Dividend.AsNoTracking().Where(c => c.Isin == isin); var total = await query.CountAsync(ct); var dividends = await query .OrderByDescending(c => c.RgtStdDt) .Skip((page - 1) * perPage) .Take(perPage) .Select(c => new { c.RgtStdDt, c.KorSecnNm, c.SecnKacdNm, c.Pval, c.CashAllocAmt, c.StkAllocRatio, c.MartpDivRate, c.Th1PayTermBeginDt }) .ToListAsync(ct); // 권리락일·확정구분 결합 — DividendSchedule (IssucoCustno + 페이지 내 RgtStdDt). 세부유형별 여러 행이면 권리락일 있는 행 우선. var scheduleByDate = new Dictionary(); if (issucoCustno is not null && dividends.Count > 0) { var dates = dividends.Select(c => c.RgtStdDt).Distinct().ToList(); var schedules = await db.DividendSchedule.AsNoTracking() .Where(c => c.IssucoCustno == issucoCustno.Value && dates.Contains(c.RgtStdDt)) .Select(c => new { c.RgtStdDt, c.XrgtDt, c.FixTpcd }) .ToListAsync(ct); foreach (var s in schedules) { if (!scheduleByDate.TryGetValue(s.RgtStdDt, out var existing) || (existing.XrgtDt is null && s.XrgtDt is not null)) { scheduleByDate[s.RgtStdDt] = (s.XrgtDt, s.FixTpcd); } } } var list = dividends.Select(c => { scheduleByDate.TryGetValue(c.RgtStdDt, out var sched); return new Response.Row { RgtStdDt = c.RgtStdDt, Name = c.KorSecnNm, SecurityKind = c.SecnKacdNm, FaceValue = c.Pval, CashPerShare = c.CashAllocAmt, StockAllocRatio = c.StkAllocRatio, MarketDividendRate = c.MartpDivRate, CashPayDate = c.Th1PayTermBeginDt, XrgtDt = sched.XrgtDt, FixType = sched.FixType }; }).ToList(); return new Response { Isin = isin, Total = total, List = list }; } }