using MediatR; using TakeoutSaaS.Application.App.Tenants.Commands; using TakeoutSaaS.Application.App.Tenants.Dto; using TakeoutSaaS.Domain.Tenants.Entities; using TakeoutSaaS.Domain.Tenants.Enums; using TakeoutSaaS.Domain.Tenants.Repositories; using TakeoutSaaS.Shared.Abstractions.Constants; using TakeoutSaaS.Shared.Abstractions.Exceptions; using TakeoutSaaS.Shared.Abstractions.Ids; using TakeoutSaaS.Shared.Abstractions.Security; namespace TakeoutSaaS.Application.App.Tenants.Handlers; /// /// 延期/赠送订阅处理器(按当前订阅套餐续费)。 /// public sealed class ExtendTenantSubscriptionCommandHandler( ITenantRepository tenantRepository, IIdGenerator idGenerator, ICurrentUserAccessor currentUserAccessor) : IRequestHandler { /// public async Task Handle(ExtendTenantSubscriptionCommand request, CancellationToken cancellationToken) { // 1. 校验时长 if (request.DurationMonths <= 0) { throw new BusinessException(ErrorCodes.BadRequest, "延期/赠送时长必须大于 0"); } // 2. 获取租户与当前订阅 var tenant = await tenantRepository.FindByIdAsync(request.TenantId, cancellationToken) ?? throw new BusinessException(ErrorCodes.NotFound, "租户不存在"); var current = await tenantRepository.GetActiveSubscriptionAsync(request.TenantId, cancellationToken) ?? throw new BusinessException(ErrorCodes.BadRequest, "订阅不存在,无法延期/赠送"); var now = DateTime.UtcNow; var effectiveFrom = current.EffectiveTo > now ? current.EffectiveTo : now; var effectiveTo = effectiveFrom.AddMonths(request.DurationMonths); var previousStatus = tenant.Status; var actorName = currentUserAccessor.IsAuthenticated ? $"user:{currentUserAccessor.UserId}" : "system"; // 3. 创建续费订阅 var subscription = new TenantSubscription { Id = idGenerator.NextId(), TenantId = tenant.Id, TenantPackageId = current.TenantPackageId, EffectiveFrom = effectiveFrom, EffectiveTo = effectiveTo, NextBillingDate = effectiveTo, Status = SubscriptionStatus.Active, AutoRenew = current.AutoRenew, Notes = request.Notes }; await tenantRepository.AddSubscriptionAsync(subscription, cancellationToken); await tenantRepository.AddSubscriptionHistoryAsync(new TenantSubscriptionHistory { Id = idGenerator.NextId(), TenantId = tenant.Id, TenantSubscriptionId = subscription.Id, FromPackageId = current.TenantPackageId, ToPackageId = current.TenantPackageId, ChangeType = SubscriptionChangeType.Renew, EffectiveFrom = effectiveFrom, EffectiveTo = effectiveTo, Amount = null, Currency = null, Notes = request.Notes }, cancellationToken); // 4. 若租户处于到期状态则恢复为正常(冻结状态需先解冻) if (tenant.Status == TenantStatus.Expired) { tenant.Status = TenantStatus.Active; } tenant.EffectiveFrom = subscription.EffectiveFrom; tenant.EffectiveTo = subscription.EffectiveTo; await tenantRepository.UpdateTenantAsync(tenant, cancellationToken); // 5. 记录审计 await tenantRepository.AddAuditLogAsync(new TenantAuditLog { TenantId = tenant.Id, Action = TenantAuditAction.SubscriptionUpdated, Title = "延期/赠送时长", Description = $"续费 {request.DurationMonths} 月,到期时间:{current.EffectiveTo:yyyy-MM-dd HH:mm:ss} -> {subscription.EffectiveTo:yyyy-MM-dd HH:mm:ss}", OperatorId = currentUserAccessor.UserId == 0 ? null : currentUserAccessor.UserId, OperatorName = actorName, PreviousStatus = previousStatus, CurrentStatus = tenant.Status }, cancellationToken); // 6. 保存并返回 await tenantRepository.SaveChangesAsync(cancellationToken); return subscription.ToSubscriptionDto() ?? throw new BusinessException(ErrorCodes.InternalServerError, "订阅生成失败"); } }