ForeignCustodySecurity.cs 3.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. using System.ComponentModel.DataAnnotations;
  2. namespace Domain.Entities.Stocks;
  3. /// <summary>
  4. /// SEIBro 종목별 외화증권 보관현황 (getSecnFrsecCusInfo) — KSD 를 통한 특정 외화종목(ISIN)의 보유금액(USD)·보유수량 (Wave 6).
  5. /// 자연키 UNIQUE (StdDt, Isin, NationCd, SecnTpcd) — getSecnFrsecCusInfo 는 종목당 **국가별 복수 행**(실샘플 DE+US)을 반환하므로
  6. /// (리뷰 결함 #3) 국가·종목구분을 자연키에 포함해 전 행을 iterate upsert 한다(기존 (StdDt,Isin) UQ 는 첫 행만 반영해 국가별 보관을 무음 병합했다).
  7. /// 응답 필드는 NationCd·SecnTpcd·FrsecTotHoldAmt·FrsecTotHoldQty 4종 — 배치가 STD_DT·ISIN 을 요청값으로 스탬핑한다.
  8. /// ForeignIsins 설정이 기본 빈 리스트라 종목별 수집은 사실상 대기 상태(seed 지정 시 활성). NationCd·SecnTpcd 는 자연키라 non-null(빈 값은 "").
  9. /// </summary>
  10. public class ForeignCustodySecurity
  11. {
  12. [Key]
  13. public int ID { get; private set; }
  14. /// <summary>기준일자 (STD_DT, 요청값 스탬핑)</summary>
  15. public DateOnly StdDt { get; private set; }
  16. /// <summary>종목코드 (ISIN, 요청값 스탬핑)</summary>
  17. public string Isin { get; private set; } = default!;
  18. /// <summary>국가코드 (NATION_CD) — 자연키(종목당 국가별 복수 행)</summary>
  19. public string NationCd { get; private set; } = default!;
  20. /// <summary>종목구분코드 (SECN_TPCD) — 71외화주식~78외화수익증권. 자연키</summary>
  21. public string SecnTpcd { get; private set; } = default!;
  22. /// <summary>외화증권보유금액 (FRSEC_TOT_HOLD_AMT) — USD</summary>
  23. public decimal? FrsecTotHoldAmt { get; private set; }
  24. /// <summary>외화증권총보유수량 (FRSEC_TOT_HOLD_QTY)</summary>
  25. public decimal? FrsecTotHoldQty { get; private set; }
  26. public DateTime UpdatedAt { get; private set; } = DateTime.UtcNow;
  27. private ForeignCustodySecurity() { }
  28. public static ForeignCustodySecurity Create(DateOnly stdDt, string isin, string? nationCd, string? secnTpcd, decimal? frsecTotHoldAmt, decimal? frsecTotHoldQty)
  29. {
  30. if (string.IsNullOrWhiteSpace(isin))
  31. {
  32. throw new ArgumentException("isin required", nameof(isin));
  33. }
  34. return new ForeignCustodySecurity
  35. {
  36. StdDt = stdDt,
  37. Isin = isin.Trim(),
  38. NationCd = NonNull(nationCd),
  39. SecnTpcd = NonNull(secnTpcd),
  40. FrsecTotHoldAmt = frsecTotHoldAmt,
  41. FrsecTotHoldQty = frsecTotHoldQty
  42. };
  43. }
  44. /// <summary>재수집 반영 — 변경 필드가 있을 때만 UpdatedAt 갱신 (자연키 StdDt/Isin/NationCd/SecnTpcd 은 불변)</summary>
  45. public void Update(decimal? frsecTotHoldAmt, decimal? frsecTotHoldQty)
  46. {
  47. var changed = false;
  48. changed |= Set(frsecTotHoldAmt, FrsecTotHoldAmt, v => FrsecTotHoldAmt = v);
  49. changed |= Set(frsecTotHoldQty, FrsecTotHoldQty, v => FrsecTotHoldQty = v);
  50. if (changed)
  51. {
  52. UpdatedAt = DateTime.UtcNow;
  53. }
  54. }
  55. private static string NonNull(string? value)
  56. {
  57. return string.IsNullOrWhiteSpace(value) ? string.Empty : value.Trim();
  58. }
  59. private static bool Set<T>(T incoming, T current, Action<T> assign)
  60. {
  61. if (EqualityComparer<T>.Default.Equals(incoming, current))
  62. {
  63. return false;
  64. }
  65. assign(incoming);
  66. return true;
  67. }
  68. }