using Microsoft.EntityFrameworkCore; using TakeoutSaaS.Domain.Tenants.Entities; using TakeoutSaaS.Domain.Tenants.Enums; using TakeoutSaaS.Domain.Tenants.Repositories; using TakeoutSaaS.Infrastructure.App.Persistence; using TakeoutSaaS.Infrastructure.Logs.Persistence; namespace TakeoutSaaS.Infrastructure.App.Repositories; /// /// 订阅管理仓储实现。 /// public sealed class EfSubscriptionRepository(TakeoutAppDbContext dbContext, TakeoutLogsDbContext logsContext) : ISubscriptionRepository { #region 订阅查询 /// public async Task FindByIdAsync( long subscriptionId, CancellationToken cancellationToken = default, bool ignoreTenantFilter = false) { var query = ignoreTenantFilter ? dbContext.TenantSubscriptions.IgnoreQueryFilters() : dbContext.TenantSubscriptions; return await query .FirstOrDefaultAsync(s => s.Id == subscriptionId, cancellationToken); } /// public async Task> FindByIdsAsync( IEnumerable subscriptionIds, CancellationToken cancellationToken = default, bool ignoreTenantFilter = false) { var ids = subscriptionIds.ToList(); var query = ignoreTenantFilter ? dbContext.TenantSubscriptions.IgnoreQueryFilters() : dbContext.TenantSubscriptions; return await query .Where(s => ids.Contains(s.Id)) .ToListAsync(cancellationToken); } /// public async Task<(IReadOnlyList Items, int Total)> SearchPagedAsync( SubscriptionSearchFilter filter, CancellationToken cancellationToken = default, bool ignoreTenantFilter = false) { // 1. 构建基础查询 var subscriptionQuery = ignoreTenantFilter ? dbContext.TenantSubscriptions.IgnoreQueryFilters() : dbContext.TenantSubscriptions; var query = subscriptionQuery .AsNoTracking() .Join( dbContext.Tenants, sub => sub.TenantId, tenant => tenant.Id, (sub, tenant) => new { Subscription = sub, Tenant = tenant } ) .Join( dbContext.TenantPackages, combined => combined.Subscription.TenantPackageId, package => package.Id, (combined, package) => new { combined.Subscription, combined.Tenant, Package = package } ) .GroupJoin( dbContext.TenantPackages, combined => combined.Subscription.ScheduledPackageId, scheduledPackage => scheduledPackage.Id, (combined, scheduledPackages) => new { combined.Subscription, combined.Tenant, combined.Package, ScheduledPackage = scheduledPackages.FirstOrDefault() } ); // 2. 应用过滤条件 if (filter.Status.HasValue) { query = query.Where(x => x.Subscription.Status == filter.Status.Value); } if (filter.TenantPackageId.HasValue) { query = query.Where(x => x.Subscription.TenantPackageId == filter.TenantPackageId.Value); } if (filter.TenantId.HasValue) { query = query.Where(x => x.Subscription.TenantId == filter.TenantId.Value); } if (!string.IsNullOrWhiteSpace(filter.TenantKeyword)) { var keyword = filter.TenantKeyword.Trim().ToLower(); query = query.Where(x => x.Tenant.Name.ToLower().Contains(keyword) || x.Tenant.Code.ToLower().Contains(keyword)); } if (filter.ExpiringWithinDays.HasValue) { var expiryDate = DateTime.UtcNow.AddDays(filter.ExpiringWithinDays.Value); query = query.Where(x => x.Subscription.EffectiveTo <= expiryDate && x.Subscription.EffectiveTo >= DateTime.UtcNow); } if (filter.AutoRenew.HasValue) { query = query.Where(x => x.Subscription.AutoRenew == filter.AutoRenew.Value); } // 3. 获取总数 var total = await query.CountAsync(cancellationToken); // 4. 排序和分页 var items = await query .OrderByDescending(x => x.Subscription.CreatedAt) .Skip((filter.Page - 1) * filter.PageSize) .Take(filter.PageSize) .Select(x => new SubscriptionWithRelations { Subscription = x.Subscription, TenantName = x.Tenant.Name, TenantCode = x.Tenant.Code, PackageName = x.Package.Name, ScheduledPackageName = x.ScheduledPackage != null ? x.ScheduledPackage.Name : null }) .ToListAsync(cancellationToken); return (items, total); } /// public async Task GetDetailAsync( long subscriptionId, CancellationToken cancellationToken = default, bool ignoreTenantFilter = false) { var subscriptionQuery = ignoreTenantFilter ? dbContext.TenantSubscriptions.IgnoreQueryFilters() : dbContext.TenantSubscriptions; var result = await subscriptionQuery .AsNoTracking() .Where(s => s.Id == subscriptionId) .Select(s => new { Subscription = s, Tenant = dbContext.Tenants.FirstOrDefault(t => t.Id == s.TenantId), Package = dbContext.TenantPackages.FirstOrDefault(p => p.Id == s.TenantPackageId), ScheduledPackage = s.ScheduledPackageId.HasValue ? dbContext.TenantPackages.FirstOrDefault(p => p.Id == s.ScheduledPackageId) : null }) .FirstOrDefaultAsync(cancellationToken); if (result == null) { return null; } return new SubscriptionDetailInfo { Subscription = result.Subscription, TenantName = result.Tenant?.Name ?? "", TenantCode = result.Tenant?.Code ?? "", Package = result.Package, ScheduledPackage = result.ScheduledPackage }; } /// public async Task> FindByIdsWithTenantAsync( IEnumerable subscriptionIds, CancellationToken cancellationToken = default, bool ignoreTenantFilter = false) { var ids = subscriptionIds.ToList(); var query = ignoreTenantFilter ? dbContext.TenantSubscriptions.IgnoreQueryFilters() : dbContext.TenantSubscriptions; return await query .Where(s => ids.Contains(s.Id)) .Join( dbContext.Tenants, sub => sub.TenantId, tenant => tenant.Id, (sub, tenant) => new SubscriptionWithTenant { Subscription = sub, Tenant = tenant } ) .ToListAsync(cancellationToken); } /// public async Task> FindAutoRenewalCandidatesAsync( DateTime now, DateTime renewalThreshold, CancellationToken cancellationToken = default, bool ignoreTenantFilter = false) { // 1. 查询开启自动续费且即将到期的活跃订阅 var subscriptionQuery = ignoreTenantFilter ? dbContext.TenantSubscriptions.IgnoreQueryFilters() : dbContext.TenantSubscriptions; var query = subscriptionQuery .Where(s => s.Status == SubscriptionStatus.Active && s.AutoRenew && s.EffectiveTo <= renewalThreshold && s.EffectiveTo > now) .Join( dbContext.TenantPackages, subscription => subscription.TenantPackageId, package => package.Id, (subscription, package) => new AutoRenewalCandidate { Subscription = subscription, Package = package }); // 2. 返回候选列表 return await query.ToListAsync(cancellationToken); } /// public async Task> FindRenewalReminderCandidatesAsync( DateTime startOfDay, DateTime endOfDay, CancellationToken cancellationToken = default, bool ignoreTenantFilter = false) { // 1. 查询到期落在指定区间的订阅(且未开启自动续费) var subscriptionQuery = ignoreTenantFilter ? dbContext.TenantSubscriptions.IgnoreQueryFilters() : dbContext.TenantSubscriptions; var query = subscriptionQuery .Where(s => s.Status == SubscriptionStatus.Active && !s.AutoRenew && s.EffectiveTo >= startOfDay && s.EffectiveTo < endOfDay) .Join( dbContext.Tenants, subscription => subscription.TenantId, tenant => tenant.Id, (subscription, tenant) => new { Subscription = subscription, Tenant = tenant }) .Join( dbContext.TenantPackages, combined => combined.Subscription.TenantPackageId, package => package.Id, (combined, package) => new RenewalReminderCandidate { Subscription = combined.Subscription, Tenant = combined.Tenant, Package = package }); // 2. 返回候选列表 return await query.ToListAsync(cancellationToken); } /// public async Task> FindExpiredActiveSubscriptionsAsync( DateTime now, CancellationToken cancellationToken = default, bool ignoreTenantFilter = false) { var query = ignoreTenantFilter ? dbContext.TenantSubscriptions.IgnoreQueryFilters() : dbContext.TenantSubscriptions; // 1. 查询已到期仍为 Active 的订阅 return await query .Where(s => s.Status == SubscriptionStatus.Active && s.EffectiveTo < now) .ToListAsync(cancellationToken); } /// public async Task> FindGracePeriodExpiredSubscriptionsAsync( DateTime now, int gracePeriodDays, CancellationToken cancellationToken = default, bool ignoreTenantFilter = false) { var query = ignoreTenantFilter ? dbContext.TenantSubscriptions.IgnoreQueryFilters() : dbContext.TenantSubscriptions; // 1. 查询宽限期已结束的订阅 return await query .Where(s => s.Status == SubscriptionStatus.GracePeriod && s.EffectiveTo.AddDays(gracePeriodDays) < now) .ToListAsync(cancellationToken); } #endregion #region 套餐查询 /// public async Task FindPackageByIdAsync(long packageId, CancellationToken cancellationToken = default) { return await dbContext.TenantPackages .AsNoTracking() .FirstOrDefaultAsync(p => p.Id == packageId, cancellationToken); } #endregion #region 订阅更新 /// public Task UpdateAsync(TenantSubscription subscription, CancellationToken cancellationToken = default) { dbContext.TenantSubscriptions.Update(subscription); return Task.CompletedTask; } #endregion #region 订阅历史 /// public Task AddHistoryAsync(TenantSubscriptionHistory history, CancellationToken cancellationToken = default) { dbContext.TenantSubscriptionHistories.Add(history); return Task.CompletedTask; } /// public async Task> GetHistoryAsync( long subscriptionId, CancellationToken cancellationToken = default) { return await dbContext.TenantSubscriptionHistories .AsNoTracking() .Where(h => h.TenantSubscriptionId == subscriptionId) .OrderByDescending(h => h.CreatedAt) .Select(h => new SubscriptionHistoryWithPackageNames { History = h, FromPackageName = dbContext.TenantPackages .Where(p => p.Id == h.FromPackageId) .Select(p => p.Name) .FirstOrDefault() ?? "", ToPackageName = dbContext.TenantPackages .Where(p => p.Id == h.ToPackageId) .Select(p => p.Name) .FirstOrDefault() ?? "" }) .ToListAsync(cancellationToken); } #endregion #region 配额使用 /// public async Task> GetQuotaUsagesAsync( long tenantId, CancellationToken cancellationToken = default, bool ignoreTenantFilter = false) { var query = ignoreTenantFilter ? dbContext.TenantQuotaUsages.IgnoreQueryFilters() : dbContext.TenantQuotaUsages; return await query .AsNoTracking() .Where(q => q.TenantId == tenantId) .ToListAsync(cancellationToken); } #endregion #region 通知 /// public Task AddNotificationAsync(TenantNotification notification, CancellationToken cancellationToken = default) { dbContext.TenantNotifications.Add(notification); return Task.CompletedTask; } #endregion #region 操作日志 /// public Task AddOperationLogAsync(OperationLog log, CancellationToken cancellationToken = default) { logsContext.OperationLogs.Add(log); return Task.CompletedTask; } #endregion /// public async Task SaveChangesAsync(CancellationToken cancellationToken = default) { // 1. 保存业务库变更 await dbContext.SaveChangesAsync(cancellationToken); // 2. 保存日志库变更 await logsContext.SaveChangesAsync(cancellationToken); } }