using System.ComponentModel.DataAnnotations; namespace Domain.Entities.Stocks; /// /// SEIBro 종목별 외화증권 보관현황 (getSecnFrsecCusInfo) — KSD 를 통한 특정 외화종목(ISIN)의 보유금액(USD)·보유수량 (Wave 6). 자연키 UNIQUE (StdDt, Isin). /// 응답 필드는 NationCd·SecnTpcd·FrsecTotHoldAmt·FrsecTotHoldQty 4종 — 배치가 STD_DT·ISIN 을 요청값으로 스탬핑한다. /// ForeignIsins 설정이 기본 빈 리스트라 종목별 수집은 사실상 대기 상태(seed 지정 시 활성). SecnTpcd 는 코드 문자열 그대로 저장. /// public class ForeignCustodySecurity { [Key] public int ID { get; private set; } /// 기준일자 (STD_DT, 요청값 스탬핑) public DateOnly StdDt { get; private set; } /// 종목코드 (ISIN, 요청값 스탬핑) public string Isin { get; private set; } = default!; /// 국가코드 (NATION_CD) public string? NationCd { get; private set; } /// 종목구분코드 (SECN_TPCD) — 71외화주식~78외화수익증권 public string? SecnTpcd { get; private set; } /// 외화증권보유금액 (FRSEC_TOT_HOLD_AMT) — USD public decimal? FrsecTotHoldAmt { get; private set; } /// 외화증권총보유수량 (FRSEC_TOT_HOLD_QTY) public decimal? FrsecTotHoldQty { get; private set; } public DateTime UpdatedAt { get; private set; } = DateTime.UtcNow; private ForeignCustodySecurity() { } public static ForeignCustodySecurity Create(DateOnly stdDt, string isin, string? nationCd, string? secnTpcd, decimal? frsecTotHoldAmt, decimal? frsecTotHoldQty) { if (string.IsNullOrWhiteSpace(isin)) { throw new ArgumentException("isin required", nameof(isin)); } return new ForeignCustodySecurity { StdDt = stdDt, Isin = isin.Trim(), NationCd = Trimmed(nationCd), SecnTpcd = Trimmed(secnTpcd), FrsecTotHoldAmt = frsecTotHoldAmt, FrsecTotHoldQty = frsecTotHoldQty }; } /// 재수집 반영 — 변경 필드가 있을 때만 UpdatedAt 갱신 (자연키 StdDt/Isin 은 불변) public void Update(string? nationCd, string? secnTpcd, decimal? frsecTotHoldAmt, decimal? frsecTotHoldQty) { var changed = false; changed |= Set(Trimmed(nationCd), NationCd, v => NationCd = v); changed |= Set(Trimmed(secnTpcd), SecnTpcd, v => SecnTpcd = v); changed |= Set(frsecTotHoldAmt, FrsecTotHoldAmt, v => FrsecTotHoldAmt = v); changed |= Set(frsecTotHoldQty, FrsecTotHoldQty, v => FrsecTotHoldQty = v); if (changed) { UpdatedAt = DateTime.UtcNow; } } private static string? Trimmed(string? value) { return string.IsNullOrWhiteSpace(value) ? null : value.Trim(); } private static bool Set(T incoming, T current, Action assign) { if (EqualityComparer.Default.Equals(incoming, current)) { return false; } assign(incoming); return true; } }