WithdrawalRequest.cs 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. using System.ComponentModel.DataAnnotations;
  2. using System.ComponentModel.DataAnnotations.Schema;
  3. using Domain.Entities.Donations.ValueObject;
  4. using Domain.Entities.Members;
  5. namespace Domain.Entities.Donations;
  6. public class WithdrawalRequest
  7. {
  8. [ForeignKey(nameof(ChannelID))]
  9. public virtual Channel? Channel { get; private set; }
  10. [ForeignKey(nameof(MemberID))]
  11. public virtual Member? Member { get; private set; }
  12. [Key]
  13. public int ID { get; private set; }
  14. public int ChannelID { get; private set; }
  15. public int MemberID { get; private set; }
  16. public int RequestedAmount { get; private set; }
  17. /// <summary>
  18. /// 후원 수익(Donation) 잔액에서 차감되는 금액. RequestedAmount = DonationAmount + StoreRevenueAmount.
  19. /// 정산서 명세에 분리 표시 (출처 보존).
  20. /// </summary>
  21. public int DonationAmount { get; private set; }
  22. /// <summary>
  23. /// 상품 판매 수익(StoreRevenue) 잔액에서 차감되는 금액. RequestedAmount = DonationAmount + StoreRevenueAmount.
  24. /// 정산서 명세에 분리 표시 (출처 보존).
  25. /// </summary>
  26. public int StoreRevenueAmount { get; private set; }
  27. public int IncomeTax { get; private set; }
  28. public int LocalTax { get; private set; }
  29. public int WithholdingTax { get; private set; }
  30. public int NetAmount { get; private set; }
  31. public WithdrawalStatus Status { get; private set; } = WithdrawalStatus.Pending;
  32. public string BankCode { get; private set; } = default!;
  33. public string BankName { get; private set; } = default!;
  34. public string AccountNumber { get; private set; } = default!;
  35. public string AccountHolder { get; private set; } = default!;
  36. public DateTime RequestedAt { get; private set; } = DateTime.UtcNow;
  37. public DateTime? ProcessedAt { get; private set; }
  38. public string? RejectedReason { get; private set; }
  39. public string? AdminMemo { get; private set; }
  40. private WithdrawalRequest() { }
  41. /// <summary>
  42. /// 기존(후원 단일) 출금 요청 생성. RequestedAmount 전액을 DonationAmount로 분배.
  43. /// 신규 코드(상점 수익 분개)는 분리 시그니처 Create(channelID, memberID, donationAmount, storeRevenueAmount, ...) 사용.
  44. /// </summary>
  45. public static WithdrawalRequest Create(
  46. int channelID,
  47. int memberID,
  48. int requestedAmount,
  49. string bankCode,
  50. string bankName,
  51. string accountNumber,
  52. string accountHolder
  53. ) => Create(
  54. channelID: channelID,
  55. memberID: memberID,
  56. donationAmount: requestedAmount,
  57. storeRevenueAmount: 0,
  58. bankCode: bankCode,
  59. bankName: bankName,
  60. accountNumber: accountNumber,
  61. accountHolder: accountHolder
  62. );
  63. /// <summary>
  64. /// 후원/상점 분개 출금 요청 생성. 정산서 명세에 두 잔액 출처를 분리 표시.
  65. /// </summary>
  66. public static WithdrawalRequest Create(
  67. int channelID,
  68. int memberID,
  69. int donationAmount,
  70. int storeRevenueAmount,
  71. string bankCode,
  72. string bankName,
  73. string accountNumber,
  74. string accountHolder
  75. ) {
  76. if (donationAmount < 0)
  77. {
  78. throw new ArgumentOutOfRangeException(nameof(donationAmount), "Donation amount must be non-negative.");
  79. }
  80. if (storeRevenueAmount < 0)
  81. {
  82. throw new ArgumentOutOfRangeException(nameof(storeRevenueAmount), "StoreRevenue amount must be non-negative.");
  83. }
  84. var requestedAmount = donationAmount + storeRevenueAmount;
  85. if (requestedAmount <= 0)
  86. {
  87. throw new ArgumentOutOfRangeException(nameof(requestedAmount), "Total withdrawal amount must be positive.");
  88. }
  89. var incomeTax = (int)Math.Floor(requestedAmount * 3m / 100m);
  90. var localTax = (int)Math.Floor(requestedAmount * 3m / 1000m);
  91. var withholdingTax = incomeTax + localTax;
  92. return new WithdrawalRequest
  93. {
  94. ChannelID = channelID,
  95. MemberID = memberID,
  96. RequestedAmount = requestedAmount,
  97. DonationAmount = donationAmount,
  98. StoreRevenueAmount = storeRevenueAmount,
  99. IncomeTax = incomeTax,
  100. LocalTax = localTax,
  101. WithholdingTax = withholdingTax,
  102. NetAmount = requestedAmount - withholdingTax,
  103. BankCode = bankCode,
  104. BankName = bankName,
  105. AccountNumber = accountNumber,
  106. AccountHolder = accountHolder
  107. };
  108. }
  109. public void MarkProcessing(string? adminMemo = null)
  110. {
  111. Status = WithdrawalStatus.Processing;
  112. AdminMemo = adminMemo;
  113. }
  114. public void Complete(string? adminMemo = null)
  115. {
  116. Status = WithdrawalStatus.Completed;
  117. ProcessedAt = DateTime.UtcNow;
  118. AdminMemo = adminMemo;
  119. }
  120. public void Reject(string reason, string? adminMemo = null)
  121. {
  122. Status = WithdrawalStatus.Rejected;
  123. ProcessedAt = DateTime.UtcNow;
  124. RejectedReason = reason;
  125. AdminMemo = adminMemo;
  126. }
  127. }