using System.ComponentModel.DataAnnotations; namespace Domain.Entities.Stocks; /// /// SEIBro 시장별 단축코드 ↔ 발행회사번호 매핑 (getShotnByMart) — (Market, ShortCode) UNIQUE. /// ISSUCO_CUSTNO 는 SEIBro 기업정보 API 전체의 조인키라 Wave 0 에서 가장 먼저 구축한다. /// K-OTC(13)·기타(50) 등 KRX Stock 이 커버하지 않는 시장의 유일한 종목 마스터이기도 하다 /// (KOSPI/KOSDAQ/KONEX 는 Stock.IssucoCustno 로도 스탬핑되지만 이 테이블이 원본 스냅샷). /// public class SeibroIssuerCode { [Key] public int ID { get; private set; } /// 시장구분 (MART_TPCD, char 2) — [11]유가 [12]코스닥 [13]K-OTC [14]코넥스 [50]기타 public string Market { get; private set; } = default!; /// 단축코드 (SHOTN_ISIN) public string ShortCode { get; private set; } = default!; /// 종목명 (KOR_SECN_NM) public string Name { get; private set; } = default!; /// 발행회사 고객번호 (ISSUCO_CUSTNO) — 예탁결제원 부여 public int IssucoCustno { get; private set; } /// 마지막 동기화 시각 (UTC) public DateTime UpdatedAt { get; private set; } = DateTime.UtcNow; private SeibroIssuerCode() { } public static SeibroIssuerCode Create(string market, string shortCode, string name, int issucoCustno) { if (market is not { Length: 2 } || market.Any(c => !char.IsAsciiDigit(c))) { throw new ArgumentException("market must be 2 digits (MART_TPCD)", nameof(market)); } if (string.IsNullOrWhiteSpace(shortCode) || shortCode.Trim().Length > 12) { throw new ArgumentException("shortCode required (max 12)", nameof(shortCode)); } if (string.IsNullOrWhiteSpace(name)) { throw new ArgumentException("name required", nameof(name)); } return new SeibroIssuerCode { Market = market, ShortCode = shortCode.Trim(), Name = name.Trim(), IssucoCustno = issucoCustno }; } /// 재동기화 반영 — 이름/회사번호 변경 시에만 UpdatedAt 갱신 (무변경 upsert 는 no-op) public void Update(string name, int issucoCustno) { var changed = false; if (!string.IsNullOrWhiteSpace(name) && Name != name.Trim()) { Name = name.Trim(); changed = true; } if (IssucoCustno != issucoCustno) { IssucoCustno = issucoCustno; changed = true; } if (changed) { UpdatedAt = DateTime.UtcNow; } } }