SeibroDividendImport.cs 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. using Application.Abstractions.Data;
  2. using Domain.Entities.Stocks;
  3. using Microsoft.EntityFrameworkCore;
  4. namespace Infrastructure.StockData;
  5. /// <summary>
  6. /// SeibroDividendSyncService 의 DB 반영 로직 분리 (테스트 가능) — HTTP 없이 파서 출력(fixture)으로 검증한다
  7. /// (SeibroIssuerImport 패턴 미러링). 세 엔티티(DividendSchedule/Dividend/RightsBaseDate) upsert 를 담당한다.
  8. /// </summary>
  9. public static class SeibroDividendImport
  10. {
  11. /// <summary>
  12. /// getDivSchedulInfo 결과를 DividendSchedule 로 upsert — UQ (IssucoCustno, RgtStdDt, RgtRsnDetailSortCd).
  13. /// 한 번의 호출로 들어온 rows 를 한 트랜잭션으로 반영한다 (날짜 스윕: 그날 여러 회사·유형).
  14. /// </summary>
  15. public static async Task<(int Inserted, int Updated)> UpsertSchedulesAsync(IAppDbContext db, IReadOnlyList<SeibroDivSchedulParser.Row> rows, CancellationToken ct)
  16. {
  17. if (rows.Count == 0)
  18. {
  19. return (0, 0);
  20. }
  21. var custnos = rows.Select(c => c.IssucoCustno).Distinct().ToList();
  22. var existing = await db.DividendSchedule.Where(c => custnos.Contains(c.IssucoCustno)).ToListAsync(ct);
  23. var byKey = existing.ToDictionary(c => (c.IssucoCustno, c.RgtStdDt, c.RgtRsnDetailSortCd));
  24. var inserted = 0;
  25. var updated = 0;
  26. foreach (var row in rows)
  27. {
  28. var key = (row.IssucoCustno, row.RgtStdDt, row.RgtRsnDetailSortCd);
  29. if (byKey.TryGetValue(key, out var entity))
  30. {
  31. var was = entity.UpdatedAt;
  32. entity.Update(row.RepSecnNm, row.RgtRacd, row.AllocWhcd, row.SetaccTpcd, row.FixTpcd, row.RostCloseBeginDt, row.RostCloseExpryDt, row.XrgtDt, row.IsElectronicSecurity);
  33. if (entity.UpdatedAt != was)
  34. {
  35. updated++;
  36. }
  37. }
  38. else
  39. {
  40. 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);
  41. await db.DividendSchedule.AddAsync(created, ct);
  42. byKey[key] = created;
  43. inserted++;
  44. }
  45. }
  46. await db.SaveChangesAsync(ct);
  47. return (inserted, updated);
  48. }
  49. /// <summary>
  50. /// getDivInfo 결과를 Dividend 로 upsert — UQ (Isin, RgtStdDt). rows 는 한 회사(회사번호) 3년치 배당 이력.
  51. /// </summary>
  52. public static async Task<(int Inserted, int Updated)> UpsertDividendsAsync(IAppDbContext db, IReadOnlyList<SeibroDivInfoParser.Row> rows, CancellationToken ct)
  53. {
  54. if (rows.Count == 0)
  55. {
  56. return (0, 0);
  57. }
  58. var isins = rows.Select(c => c.Isin).Distinct().ToList();
  59. var existing = await db.Dividend.Where(c => isins.Contains(c.Isin)).ToListAsync(ct);
  60. var byKey = existing.ToDictionary(c => (c.Isin, c.RgtStdDt));
  61. var inserted = 0;
  62. var updated = 0;
  63. foreach (var row in rows)
  64. {
  65. var key = (row.Isin, row.RgtStdDt);
  66. if (byKey.TryGetValue(key, out var entity))
  67. {
  68. var was = entity.UpdatedAt;
  69. 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);
  70. if (entity.UpdatedAt != was)
  71. {
  72. updated++;
  73. }
  74. }
  75. else
  76. {
  77. 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);
  78. await db.Dividend.AddAsync(created, ct);
  79. byKey[key] = created;
  80. inserted++;
  81. }
  82. }
  83. await db.SaveChangesAsync(ct);
  84. return (inserted, updated);
  85. }
  86. /// <summary>
  87. /// getStddtInfo 결과를 RightsBaseDate 로 upsert — UQ (IssucoCustno, RgtStdDt, RgtRacd, RgtRsnDetailSortCd).
  88. /// rows 는 한 회사 3년치 권리기준일.
  89. /// </summary>
  90. public static async Task<(int Inserted, int Updated)> UpsertRightsBaseDatesAsync(IAppDbContext db, IReadOnlyList<SeibroStddtParser.Row> rows, CancellationToken ct)
  91. {
  92. if (rows.Count == 0)
  93. {
  94. return (0, 0);
  95. }
  96. var custnos = rows.Select(c => c.IssucoCustno).Distinct().ToList();
  97. var existing = await db.RightsBaseDate.Where(c => custnos.Contains(c.IssucoCustno)).ToListAsync(ct);
  98. var byKey = existing.ToDictionary(c => (c.IssucoCustno, c.RgtStdDt, c.RgtRacd, c.RgtRsnDetailSortCd));
  99. var inserted = 0;
  100. var updated = 0;
  101. foreach (var row in rows)
  102. {
  103. var key = (row.IssucoCustno, row.RgtStdDt, row.RgtRacd, row.RgtRsnDetailSortCd);
  104. if (byKey.TryGetValue(key, out var entity))
  105. {
  106. var was = entity.UpdatedAt;
  107. entity.Update(row.RepSecnNm, row.RgtRacdNm);
  108. if (entity.UpdatedAt != was)
  109. {
  110. updated++;
  111. }
  112. }
  113. else
  114. {
  115. var created = RightsBaseDate.Create(row.IssucoCustno, row.RepSecnNm, row.RgtStdDt, row.RgtRacd, row.RgtRacdNm, row.RgtRsnDetailSortCd);
  116. await db.RightsBaseDate.AddAsync(created, ct);
  117. byKey[key] = created;
  118. inserted++;
  119. }
  120. }
  121. await db.SaveChangesAsync(ct);
  122. return (inserted, updated);
  123. }
  124. }