using Application.Abstractions.Data; using Application.Abstractions.Messaging; using Microsoft.EntityFrameworkCore; using SharedKernel.Results; namespace Application.Features.Api.Stocks.GetBondDetail; internal sealed class Handler(IAppDbContext db) : IQueryHandler> { public async Task> Handle(Query request, CancellationToken ct) { var isin = request.Isin?.Trim(); if (string.IsNullOrEmpty(isin)) { return Result.Failure(Error.Problem("Bond.IsinRequired", "채권 종목코드(ISIN)가 필요합니다.")); } var master = await db.BondMaster.AsNoTracking().Where(c => c.Isin == isin).FirstOrDefaultAsync(ct); var interest = await db.BondInterestPayment.AsNoTracking().Where(c => c.Isin == isin).FirstOrDefaultAsync(ct); // KRX 일별매매(BondDailyTrade)는 (BondMarket, Code, TradeDate) 라 ISIN(Code) 최신 거래일 1행 var market = await db.BondDailyTrade.AsNoTracking() .Where(c => c.Code == isin) .OrderByDescending(c => c.TradeDate) .FirstOrDefaultAsync(ct); if (master is null && interest is null && market is null) { return Result.Failure(Error.NotFound("Bond.NotFound", "해당 채권 정보를 찾을 수 없습니다.")); } return new Response { Isin = isin, Master = master is null ? null : new Response.MasterInfo { Name = master.KorSecnNm, IssucoCustno = master.IssucoCustno, SecnKacd = master.SecnKacd, IssueDate = master.IssuDt, MaturityDate = master.XpirDt, Currency = master.IssuCurCd, FirstIssueAmount = master.FirstIssuAmt, IssueRemaining = master.IssuRema, CouponRate = master.CouponRate, OptionTpcd = master.OptionTpcd, ForcedEarlyRedemption = master.ForcErlyRedYn, RateChangeTpcd = master.MrChgTpcd, GuaranteeTpcd = master.GrtyTpcd, RankTpcd = master.RankTpcd, IntPayWayTpcd = master.IntPayWayTpcd, ParticularBondKindTpcd = master.ParticulBondKindTpcd, KisGrade = master.KisValatGrdCd, NiceGrade = master.NiceValatGrdCd, SciGrade = master.SciValatGrdCd, KrGrade = master.KrValatGrdCd, IsElectronicSecurity = master.EltscYn }, Interest = interest is null ? null : new Response.InterestInfo { CouponRate = interest.CouponRate, IntPayWayTpcd = interest.IntPayWayTpcd, CycleTerms = interest.IntPayCycleTerms, CycleTpcd = interest.IntPayCycleTpcd, PayTimingTpcd = interest.IntPayTimsTpcd, BeforeCouponDate = interest.BeforeDate, AfterCouponDate = interest.AfterDate }, Market = market is null ? null : new Response.MarketInfo { TradeDate = market.TradeDate, Close = market.Close, YieldToMaturity = market.YieldToMaturity, Volume = market.Volume } }; } }