using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using Domain.Entities.Paper.ValueObject; namespace Domain.Entities.Paper; /// /// 모의투자 주문 — 접수 시 서버가 FillRule/TargetDate/CancelableUntil 을 확정한다 (d4 §③ 체결 규칙). /// 실제 기표는 TargetDate 시세가 도착하는 배치(M2)에서 수행 (Pending → Filled/Rejected). /// public class PaperOrder { [ForeignKey(nameof(AccountID))] public virtual PaperAccount Account { get; private set; } = null!; [Key] public int ID { get; private set; } public int AccountID { get; private set; } /// 단축코드 (char 6) public string StockCode { get; private set; } = default!; public PaperOrderSide Side { get; private set; } public PaperFillRule FillRule { get; private set; } public int Quantity { get; private set; } /// 매수 예약금 (매도는 0) public decimal ReservedAmount { get; private set; } /// 체결 기준일 public DateOnly TargetDate { get; private set; } /// 취소 가능 시한 — 이후 취소 불가 (look-ahead 어뷰징 차단) public DateTime CancelableUntil { get; private set; } public PaperOrderStatus Status { get; private set; } /// 거부 사유 (Rejected 시) public string? RejectReason { get; private set; } public DateTime CreatedAt { get; private set; } = DateTime.UtcNow; private PaperOrder() { } public static PaperOrder Create( int accountID, string stockCode, PaperOrderSide side, PaperFillRule fillRule, int quantity, decimal reservedAmount, DateOnly targetDate, DateTime cancelableUntil ) { if (accountID <= 0) { throw new ArgumentOutOfRangeException(nameof(accountID)); } if (stockCode is not { Length: 6 }) { throw new ArgumentException("stockCode must be 6 chars", nameof(stockCode)); } if (quantity <= 0) { throw new ArgumentOutOfRangeException(nameof(quantity)); } if (reservedAmount < 0) { throw new ArgumentOutOfRangeException(nameof(reservedAmount)); } return new PaperOrder { AccountID = accountID, StockCode = stockCode, Side = side, FillRule = fillRule, Quantity = quantity, ReservedAmount = reservedAmount, TargetDate = targetDate, CancelableUntil = cancelableUntil, Status = PaperOrderStatus.Pending }; } /// 취소 — Pending 만 취소 가능. 성공 시 true. 시한 검증은 호출자. public bool Cancel() { if (Status != PaperOrderStatus.Pending) { return false; } Status = PaperOrderStatus.Cancelled; return true; } /// 체결 확정 (배치 M2). public void MarkFilled() { Status = PaperOrderStatus.Filled; } /// 거부 처리 (배치 M2). public void MarkRejected(string reason) { Status = PaperOrderStatus.Rejected; RejectReason = reason; } }