using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Domain.Entities.Store.ValueObject;
namespace Domain.Entities.Store;
///
/// 게임사 월별 정산 큐. 매월 1일 새벽 HostedService 가 전월 Order 들의 GameRevenueAmount 를 GameID 별로 집계.
/// UNIQUE (GameID, Year, Month). Admin 이 다운로드 후 수동 송금하고 Settled 마킹.
///
public class GameSettlement
{
[ForeignKey(nameof(GameID))]
public virtual Game? Game { get; private set; }
[Key]
public int ID { get; private set; }
public int GameID { get; private set; }
public int Year { get; private set; }
public int Month { get; private set; }
/// 해당 월 누적 게임사 몫 (= Σ Order.GameRevenueAmount).
public int TotalRevenueAmount { get; private set; }
/// 해당 월 주문 건수.
public int OrderCount { get; private set; }
public GameSettlementStatus Status { get; private set; } = GameSettlementStatus.Pending;
public DateTime CreatedAt { get; private set; } = DateTime.UtcNow;
public DateTime? SettledAt { get; private set; }
/// 처리 관리자 (ASP.NET Identity ApplicationUser.Id).
public string? SettledByAdminUserId { get; private set; }
/// 처리 관리자 Email 스냅샷 (표시용).
public string? SettledByAdminEmail { get; private set; }
public string? AdminMemo { get; private set; }
private GameSettlement() { }
public static GameSettlement Create(int gameID, int year, int month)
{
if (gameID <= 0)
{
throw new ArgumentOutOfRangeException(nameof(gameID));
}
if (year < 2000 || year > 9999)
{
throw new ArgumentOutOfRangeException(nameof(year));
}
if (month < 1 || month > 12)
{
throw new ArgumentOutOfRangeException(nameof(month));
}
return new GameSettlement
{
GameID = gameID,
Year = year,
Month = month
};
}
/// 월간 집계 시 row 가 이미 있으면 누적. HostedService 에서 멱등 처리용.
public void AddRevenue(int revenueAmount, int orderCount = 1)
{
if (revenueAmount < 0)
{
throw new ArgumentOutOfRangeException(nameof(revenueAmount));
}
if (orderCount < 0)
{
throw new ArgumentOutOfRangeException(nameof(orderCount));
}
if (Status != GameSettlementStatus.Pending)
{
throw new InvalidOperationException("이미 정산된 row 에는 추가 적재 불가.");
}
TotalRevenueAmount += revenueAmount;
OrderCount += orderCount;
}
/// 환불 시 비례 차감.
public void SubtractRevenue(int revenueAmount)
{
if (revenueAmount <= 0)
{
throw new ArgumentOutOfRangeException(nameof(revenueAmount));
}
if (Status != GameSettlementStatus.Pending)
{
throw new InvalidOperationException("이미 정산된 row 에는 환불 차감 불가. 별도 정정 정산 row 필요.");
}
TotalRevenueAmount = Math.Max(0, TotalRevenueAmount - revenueAmount);
}
public void MarkSettled(string adminUserId, string? adminEmail, string? adminMemo = null)
{
if (Status != GameSettlementStatus.Pending)
{
throw new InvalidOperationException($"Cannot mark settled from status {Status}.");
}
if (string.IsNullOrWhiteSpace(adminUserId))
{
throw new ArgumentException("AdminUserId is required.", nameof(adminUserId));
}
Status = GameSettlementStatus.Settled;
SettledByAdminUserId = adminUserId;
SettledByAdminEmail = adminEmail;
SettledAt = DateTime.UtcNow;
AdminMemo = adminMemo;
}
}