using Application.Abstractions.Data;
using Domain.Entities.Stocks;
using Microsoft.EntityFrameworkCore;
namespace Infrastructure.StockData;
///
/// SeibroForeignSyncService 의 DB 반영 로직 분리 (테스트 가능) — HTTP 없이 파서 출력(fixture)으로 검증한다 (SeibroDerivImport 패턴 미러링).
/// 네 엔티티(국가별 보관·국가별 결제·종목별 보관·종목별 결제) upsert 담당. 모든 응답에 날짜/국가/ISIN 이 없어(요청 파라미터로만 받음)
/// 배치가 STD_DT|SETL_DT|PROC_DT·NATION_CD·ISIN 요청값을 스탬핑한다. 자연키:
/// • ForeignCustodyNation UQ (StdDt, NationCd, SecnTpcd) — 국가×종목구분 다수 행/콜
/// • ForeignSettlementNation UQ (SetlDt, NationCd, IntlBizCacd, SecnTpcd) — 국가×매매×종목구분 다수 행/콜
/// • ForeignCustodySecurity UQ (StdDt, Isin, NationCd, SecnTpcd) — 종목당 국가별 복수 행 전부 반영 (리뷰 결함 #3)
/// • ForeignSettlementSecurity UQ (ProcDt, Isin, IntlBizCacd) — 종목×매매(1110매수/1120매도) 다수 행/콜. 종목당 단일 국가라 NationCd 불요(샘플 확인)
///
public static class SeibroForeignImport
{
/// getNationFrsecCusInfo 결과를 ForeignCustodyNation 으로 upsert — UQ (StdDt, NationCd, SecnTpcd). STD_DT·NATION_CD 요청값 스탬핑. 종목구분 다수 행/콜.
public static async Task<(int Inserted, int Updated)> UpsertCustodyNationAsync(IAppDbContext db, DateOnly stdDt, string nationCd, IReadOnlyList rows, CancellationToken ct)
{
if (rows.Count == 0)
{
return (0, 0);
}
var existing = await db.ForeignCustodyNation.Where(c => c.StdDt == stdDt && c.NationCd == nationCd).ToListAsync(ct);
var byKey = existing.ToDictionary(c => c.SecnTpcd);
var seen = new HashSet();
var inserted = 0;
var updated = 0;
foreach (var row in rows)
{
if (!seen.Add(row.SecnTpcd))
{
continue;
}
if (byKey.TryGetValue(row.SecnTpcd, out var entity))
{
var was = entity.UpdatedAt;
entity.Update(row.FrsecCusAmt);
if (entity.UpdatedAt != was)
{
updated++;
}
}
else
{
var created = ForeignCustodyNation.Create(stdDt, nationCd, row.SecnTpcd, row.FrsecCusAmt);
await db.ForeignCustodyNation.AddAsync(created, ct);
byKey[row.SecnTpcd] = created;
inserted++;
}
}
await db.SaveChangesAsync(ct);
return (inserted, updated);
}
/// getNationFrsecSetlInfo 결과를 ForeignSettlementNation 으로 upsert — UQ (SetlDt, NationCd, IntlBizCacd, SecnTpcd). SETL_DT·NATION_CD 요청값 스탬핑.
public static async Task<(int Inserted, int Updated)> UpsertSettlementNationAsync(IAppDbContext db, DateOnly setlDt, string nationCd, IReadOnlyList rows, CancellationToken ct)
{
if (rows.Count == 0)
{
return (0, 0);
}
var existing = await db.ForeignSettlementNation.Where(c => c.SetlDt == setlDt && c.NationCd == nationCd).ToListAsync(ct);
var byKey = existing.ToDictionary(c => (c.IntlBizCacd, c.SecnTpcd));
var seen = new HashSet<(string, string)>();
var inserted = 0;
var updated = 0;
foreach (var row in rows)
{
var key = (row.IntlBizCacd, row.SecnTpcd);
if (!seen.Add(key))
{
continue;
}
if (byKey.TryGetValue(key, out var entity))
{
var was = entity.UpdatedAt;
entity.Update(row.SetlCount, row.FrsecSetlAmt);
if (entity.UpdatedAt != was)
{
updated++;
}
}
else
{
var created = ForeignSettlementNation.Create(setlDt, nationCd, row.IntlBizCacd, row.SecnTpcd, row.SetlCount, row.FrsecSetlAmt);
await db.ForeignSettlementNation.AddAsync(created, ct);
byKey[key] = created;
inserted++;
}
}
await db.SaveChangesAsync(ct);
return (inserted, updated);
}
///
/// getSecnFrsecCusInfo 결과를 ForeignCustodySecurity 로 upsert — UQ (StdDt, Isin, NationCd, SecnTpcd). STD_DT·ISIN 요청값 스탬핑.
/// 종목당 국가별 복수 행(실샘플 DE+US)을 전 행 iterate upsert 한다 (리뷰 결함 #3 — 기존 첫 행만 반영 → 국가별 보관 무음 병합 해소). 빈결과면 no-op.
///
public static async Task UpsertCustodySecurityAsync(IAppDbContext db, DateOnly stdDt, string isin, IReadOnlyList rows, CancellationToken ct)
{
if (rows.Count == 0)
{
return false;
}
var existing = await db.ForeignCustodySecurity.Where(c => c.StdDt == stdDt && c.Isin == isin).ToListAsync(ct);
var byKey = existing.ToDictionary(c => (c.NationCd, c.SecnTpcd));
var seen = new HashSet<(string, string)>();
foreach (var row in rows)
{
var nationCd = string.IsNullOrWhiteSpace(row.NationCd) ? string.Empty : row.NationCd.Trim();
var secnTpcd = string.IsNullOrWhiteSpace(row.SecnTpcd) ? string.Empty : row.SecnTpcd.Trim();
var key = (nationCd, secnTpcd);
if (!seen.Add(key))
{
continue;
}
if (byKey.TryGetValue(key, out var entity))
{
entity.Update(row.FrsecTotHoldAmt, row.FrsecTotHoldQty);
}
else
{
var created = ForeignCustodySecurity.Create(stdDt, isin, row.NationCd, row.SecnTpcd, row.FrsecTotHoldAmt, row.FrsecTotHoldQty);
await db.ForeignCustodySecurity.AddAsync(created, ct);
byKey[key] = created;
}
}
await db.SaveChangesAsync(ct);
return true;
}
/// getSecnFrsecSetlInfo 결과를 ForeignSettlementSecurity 로 upsert — UQ (ProcDt, Isin, IntlBizCacd). PROC_DT·ISIN 요청값 스탬핑. 종목×매매 다수 행/콜.
public static async Task<(int Inserted, int Updated)> UpsertSettlementSecurityAsync(IAppDbContext db, DateOnly procDt, string isin, IReadOnlyList rows, CancellationToken ct)
{
if (rows.Count == 0)
{
return (0, 0);
}
var existing = await db.ForeignSettlementSecurity.Where(c => c.ProcDt == procDt && c.Isin == isin).ToListAsync(ct);
var byKey = existing.ToDictionary(c => c.IntlBizCacd);
var seen = new HashSet();
var inserted = 0;
var updated = 0;
foreach (var row in rows)
{
if (!seen.Add(row.IntlBizCacd))
{
continue;
}
if (byKey.TryGetValue(row.IntlBizCacd, out var entity))
{
var was = entity.UpdatedAt;
entity.Update(row.NationCd, row.SecnTpcd, row.SetlAskQty, row.SetlAmt);
if (entity.UpdatedAt != was)
{
updated++;
}
}
else
{
var created = ForeignSettlementSecurity.Create(procDt, isin, row.IntlBizCacd, row.NationCd, row.SecnTpcd, row.SetlAskQty, row.SetlAmt);
await db.ForeignSettlementSecurity.AddAsync(created, ct);
byKey[row.IntlBizCacd] = created;
inserted++;
}
}
await db.SaveChangesAsync(ct);
return (inserted, updated);
}
}