GameSettlement.cs 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. using System.ComponentModel.DataAnnotations;
  2. using System.ComponentModel.DataAnnotations.Schema;
  3. using Domain.Entities.Store.ValueObject;
  4. namespace Domain.Entities.Store;
  5. /// <summary>
  6. /// 게임사 월별 정산 큐. 매월 1일 새벽 HostedService 가 전월 Order 들의 GameRevenueAmount 를 GameID 별로 집계.
  7. /// UNIQUE (GameID, Year, Month). Admin 이 다운로드 후 수동 송금하고 Settled 마킹.
  8. /// </summary>
  9. public class GameSettlement
  10. {
  11. [ForeignKey(nameof(GameID))]
  12. public virtual Game? Game { get; private set; }
  13. [Key]
  14. public int ID { get; private set; }
  15. public int GameID { get; private set; }
  16. public int Year { get; private set; }
  17. public int Month { get; private set; }
  18. /// <summary>해당 월 누적 게임사 몫 (= Σ Order.GameRevenueAmount).</summary>
  19. public int TotalRevenueAmount { get; private set; }
  20. /// <summary>해당 월 주문 건수.</summary>
  21. public int OrderCount { get; private set; }
  22. public GameSettlementStatus Status { get; private set; } = GameSettlementStatus.Pending;
  23. public DateTime CreatedAt { get; private set; } = DateTime.UtcNow;
  24. public DateTime? SettledAt { get; private set; }
  25. /// <summary>처리 관리자 (ASP.NET Identity ApplicationUser.Id).</summary>
  26. public string? SettledByAdminUserId { get; private set; }
  27. /// <summary>처리 관리자 Email 스냅샷 (표시용).</summary>
  28. public string? SettledByAdminEmail { get; private set; }
  29. public string? AdminMemo { get; private set; }
  30. private GameSettlement() { }
  31. public static GameSettlement Create(int gameID, int year, int month)
  32. {
  33. if (gameID <= 0)
  34. {
  35. throw new ArgumentOutOfRangeException(nameof(gameID));
  36. }
  37. if (year < 2000 || year > 9999)
  38. {
  39. throw new ArgumentOutOfRangeException(nameof(year));
  40. }
  41. if (month < 1 || month > 12)
  42. {
  43. throw new ArgumentOutOfRangeException(nameof(month));
  44. }
  45. return new GameSettlement
  46. {
  47. GameID = gameID,
  48. Year = year,
  49. Month = month
  50. };
  51. }
  52. /// <summary>월간 집계 시 row 가 이미 있으면 누적. HostedService 에서 멱등 처리용.</summary>
  53. public void AddRevenue(int revenueAmount, int orderCount = 1)
  54. {
  55. if (revenueAmount < 0)
  56. {
  57. throw new ArgumentOutOfRangeException(nameof(revenueAmount));
  58. }
  59. if (orderCount < 0)
  60. {
  61. throw new ArgumentOutOfRangeException(nameof(orderCount));
  62. }
  63. if (Status != GameSettlementStatus.Pending)
  64. {
  65. throw new InvalidOperationException("이미 정산된 row 에는 추가 적재 불가.");
  66. }
  67. TotalRevenueAmount += revenueAmount;
  68. OrderCount += orderCount;
  69. }
  70. /// <summary>환불 시 비례 차감.</summary>
  71. public void SubtractRevenue(int revenueAmount)
  72. {
  73. if (revenueAmount <= 0)
  74. {
  75. throw new ArgumentOutOfRangeException(nameof(revenueAmount));
  76. }
  77. if (Status != GameSettlementStatus.Pending)
  78. {
  79. throw new InvalidOperationException("이미 정산된 row 에는 환불 차감 불가. 별도 정정 정산 row 필요.");
  80. }
  81. TotalRevenueAmount = Math.Max(0, TotalRevenueAmount - revenueAmount);
  82. }
  83. public void MarkSettled(string adminUserId, string? adminEmail, string? adminMemo = null)
  84. {
  85. if (Status != GameSettlementStatus.Pending)
  86. {
  87. throw new InvalidOperationException($"Cannot mark settled from status {Status}.");
  88. }
  89. if (string.IsNullOrWhiteSpace(adminUserId))
  90. {
  91. throw new ArgumentException("AdminUserId is required.", nameof(adminUserId));
  92. }
  93. Status = GameSettlementStatus.Settled;
  94. SettledByAdminUserId = adminUserId;
  95. SettledByAdminEmail = adminEmail;
  96. SettledAt = DateTime.UtcNow;
  97. AdminMemo = adminMemo;
  98. }
  99. }