using System.ComponentModel.DataAnnotations; namespace Domain.Entities.Stocks; /// /// SEIBro 주식관련사채 행사대상 주식정보 (getXrcStkStatInfo) — CB/BW/EB 행사대상·행사가·행사비율 (Wave 3). /// 자연키 UNIQUE (BondIsin, XrcStkIsin) — 한 채권·행사주식 조합당 1행. /// rolling per-채권ISIN — 발견 소스는 ConvertibleExercise(getXrcStkOptionXrcInfo) 가 반환한 DISTINCT BOND_ISIN. /// ⚠️ getXrcStkStatInfo 실샘플이 result="0"(0행) 이라 필드 매핑은 주식.md 스펙 기준 — 운영 첫 수집 시 로그로 확인 필요. /// public class ConvertibleBondTarget { [Key] public int ID { get; private set; } /// 채권 종목번호 (BOND_ISIN) — EB/BW/CB public string BondIsin { get; private set; } = default!; /// 채권 종목명 (BOND_SECN_NM) public string? BondSecnNm { get; private set; } /// 채권종류코드 (BOND_KIND_NM) — EB/CB/분리형BW/비분리형BW public string? BondKindNm { get; private set; } /// 행사주식종목번호 (XRC_STK_ISIN) public string XrcStkIsin { get; private set; } = default!; /// 주식 종목명 (STK_SECN_NM) public string? StkSecnNm { get; private set; } /// 신주인수권증권종목번호 (WRTB_ISIN) public string? WrtbIsin { get; private set; } /// 신주인수권증권명 (WRTB_SECN_NM) public string? WrtbSecnNm { get; private set; } /// 행사비율 (XRC_RATIO) public decimal? XrcRatio { get; private set; } /// 행사가격 (XRC_PRICE) public decimal? XrcPrice { get; private set; } /// 결산기말제한일수 (SETACC_ENDTERM_LIMIT_DAYS) public int? SetaccEndtermLimitDays { get; private set; } /// 신주배정일전제한일수 (NEWSTK_ALOC_DD_BF_LIMIT_DAYS) public int? NewstkAllocDdBfLimitDays { get; private set; } /// 청구제한사유내용 (DMAN_LIMIT_RSN_CONTENT) public string? DmanLimitRsnContent { get; private set; } public DateTime UpdatedAt { get; private set; } = DateTime.UtcNow; private ConvertibleBondTarget() { } public static ConvertibleBondTarget Create( string bondIsin, string? bondSecnNm, string? bondKindNm, string xrcStkIsin, string? stkSecnNm, string? wrtbIsin, string? wrtbSecnNm, decimal? xrcRatio, decimal? xrcPrice, int? setaccEndtermLimitDays, int? newstkAllocDdBfLimitDays, string? dmanLimitRsnContent) { if (string.IsNullOrWhiteSpace(bondIsin)) { throw new ArgumentException("bondIsin required", nameof(bondIsin)); } if (string.IsNullOrWhiteSpace(xrcStkIsin)) { throw new ArgumentException("xrcStkIsin required", nameof(xrcStkIsin)); } return new ConvertibleBondTarget { BondIsin = bondIsin.Trim(), BondSecnNm = Trimmed(bondSecnNm), BondKindNm = Trimmed(bondKindNm), XrcStkIsin = xrcStkIsin.Trim(), StkSecnNm = Trimmed(stkSecnNm), WrtbIsin = Trimmed(wrtbIsin), WrtbSecnNm = Trimmed(wrtbSecnNm), XrcRatio = xrcRatio, XrcPrice = xrcPrice, SetaccEndtermLimitDays = setaccEndtermLimitDays, NewstkAllocDdBfLimitDays = newstkAllocDdBfLimitDays, DmanLimitRsnContent = Trimmed(dmanLimitRsnContent) }; } /// 재수집 반영 — 변경 필드가 있을 때만 UpdatedAt 갱신 (자연키는 불변) public void Update( string? bondSecnNm, string? bondKindNm, string? stkSecnNm, string? wrtbIsin, string? wrtbSecnNm, decimal? xrcRatio, decimal? xrcPrice, int? setaccEndtermLimitDays, int? newstkAllocDdBfLimitDays, string? dmanLimitRsnContent) { var changed = false; changed |= Set(Trimmed(bondSecnNm), BondSecnNm, v => BondSecnNm = v); changed |= Set(Trimmed(bondKindNm), BondKindNm, v => BondKindNm = v); changed |= Set(Trimmed(stkSecnNm), StkSecnNm, v => StkSecnNm = v); changed |= Set(Trimmed(wrtbIsin), WrtbIsin, v => WrtbIsin = v); changed |= Set(Trimmed(wrtbSecnNm), WrtbSecnNm, v => WrtbSecnNm = v); changed |= Set(xrcRatio, XrcRatio, v => XrcRatio = v); changed |= Set(xrcPrice, XrcPrice, v => XrcPrice = v); changed |= Set(setaccEndtermLimitDays, SetaccEndtermLimitDays, v => SetaccEndtermLimitDays = v); changed |= Set(newstkAllocDdBfLimitDays, NewstkAllocDdBfLimitDays, v => NewstkAllocDdBfLimitDays = v); changed |= Set(Trimmed(dmanLimitRsnContent), DmanLimitRsnContent, v => DmanLimitRsnContent = 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; } }