using TakeoutSaaS.Domain.Tenants.Enums; using TakeoutSaaS.Shared.Abstractions.Entities; namespace TakeoutSaaS.Domain.Tenants.Entities; /// /// 租户账单,用于呈现周期性收费。 /// public sealed class TenantBillingStatement : MultiTenantEntityBase { /// /// 账单编号,供对账查询。 /// public string StatementNo { get; set; } = string.Empty; /// /// 账单类型(订阅账单/配额包账单/手动账单/续费账单)。 /// public BillingType BillingType { get; set; } = BillingType.Subscription; /// /// 关联的订阅 ID(仅当 BillingType 为 Subscription 或 Renewal 时有值)。 /// public long? SubscriptionId { get; set; } /// /// 账单周期开始时间。 /// public DateTime PeriodStart { get; set; } /// /// 账单周期结束时间。 /// public DateTime PeriodEnd { get; set; } /// /// 应付金额(原始金额)。 /// public decimal AmountDue { get; set; } /// /// 折扣金额。 /// public decimal DiscountAmount { get; set; } /// /// 税费金额。 /// public decimal TaxAmount { get; set; } /// /// 实付金额。 /// public decimal AmountPaid { get; set; } /// /// 货币类型(默认 CNY)。 /// public string Currency { get; set; } = "CNY"; /// /// 当前付款状态。 /// public TenantBillingStatus Status { get; set; } = TenantBillingStatus.Pending; /// /// 到期日。 /// public DateTime DueDate { get; set; } /// /// 提醒发送时间(续费提醒、逾期提醒等)。 /// public DateTime? ReminderSentAt { get; set; } /// /// 逾期通知时间。 /// public DateTime? OverdueNotifiedAt { get; set; } /// /// 账单明细 JSON,记录各项费用。 /// public string? LineItemsJson { get; set; } /// /// 备注信息(如:人工备注、取消原因等)。 /// public string? Notes { get; set; } /// /// 计算总金额(应付金额 - 折扣 + 税费)。 /// /// 总金额。 public decimal CalculateTotalAmount() { return AmountDue - DiscountAmount + TaxAmount; } /// /// 标记为已支付(直接结清)。 /// public void MarkAsPaid() { // 1. 计算剩余应付金额 var remainingAmount = CalculateTotalAmount() - AmountPaid; // 2. 若已结清则直接返回 if (remainingAmount <= 0) { Status = TenantBillingStatus.Paid; return; } // 3. 补足剩余金额并标记为已支付 MarkAsPaid(remainingAmount, string.Empty); } /// /// 标记为已支付。 /// /// 支付金额。 /// 交易号。 public void MarkAsPaid(decimal amount, string transactionNo) { if (Status == TenantBillingStatus.Paid) { throw new InvalidOperationException("账单已经处于已支付状态,不能重复标记。"); } if (Status == TenantBillingStatus.Cancelled) { throw new InvalidOperationException("已取消的账单不能标记为已支付。"); } // 1. 累加支付金额 AmountPaid += amount; // 2. 如果实付金额大于等于应付总额,则标记为已支付 if (AmountPaid >= CalculateTotalAmount()) { Status = TenantBillingStatus.Paid; } } /// /// 标记为逾期。 /// public void MarkAsOverdue() { // 1. 仅待支付账单允许标记逾期 if (Status != TenantBillingStatus.Pending) { return; } // 2. 未超过到期日则不处理 if (DateTime.UtcNow <= DueDate) { return; } // 3. 标记为逾期(通知时间由外部流程在发送通知时写入) Status = TenantBillingStatus.Overdue; } /// /// 取消账单。 /// public void Cancel() { Cancel(null); } /// /// 取消账单。 /// /// 取消原因。 public void Cancel(string? reason) { if (Status == TenantBillingStatus.Paid) { throw new InvalidOperationException("已支付的账单不能取消。"); } if (Status == TenantBillingStatus.Cancelled) { throw new InvalidOperationException("账单已经处于取消状态。"); } // 1. 变更状态 Status = TenantBillingStatus.Cancelled; // 2. 记录取消原因(可选) if (!string.IsNullOrWhiteSpace(reason)) { Notes = string.IsNullOrWhiteSpace(Notes) ? $"[取消原因] {reason}" : $"{Notes}\n[取消原因] {reason}"; } } }