using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using Domain.Entities.Store.ValueObject; namespace Domain.Entities.Store; /// /// 배송 정보. Order 1:1 (Physical 상품 포함 주문 시점에 생성). /// 주문 시점의 회원 주소 스냅샷을 보존 (회원이 추후 주소 변경해도 영향 없음). 암호화는 MemberAddress 와 동일. /// public class Shipment { [ForeignKey(nameof(OrderID))] public virtual Order? Order { get; private set; } [Key] public int ID { get; private set; } public int OrderID { get; private set; } // 주소 스냅샷 (암호화) public string RecipientName_Encrypted { get; private set; } = default!; public string Phone_Encrypted { get; private set; } = default!; public string ZipCode { get; private set; } = default!; public string Address1_Encrypted { get; private set; } = default!; public string Address2_Encrypted { get; private set; } = default!; public int KeyVersion { get; private set; } public ShipmentStatus Status { get; private set; } = ShipmentStatus.Preparing; public string? Carrier { get; private set; } public string? TrackingNumber { get; private set; } /// 배송비 (원) — 관리자 수동 입력. 기본 0. public int ShippingFee { get; private set; } /// 관리자 메모 (운영용 내부 메모, 최대 500자) public string? AdminMemo { get; private set; } public DateTime CreatedAt { get; private set; } = DateTime.UtcNow; public DateTime? ShippedAt { get; private set; } public DateTime? DeliveredAt { get; private set; } private Shipment() { } public static Shipment Create( int orderID, string recipientNameEncrypted, string phoneEncrypted, string zipCode, string address1Encrypted, string address2Encrypted, int keyVersion ) { if (orderID <= 0) { throw new ArgumentOutOfRangeException(nameof(orderID)); } if (string.IsNullOrWhiteSpace(recipientNameEncrypted)) { throw new ArgumentException("RecipientName_Encrypted is required.", nameof(recipientNameEncrypted)); } if (string.IsNullOrWhiteSpace(phoneEncrypted)) { throw new ArgumentException("Phone_Encrypted is required.", nameof(phoneEncrypted)); } if (string.IsNullOrWhiteSpace(zipCode)) { throw new ArgumentException("ZipCode is required.", nameof(zipCode)); } if (zipCode.Length > 10) { throw new ArgumentOutOfRangeException(nameof(zipCode)); } if (string.IsNullOrWhiteSpace(address1Encrypted)) { throw new ArgumentException("Address1_Encrypted is required.", nameof(address1Encrypted)); } if (string.IsNullOrWhiteSpace(address2Encrypted)) { throw new ArgumentException("Address2_Encrypted is required.", nameof(address2Encrypted)); } if (keyVersion <= 0) { throw new ArgumentOutOfRangeException(nameof(keyVersion)); } return new Shipment { OrderID = orderID, RecipientName_Encrypted = recipientNameEncrypted, Phone_Encrypted = phoneEncrypted, ZipCode = zipCode, Address1_Encrypted = address1Encrypted, Address2_Encrypted = address2Encrypted, KeyVersion = keyVersion }; } public void RegisterTracking(string carrier, string trackingNumber) { if (string.IsNullOrWhiteSpace(carrier)) { throw new ArgumentException("Carrier is required.", nameof(carrier)); } if (carrier.Length > 50) { throw new ArgumentOutOfRangeException(nameof(carrier)); } if (string.IsNullOrWhiteSpace(trackingNumber)) { throw new ArgumentException("TrackingNumber is required.", nameof(trackingNumber)); } if (trackingNumber.Length > 50) { throw new ArgumentOutOfRangeException(nameof(trackingNumber)); } Carrier = carrier; TrackingNumber = trackingNumber; if (Status == ShipmentStatus.Preparing) { Status = ShipmentStatus.Pickup; } } public void MarkInTransit() { if (Status is not ShipmentStatus.Pickup and not ShipmentStatus.Preparing) { throw new InvalidOperationException($"Cannot mark in transit from status {Status}."); } Status = ShipmentStatus.InTransit; ShippedAt = DateTime.UtcNow; } public void MarkDelivered() { if (Status != ShipmentStatus.InTransit) { throw new InvalidOperationException($"Cannot mark delivered from status {Status}."); } Status = ShipmentStatus.Delivered; DeliveredAt = DateTime.UtcNow; } public void MarkFailed() { Status = ShipmentStatus.Failed; } /// 배송비 + 관리자 메모 동시 갱신 (배송 상세 form 에서 호출) public void UpdateBillingInfo(int shippingFee, string? adminMemo) { if (shippingFee < 0) { throw new ArgumentOutOfRangeException(nameof(shippingFee), "ShippingFee must be >= 0."); } if (adminMemo is { Length: > 500 }) { throw new ArgumentOutOfRangeException(nameof(adminMemo), "AdminMemo max length is 500."); } ShippingFee = shippingFee; AdminMemo = string.IsNullOrWhiteSpace(adminMemo) ? null : adminMemo.Trim(); } }