141 lines
5.5 KiB
C#
141 lines
5.5 KiB
C#
using MediatR;
|
|
using TakeoutSaaS.Application.App.Subscriptions.Dto;
|
|
using TakeoutSaaS.Application.App.Subscriptions.Queries;
|
|
using TakeoutSaaS.Application.App.Tenants;
|
|
using TakeoutSaaS.Domain.Tenants.Repositories;
|
|
using TakeoutSaaS.Domain.Tenants.Enums;
|
|
|
|
namespace TakeoutSaaS.Application.App.Subscriptions.Handlers;
|
|
|
|
/// <summary>
|
|
/// 订阅详情查询处理器。
|
|
/// </summary>
|
|
public sealed class GetSubscriptionDetailQueryHandler(
|
|
ISubscriptionRepository subscriptionRepository)
|
|
: IRequestHandler<GetSubscriptionDetailQuery, SubscriptionDetailDto?>
|
|
{
|
|
/// <inheritdoc />
|
|
public async Task<SubscriptionDetailDto?> Handle(GetSubscriptionDetailQuery request, CancellationToken cancellationToken)
|
|
{
|
|
// 1. 查询订阅基础信息
|
|
var detail = await subscriptionRepository.GetDetailAsync(
|
|
request.SubscriptionId,
|
|
cancellationToken,
|
|
includeDeleted: request.IncludeDeleted);
|
|
|
|
if (detail == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
// 2. 查询配额使用情况
|
|
var quotaUsages = await subscriptionRepository.GetQuotaUsagesAsync(
|
|
detail.Subscription.TenantId,
|
|
cancellationToken,
|
|
includeDeleted: request.IncludeDeleted);
|
|
|
|
var quotaUsageDtos = BuildQuotaUsageDtos(detail.Package, quotaUsages);
|
|
|
|
// 3. 查询订阅变更历史(关联套餐信息)
|
|
var histories = await subscriptionRepository.GetHistoryAsync(request.SubscriptionId, cancellationToken);
|
|
|
|
var historyDtos = histories.Select(h => new SubscriptionHistoryDto
|
|
{
|
|
Id = h.History.Id,
|
|
TenantSubscriptionId = h.History.TenantSubscriptionId,
|
|
FromPackageId = h.History.FromPackageId,
|
|
FromPackageName = h.FromPackageName,
|
|
ToPackageId = h.History.ToPackageId,
|
|
ToPackageName = h.ToPackageName,
|
|
ChangeType = h.History.ChangeType,
|
|
EffectiveFrom = h.History.EffectiveFrom,
|
|
EffectiveTo = h.History.EffectiveTo,
|
|
Amount = h.History.Amount,
|
|
Currency = h.History.Currency,
|
|
Notes = h.History.Notes,
|
|
CreatedAt = h.History.CreatedAt
|
|
}).ToList();
|
|
|
|
// 4. 构建返回结果
|
|
return new SubscriptionDetailDto
|
|
{
|
|
Id = detail.Subscription.Id,
|
|
TenantId = detail.Subscription.TenantId,
|
|
TenantName = detail.TenantName,
|
|
TenantCode = detail.TenantCode,
|
|
TenantPackageId = detail.Subscription.TenantPackageId,
|
|
Package = detail.Package?.ToDto(),
|
|
ScheduledPackageId = detail.Subscription.ScheduledPackageId,
|
|
ScheduledPackage = detail.ScheduledPackage?.ToDto(),
|
|
Status = detail.Subscription.Status,
|
|
EffectiveFrom = detail.Subscription.EffectiveFrom,
|
|
EffectiveTo = detail.Subscription.EffectiveTo,
|
|
NextBillingDate = detail.Subscription.NextBillingDate,
|
|
AutoRenew = detail.Subscription.AutoRenew,
|
|
Notes = detail.Subscription.Notes,
|
|
QuotaUsages = quotaUsageDtos,
|
|
ChangeHistory = historyDtos,
|
|
CreatedAt = detail.Subscription.CreatedAt,
|
|
UpdatedAt = detail.Subscription.UpdatedAt
|
|
};
|
|
}
|
|
|
|
private static List<QuotaUsageDto> BuildQuotaUsageDtos(
|
|
TakeoutSaaS.Domain.Tenants.Entities.TenantPackage? package,
|
|
IReadOnlyList<TakeoutSaaS.Domain.Tenants.Entities.TenantQuotaUsage> quotaUsages)
|
|
{
|
|
var usageByType = quotaUsages
|
|
.GroupBy(u => u.QuotaType)
|
|
.ToDictionary(g => g.Key, g => g.First());
|
|
|
|
var baselineTypes = new List<(TenantQuotaType Type, decimal LimitValue)>();
|
|
if (package != null)
|
|
{
|
|
baselineTypes.Add((TenantQuotaType.StoreCount, package.MaxStoreCount.HasValue ? package.MaxStoreCount.Value : 0));
|
|
baselineTypes.Add((TenantQuotaType.AccountCount, package.MaxAccountCount.HasValue ? package.MaxAccountCount.Value : 0));
|
|
baselineTypes.Add((TenantQuotaType.Storage, package.MaxStorageGb.HasValue ? package.MaxStorageGb.Value : 0));
|
|
baselineTypes.Add((TenantQuotaType.SmsCredits, package.MaxSmsCredits.HasValue ? package.MaxSmsCredits.Value : 0));
|
|
baselineTypes.Add((TenantQuotaType.DeliveryOrders, package.MaxDeliveryOrders.HasValue ? package.MaxDeliveryOrders.Value : 0));
|
|
}
|
|
|
|
var results = new List<QuotaUsageDto>();
|
|
|
|
foreach (var (type, limitValue) in baselineTypes)
|
|
{
|
|
usageByType.TryGetValue(type, out var usage);
|
|
results.Add(new QuotaUsageDto
|
|
{
|
|
Id = usage?.Id ?? 0,
|
|
QuotaType = type,
|
|
LimitValue = limitValue,
|
|
UsedValue = usage?.UsedValue ?? 0,
|
|
ResetCycle = usage?.ResetCycle,
|
|
LastResetAt = usage?.LastResetAt
|
|
});
|
|
}
|
|
|
|
// 补充套餐字段未覆盖的配额使用项(例如营销槽位等扩展配额)
|
|
foreach (var usage in usageByType.Values)
|
|
{
|
|
if (baselineTypes.Any(x => x.Type == usage.QuotaType))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
results.Add(new QuotaUsageDto
|
|
{
|
|
Id = usage.Id,
|
|
QuotaType = usage.QuotaType,
|
|
LimitValue = usage.LimitValue,
|
|
UsedValue = usage.UsedValue,
|
|
ResetCycle = usage.ResetCycle,
|
|
LastResetAt = usage.LastResetAt
|
|
});
|
|
}
|
|
|
|
return results
|
|
.OrderBy(x => (int)x.QuotaType)
|
|
.ToList();
|
|
}
|
|
}
|