using Application.Abstractions.Data; using Domain.Entities.Stocks; using Microsoft.EntityFrameworkCore; namespace Infrastructure.StockData; /// /// SeibroDividendSyncService 의 DB 반영 로직 분리 (테스트 가능) — HTTP 없이 파서 출력(fixture)으로 검증한다 /// (SeibroIssuerImport 패턴 미러링). 세 엔티티(DividendSchedule/Dividend/RightsBaseDate) upsert 를 담당한다. /// public static class SeibroDividendImport { /// /// getDivSchedulInfo 결과를 DividendSchedule 로 upsert — UQ (IssucoCustno, RgtStdDt, RgtRsnDetailSortCd). /// 한 번의 호출로 들어온 rows 를 한 트랜잭션으로 반영한다 (날짜 스윕: 그날 여러 회사·유형). /// public static async Task<(int Inserted, int Updated)> UpsertSchedulesAsync(IAppDbContext db, IReadOnlyList rows, CancellationToken ct) { if (rows.Count == 0) { return (0, 0); } var custnos = rows.Select(c => c.IssucoCustno).Distinct().ToList(); var existing = await db.DividendSchedule.Where(c => custnos.Contains(c.IssucoCustno)).ToListAsync(ct); var byKey = existing.ToDictionary(c => (c.IssucoCustno, c.RgtStdDt, c.RgtRsnDetailSortCd)); var inserted = 0; var updated = 0; foreach (var row in rows) { var key = (row.IssucoCustno, row.RgtStdDt, row.RgtRsnDetailSortCd); if (byKey.TryGetValue(key, out var entity)) { var was = entity.UpdatedAt; entity.Update(row.RepSecnNm, row.RgtRacd, row.AllocWhcd, row.SetaccTpcd, row.FixTpcd, row.RostCloseBeginDt, row.RostCloseExpryDt, row.XrgtDt, row.IsElectronicSecurity); if (entity.UpdatedAt != was) { updated++; } } else { var created = DividendSchedule.Create(row.IssucoCustno, row.RepSecnNm, row.RgtRacd, row.RgtRsnDetailSortCd, row.RgtStdDt, row.AllocWhcd, row.SetaccTpcd, row.FixTpcd, row.RostCloseBeginDt, row.RostCloseExpryDt, row.XrgtDt, row.IsElectronicSecurity); await db.DividendSchedule.AddAsync(created, ct); byKey[key] = created; inserted++; } } await db.SaveChangesAsync(ct); return (inserted, updated); } /// /// getDivInfo 결과를 Dividend 로 upsert — UQ (Isin, RgtStdDt). rows 는 한 회사(회사번호) 3년치 배당 이력. /// public static async Task<(int Inserted, int Updated)> UpsertDividendsAsync(IAppDbContext db, IReadOnlyList rows, CancellationToken ct) { if (rows.Count == 0) { return (0, 0); } var isins = rows.Select(c => c.Isin).Distinct().ToList(); var existing = await db.Dividend.Where(c => isins.Contains(c.Isin)).ToListAsync(ct); var byKey = existing.ToDictionary(c => (c.Isin, c.RgtStdDt)); var inserted = 0; var updated = 0; foreach (var row in rows) { var key = (row.Isin, row.RgtStdDt); if (byKey.TryGetValue(key, out var entity)) { var was = entity.UpdatedAt; entity.Update(row.KorSecnNm, row.SecnKacdNm, row.Pval, row.StkAllocRatio, row.CashAllocRatio, row.CashAllocAmt, row.MartpDivRate, row.Th1PayTermBeginDt, row.DeliDt, row.MajshrEtcDiffAllocYn, row.TstkNoagnYn, row.CashDiffDiviAmt, row.CashDiffDiviRate, row.StkDiffDiviRate, row.MartpDiffDiviRate); if (entity.UpdatedAt != was) { updated++; } } else { var created = Dividend.Create(row.Isin, row.KorSecnNm, row.SecnKacdNm, row.RgtStdDt, row.Pval, row.StkAllocRatio, row.CashAllocRatio, row.CashAllocAmt, row.MartpDivRate, row.Th1PayTermBeginDt, row.DeliDt, row.MajshrEtcDiffAllocYn, row.TstkNoagnYn, row.CashDiffDiviAmt, row.CashDiffDiviRate, row.StkDiffDiviRate, row.MartpDiffDiviRate); await db.Dividend.AddAsync(created, ct); byKey[key] = created; inserted++; } } await db.SaveChangesAsync(ct); return (inserted, updated); } /// /// getStddtInfo 결과를 RightsBaseDate 로 upsert — UQ (IssucoCustno, RgtStdDt, RgtRacd, RgtRsnDetailSortCd). /// rows 는 한 회사 3년치 권리기준일. /// public static async Task<(int Inserted, int Updated)> UpsertRightsBaseDatesAsync(IAppDbContext db, IReadOnlyList rows, CancellationToken ct) { if (rows.Count == 0) { return (0, 0); } var custnos = rows.Select(c => c.IssucoCustno).Distinct().ToList(); var existing = await db.RightsBaseDate.Where(c => custnos.Contains(c.IssucoCustno)).ToListAsync(ct); var byKey = existing.ToDictionary(c => (c.IssucoCustno, c.RgtStdDt, c.RgtRacd, c.RgtRsnDetailSortCd)); var inserted = 0; var updated = 0; foreach (var row in rows) { var key = (row.IssucoCustno, row.RgtStdDt, row.RgtRacd, row.RgtRsnDetailSortCd); if (byKey.TryGetValue(key, out var entity)) { var was = entity.UpdatedAt; entity.Update(row.RepSecnNm, row.RgtRacdNm); if (entity.UpdatedAt != was) { updated++; } } else { var created = RightsBaseDate.Create(row.IssucoCustno, row.RepSecnNm, row.RgtStdDt, row.RgtRacd, row.RgtRacdNm, row.RgtRsnDetailSortCd); await db.RightsBaseDate.AddAsync(created, ct); byKey[key] = created; inserted++; } } await db.SaveChangesAsync(ct); return (inserted, updated); } }