using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using Domain.Entities.Donations.ValueObject; using Domain.Entities.Members; namespace Domain.Entities.Donations; public class WithdrawalRequest { [ForeignKey(nameof(ChannelID))] public virtual Channel? Channel { get; private set; } [ForeignKey(nameof(MemberID))] public virtual Member? Member { get; private set; } [Key] public int ID { get; private set; } public int ChannelID { get; private set; } public int MemberID { get; private set; } public int RequestedAmount { get; private set; } /// /// 후원 수익(Donation) 잔액에서 차감되는 금액. RequestedAmount = DonationAmount + StoreRevenueAmount. /// 정산서 명세에 분리 표시 (출처 보존). /// public int DonationAmount { get; private set; } /// /// 상품 판매 수익(StoreRevenue) 잔액에서 차감되는 금액. RequestedAmount = DonationAmount + StoreRevenueAmount. /// 정산서 명세에 분리 표시 (출처 보존). /// public int StoreRevenueAmount { get; private set; } public int IncomeTax { get; private set; } public int LocalTax { get; private set; } public int WithholdingTax { get; private set; } public int NetAmount { get; private set; } public WithdrawalStatus Status { get; private set; } = WithdrawalStatus.Pending; public string BankCode { get; private set; } = default!; public string BankName { get; private set; } = default!; public string AccountNumber { get; private set; } = default!; public string AccountHolder { get; private set; } = default!; public DateTime RequestedAt { get; private set; } = DateTime.UtcNow; public DateTime? ProcessedAt { get; private set; } public string? RejectedReason { get; private set; } public string? AdminMemo { get; private set; } private WithdrawalRequest() { } /// /// 기존(후원 단일) 출금 요청 생성. RequestedAmount 전액을 DonationAmount로 분배. /// 신규 코드(상점 수익 분개)는 분리 시그니처 Create(channelID, memberID, donationAmount, storeRevenueAmount, ...) 사용. /// public static WithdrawalRequest Create( int channelID, int memberID, int requestedAmount, string bankCode, string bankName, string accountNumber, string accountHolder ) => Create( channelID: channelID, memberID: memberID, donationAmount: requestedAmount, storeRevenueAmount: 0, bankCode: bankCode, bankName: bankName, accountNumber: accountNumber, accountHolder: accountHolder ); /// /// 후원/상점 분개 출금 요청 생성. 정산서 명세에 두 잔액 출처를 분리 표시. /// public static WithdrawalRequest Create( int channelID, int memberID, int donationAmount, int storeRevenueAmount, string bankCode, string bankName, string accountNumber, string accountHolder ) { if (donationAmount < 0) { throw new ArgumentOutOfRangeException(nameof(donationAmount), "Donation amount must be non-negative."); } if (storeRevenueAmount < 0) { throw new ArgumentOutOfRangeException(nameof(storeRevenueAmount), "StoreRevenue amount must be non-negative."); } var requestedAmount = donationAmount + storeRevenueAmount; if (requestedAmount <= 0) { throw new ArgumentOutOfRangeException(nameof(requestedAmount), "Total withdrawal amount must be positive."); } var incomeTax = (int)Math.Floor(requestedAmount * 3m / 100m); var localTax = (int)Math.Floor(requestedAmount * 3m / 1000m); var withholdingTax = incomeTax + localTax; return new WithdrawalRequest { ChannelID = channelID, MemberID = memberID, RequestedAmount = requestedAmount, DonationAmount = donationAmount, StoreRevenueAmount = storeRevenueAmount, IncomeTax = incomeTax, LocalTax = localTax, WithholdingTax = withholdingTax, NetAmount = requestedAmount - withholdingTax, BankCode = bankCode, BankName = bankName, AccountNumber = accountNumber, AccountHolder = accountHolder }; } public void MarkProcessing(string? adminMemo = null) { Status = WithdrawalStatus.Processing; AdminMemo = adminMemo; } public void Complete(string? adminMemo = null) { Status = WithdrawalStatus.Completed; ProcessedAt = DateTime.UtcNow; AdminMemo = adminMemo; } public void Reject(string reason, string? adminMemo = null) { Status = WithdrawalStatus.Rejected; ProcessedAt = DateTime.UtcNow; RejectedReason = reason; AdminMemo = adminMemo; } }