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();
}
}