117 lines
3.8 KiB
C#
117 lines
3.8 KiB
C#
using Microsoft.EntityFrameworkCore;
|
|
using TakeoutSaaS.Domain.Tenants.Entities;
|
|
using TakeoutSaaS.Domain.Tenants.Enums;
|
|
using TakeoutSaaS.Domain.Tenants.Repositories;
|
|
using TakeoutSaaS.Infrastructure.App.Persistence;
|
|
|
|
namespace TakeoutSaaS.Infrastructure.App.Repositories;
|
|
|
|
/// <summary>
|
|
/// 统计数据仓储实现。
|
|
/// </summary>
|
|
public sealed class EfStatisticsRepository(TakeoutAdminDbContext dbContext) : IStatisticsRepository
|
|
{
|
|
#region 订阅统计
|
|
|
|
/// <inheritdoc />
|
|
public async Task<IReadOnlyList<TenantSubscription>> GetAllSubscriptionsAsync(CancellationToken cancellationToken = default)
|
|
{
|
|
return await dbContext.TenantSubscriptions
|
|
.AsNoTracking()
|
|
.ToListAsync(cancellationToken);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public async Task<IReadOnlyList<ExpiringSubscriptionInfo>> GetExpiringSubscriptionsAsync(
|
|
int daysAhead,
|
|
bool onlyWithoutAutoRenew,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
var now = DateTime.UtcNow;
|
|
var targetDate = now.AddDays(daysAhead);
|
|
|
|
// 构建基础查询
|
|
var query = dbContext.TenantSubscriptions
|
|
.AsNoTracking()
|
|
.Where(s => s.Status == SubscriptionStatus.Active
|
|
&& s.EffectiveTo >= now
|
|
&& s.EffectiveTo <= targetDate);
|
|
|
|
// 如果只查询未开启自动续费的
|
|
if (onlyWithoutAutoRenew)
|
|
{
|
|
query = query.Where(s => !s.AutoRenew);
|
|
}
|
|
|
|
// 连接租户和套餐信息
|
|
var result = await query
|
|
.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 ExpiringSubscriptionInfo
|
|
{
|
|
Subscription = combined.Subscription,
|
|
TenantName = combined.Tenant.Name,
|
|
PackageName = package.Name
|
|
}
|
|
)
|
|
.OrderBy(x => x.Subscription.EffectiveTo)
|
|
.ToListAsync(cancellationToken);
|
|
|
|
return result;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region 收入统计
|
|
|
|
/// <inheritdoc />
|
|
public async Task<IReadOnlyList<TenantBillingStatement>> GetPaidBillsAsync(CancellationToken cancellationToken = default)
|
|
{
|
|
return await dbContext.TenantBillingStatements
|
|
.AsNoTracking()
|
|
.Where(b => b.Status == TenantBillingStatus.Paid)
|
|
.ToListAsync(cancellationToken);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region 配额使用排行
|
|
|
|
/// <inheritdoc />
|
|
public async Task<IReadOnlyList<QuotaUsageRankInfo>> GetQuotaUsageRankingAsync(
|
|
TenantQuotaType quotaType,
|
|
int topN,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
return await dbContext.TenantQuotaUsages
|
|
.AsNoTracking()
|
|
.Where(q => q.QuotaType == quotaType && q.LimitValue > 0)
|
|
.Join(
|
|
dbContext.Tenants,
|
|
quota => quota.TenantId,
|
|
tenant => tenant.Id,
|
|
(quota, tenant) => new QuotaUsageRankInfo
|
|
{
|
|
TenantId = quota.TenantId,
|
|
TenantName = tenant.Name,
|
|
UsedValue = quota.UsedValue,
|
|
LimitValue = quota.LimitValue,
|
|
UsagePercentage = quota.LimitValue > 0 ? (quota.UsedValue / quota.LimitValue * 100) : 0
|
|
}
|
|
)
|
|
.OrderByDescending(x => x.UsagePercentage)
|
|
.Take(topN)
|
|
.ToListAsync(cancellationToken);
|
|
}
|
|
|
|
#endregion
|
|
}
|