Handler.cs 3.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. using Application.Abstractions.Data;
  2. using Application.Abstractions.Messaging;
  3. using Microsoft.EntityFrameworkCore;
  4. using SharedKernel.Results;
  5. namespace Application.Features.Api.Stocks.GetStockDividends;
  6. internal sealed class Handler(IAppDbContext db) : IQueryHandler<Query, Result<Response>>
  7. {
  8. private const ushort MaxPerPage = 200;
  9. public async Task<Result<Response>> Handle(Query request, CancellationToken ct)
  10. {
  11. var input = request.CodeOrIsin?.Trim();
  12. if (string.IsNullOrEmpty(input))
  13. {
  14. return Result.Failure<Response>(Error.Problem("Stock.CodeRequired", "종목 코드가 필요합니다."));
  15. }
  16. // 단축코드(6자리) 우선 → Stock 조인으로 ISIN·IssucoCustno 확보. 미매칭이면 입력을 ISIN 으로 간주.
  17. var stock = await db.Stock.AsNoTracking()
  18. .Where(c => c.Code == input || c.ISIN == input)
  19. .Select(c => new { c.ISIN, c.IssucoCustno })
  20. .FirstOrDefaultAsync(ct);
  21. var isin = stock?.ISIN ?? input;
  22. var issucoCustno = stock?.IssucoCustno;
  23. var page = request.Page < 1 ? 1 : request.Page;
  24. var perPage = request.PerPage is 0 or > MaxPerPage ? (ushort)30 : request.PerPage;
  25. var query = db.Dividend.AsNoTracking().Where(c => c.Isin == isin);
  26. var total = await query.CountAsync(ct);
  27. var dividends = await query
  28. .OrderByDescending(c => c.RgtStdDt)
  29. .Skip((page - 1) * perPage)
  30. .Take(perPage)
  31. .Select(c => new
  32. {
  33. c.RgtStdDt,
  34. c.KorSecnNm,
  35. c.SecnKacdNm,
  36. c.Pval,
  37. c.CashAllocAmt,
  38. c.StkAllocRatio,
  39. c.MartpDivRate,
  40. c.Th1PayTermBeginDt
  41. })
  42. .ToListAsync(ct);
  43. // 권리락일·확정구분 결합 — DividendSchedule (IssucoCustno + 페이지 내 RgtStdDt). 세부유형별 여러 행이면 권리락일 있는 행 우선.
  44. var scheduleByDate = new Dictionary<DateOnly, (DateOnly? XrgtDt, string? FixType)>();
  45. if (issucoCustno is not null && dividends.Count > 0)
  46. {
  47. var dates = dividends.Select(c => c.RgtStdDt).Distinct().ToList();
  48. var schedules = await db.DividendSchedule.AsNoTracking()
  49. .Where(c => c.IssucoCustno == issucoCustno.Value && dates.Contains(c.RgtStdDt))
  50. .Select(c => new { c.RgtStdDt, c.XrgtDt, c.FixTpcd })
  51. .ToListAsync(ct);
  52. foreach (var s in schedules)
  53. {
  54. if (!scheduleByDate.TryGetValue(s.RgtStdDt, out var existing) || (existing.XrgtDt is null && s.XrgtDt is not null))
  55. {
  56. scheduleByDate[s.RgtStdDt] = (s.XrgtDt, s.FixTpcd);
  57. }
  58. }
  59. }
  60. var list = dividends.Select(c =>
  61. {
  62. scheduleByDate.TryGetValue(c.RgtStdDt, out var sched);
  63. return new Response.Row
  64. {
  65. RgtStdDt = c.RgtStdDt,
  66. Name = c.KorSecnNm,
  67. SecurityKind = c.SecnKacdNm,
  68. FaceValue = c.Pval,
  69. CashPerShare = c.CashAllocAmt,
  70. StockAllocRatio = c.StkAllocRatio,
  71. MarketDividendRate = c.MartpDivRate,
  72. CashPayDate = c.Th1PayTermBeginDt,
  73. XrgtDt = sched.XrgtDt,
  74. FixType = sched.FixType
  75. };
  76. }).ToList();
  77. return new Response
  78. {
  79. Isin = isin,
  80. Total = total,
  81. List = list
  82. };
  83. }
  84. }