using System.ComponentModel.DataAnnotations; namespace Domain.Entities.Stocks; /// /// SEIBro 주식증감내역 (getStkIncdecDetails) — 발행횟수별 주식수 변동(액면분할·증자·이익소각 등) (Wave 2 ★P1). /// rolling per-회사번호(SHOTN_ISIN) 로 수집한다. /// ⚠️ 자연키: plan 은 (Isin, SecnIssuNtimes) 를 제시하나 실응답(삼성전자 fixture)에서 동일 발행횟수(75)가 /// 서로 다른 발행일·사유(액면분할 20180503 / 이익소각 20181204)로 2행 존재한다 → (Isin, SecnIssuNtimes) 단독은 충돌. /// 따라서 UNIQUE (Isin, SecnIssuNtimes, IssuDt, SecnIssuRacd) 로 확장해 데이터 손실 없이 upsert 한다. /// public class StockIssuanceChange { [Key] public int ID { get; private set; } /// 발행회사 고객번호 (ISSUCO_CUSTNO) public int? IssucoCustno { get; private set; } /// 종목번호 (ISIN) public string Isin { get; private set; } = default!; /// 종목종류코드 (SECN_KACD) — 0101 보통주 0201 우선주… public string? SecnKacd { get; private set; } /// 종목발행횟수 (SECN_ISSU_NTIMES) public int SecnIssuNtimes { get; private set; } /// 발행일자 (ISSU_DT) public DateOnly IssuDt { get; private set; } /// 발행가 (ISSUPRC, 1주당) public decimal? Issuprc { get; private set; } /// 발행수량 (ISSU_QTY) — 감소는 음수 public long? IssuQty { get; private set; } /// 종목발행사유코드 (SECN_ISSU_RACD) public string SecnIssuRacd { get; private set; } = default!; /// 종목발행사유명 (SECN_ISSU_NM) — 예 액면분할 public string? SecnIssuNm { get; private set; } /// 상장일자 (LIST_DT) public DateOnly? ListDt { get; private set; } public DateTime UpdatedAt { get; private set; } = DateTime.UtcNow; private StockIssuanceChange() { } public static StockIssuanceChange Create( int? issucoCustno, string isin, string? secnKacd, int secnIssuNtimes, DateOnly issuDt, decimal? issuprc, long? issuQty, string secnIssuRacd, string? secnIssuNm, DateOnly? listDt) { if (string.IsNullOrWhiteSpace(isin)) { throw new ArgumentException("isin required", nameof(isin)); } if (string.IsNullOrWhiteSpace(secnIssuRacd)) { throw new ArgumentException("secnIssuRacd required", nameof(secnIssuRacd)); } return new StockIssuanceChange { IssucoCustno = issucoCustno, Isin = isin.Trim(), SecnKacd = string.IsNullOrWhiteSpace(secnKacd) ? null : secnKacd.Trim(), SecnIssuNtimes = secnIssuNtimes, IssuDt = issuDt, Issuprc = issuprc, IssuQty = issuQty, SecnIssuRacd = secnIssuRacd.Trim(), SecnIssuNm = string.IsNullOrWhiteSpace(secnIssuNm) ? null : secnIssuNm.Trim(), ListDt = listDt }; } /// 재수집 반영 — 변경 필드가 있을 때만 UpdatedAt 갱신 (자연키는 불변) public void Update( int? issucoCustno, string? secnKacd, decimal? issuprc, long? issuQty, string? secnIssuNm, DateOnly? listDt) { var changed = false; if (issucoCustno is not null && IssucoCustno != issucoCustno) { IssucoCustno = issucoCustno; changed = true; } var normalizedSecnKacd = string.IsNullOrWhiteSpace(secnKacd) ? null : secnKacd.Trim(); if (SecnKacd != normalizedSecnKacd) { SecnKacd = normalizedSecnKacd; changed = true; } if (Issuprc != issuprc) { Issuprc = issuprc; changed = true; } if (IssuQty != issuQty) { IssuQty = issuQty; changed = true; } var normalizedSecnIssuNm = string.IsNullOrWhiteSpace(secnIssuNm) ? null : secnIssuNm.Trim(); if (SecnIssuNm != normalizedSecnIssuNm) { SecnIssuNm = normalizedSecnIssuNm; changed = true; } if (ListDt != listDt) { ListDt = listDt; changed = true; } if (changed) { UpdatedAt = DateTime.UtcNow; } } }