feat: 新增配额包/支付相关实体与迁移
App:新增 operation_logs/quota_packages/tenant_payments/tenant_quota_package_purchases 表 Identity:修正 Avatar 字段类型(varchar(256)->text),保持现有数据不变
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
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(TakeoutAppDbContext 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
|
||||
}
|
||||
Reference in New Issue
Block a user