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,133 @@
|
||||
using MediatR;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Text.Json;
|
||||
using TakeoutSaaS.Application.App.Subscriptions.Commands;
|
||||
using TakeoutSaaS.Domain.Tenants.Entities;
|
||||
using TakeoutSaaS.Domain.Tenants.Enums;
|
||||
using TakeoutSaaS.Domain.Tenants.Repositories;
|
||||
using TakeoutSaaS.Shared.Abstractions.Ids;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Subscriptions.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 批量延期订阅命令处理器。
|
||||
/// </summary>
|
||||
public sealed class BatchExtendSubscriptionsCommandHandler(
|
||||
ISubscriptionRepository subscriptionRepository,
|
||||
IIdGenerator idGenerator,
|
||||
ILogger<BatchExtendSubscriptionsCommandHandler> logger)
|
||||
: IRequestHandler<BatchExtendSubscriptionsCommand, BatchExtendResult>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<BatchExtendResult> Handle(BatchExtendSubscriptionsCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var successCount = 0;
|
||||
var failures = new List<BatchFailureItem>();
|
||||
|
||||
// 验证参数
|
||||
if (!request.DurationDays.HasValue && !request.DurationMonths.HasValue)
|
||||
{
|
||||
throw new InvalidOperationException("必须指定延期天数或延期月数");
|
||||
}
|
||||
|
||||
// 计算延期时间
|
||||
var extendDays = request.DurationDays ?? 0;
|
||||
var extendMonths = request.DurationMonths ?? 0;
|
||||
|
||||
// 查询所有订阅
|
||||
var subscriptions = await subscriptionRepository.FindByIdsAsync(request.SubscriptionIds, cancellationToken);
|
||||
|
||||
foreach (var subscriptionId in request.SubscriptionIds)
|
||||
{
|
||||
try
|
||||
{
|
||||
var subscription = subscriptions.FirstOrDefault(s => s.Id == subscriptionId);
|
||||
if (subscription == null)
|
||||
{
|
||||
failures.Add(new BatchFailureItem
|
||||
{
|
||||
SubscriptionId = subscriptionId,
|
||||
Reason = "订阅不存在"
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// 记录原始到期时间
|
||||
var originalEffectiveTo = subscription.EffectiveTo;
|
||||
|
||||
// 计算新的到期时间
|
||||
var newEffectiveTo = subscription.EffectiveTo;
|
||||
if (extendMonths > 0)
|
||||
{
|
||||
newEffectiveTo = newEffectiveTo.AddMonths(extendMonths);
|
||||
}
|
||||
if (extendDays > 0)
|
||||
{
|
||||
newEffectiveTo = newEffectiveTo.AddDays(extendDays);
|
||||
}
|
||||
|
||||
subscription.EffectiveTo = newEffectiveTo;
|
||||
|
||||
// 更新备注
|
||||
if (!string.IsNullOrWhiteSpace(request.Notes))
|
||||
{
|
||||
subscription.Notes = request.Notes;
|
||||
}
|
||||
|
||||
// 记录变更历史
|
||||
var history = new TenantSubscriptionHistory
|
||||
{
|
||||
Id = idGenerator.NextId(),
|
||||
TenantId = subscription.TenantId,
|
||||
TenantSubscriptionId = subscription.Id,
|
||||
FromPackageId = subscription.TenantPackageId,
|
||||
ToPackageId = subscription.TenantPackageId,
|
||||
ChangeType = SubscriptionChangeType.Renew,
|
||||
EffectiveFrom = originalEffectiveTo,
|
||||
EffectiveTo = newEffectiveTo,
|
||||
Amount = null,
|
||||
Currency = null,
|
||||
Notes = request.Notes ?? $"批量延期: {(extendMonths > 0 ? $"{extendMonths}个月" : "")}{(extendDays > 0 ? $"{extendDays}天" : "")}"
|
||||
};
|
||||
|
||||
await subscriptionRepository.AddHistoryAsync(history, cancellationToken);
|
||||
await subscriptionRepository.UpdateAsync(subscription, cancellationToken);
|
||||
successCount++;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "批量延期订阅失败: SubscriptionId={SubscriptionId}", subscriptionId);
|
||||
failures.Add(new BatchFailureItem
|
||||
{
|
||||
SubscriptionId = subscriptionId,
|
||||
Reason = $"处理失败: {ex.Message}"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 记录操作日志
|
||||
var operationLog = new OperationLog
|
||||
{
|
||||
Id = idGenerator.NextId(),
|
||||
OperationType = "BatchExtend",
|
||||
TargetType = "Subscription",
|
||||
TargetIds = JsonSerializer.Serialize(request.SubscriptionIds),
|
||||
Parameters = JsonSerializer.Serialize(new { request.DurationDays, request.DurationMonths, request.Notes }),
|
||||
Result = JsonSerializer.Serialize(new { SuccessCount = successCount, FailureCount = failures.Count }),
|
||||
Success = failures.Count == 0,
|
||||
CreatedAt = DateTime.UtcNow
|
||||
};
|
||||
|
||||
await subscriptionRepository.AddOperationLogAsync(operationLog, cancellationToken);
|
||||
|
||||
// 保存所有更改
|
||||
await subscriptionRepository.SaveChangesAsync(cancellationToken);
|
||||
|
||||
return new BatchExtendResult
|
||||
{
|
||||
SuccessCount = successCount,
|
||||
FailureCount = failures.Count,
|
||||
Failures = failures
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
using MediatR;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Text.Json;
|
||||
using TakeoutSaaS.Application.App.Subscriptions.Commands;
|
||||
using TakeoutSaaS.Domain.Tenants.Entities;
|
||||
using TakeoutSaaS.Domain.Tenants.Enums;
|
||||
using TakeoutSaaS.Domain.Tenants.Repositories;
|
||||
using TakeoutSaaS.Shared.Abstractions.Ids;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Subscriptions.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 批量发送续费提醒命令处理器。
|
||||
/// </summary>
|
||||
public sealed class BatchSendReminderCommandHandler(
|
||||
ISubscriptionRepository subscriptionRepository,
|
||||
IIdGenerator idGenerator,
|
||||
ILogger<BatchSendReminderCommandHandler> logger)
|
||||
: IRequestHandler<BatchSendReminderCommand, BatchSendReminderResult>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<BatchSendReminderResult> Handle(BatchSendReminderCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var successCount = 0;
|
||||
var failures = new List<BatchFailureItem>();
|
||||
|
||||
// 查询所有订阅及租户信息
|
||||
var subscriptions = await subscriptionRepository.FindByIdsWithTenantAsync(
|
||||
request.SubscriptionIds,
|
||||
cancellationToken);
|
||||
|
||||
foreach (var subscriptionId in request.SubscriptionIds)
|
||||
{
|
||||
try
|
||||
{
|
||||
var item = subscriptions.FirstOrDefault(s => s.Subscription.Id == subscriptionId);
|
||||
if (item == null)
|
||||
{
|
||||
failures.Add(new BatchFailureItem
|
||||
{
|
||||
SubscriptionId = subscriptionId,
|
||||
Reason = "订阅不存在"
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// 创建通知记录
|
||||
var notification = new TenantNotification
|
||||
{
|
||||
Id = idGenerator.NextId(),
|
||||
TenantId = item.Subscription.TenantId,
|
||||
Title = "续费提醒",
|
||||
Message = request.ReminderContent,
|
||||
Severity = TenantNotificationSeverity.Warning,
|
||||
Channel = TenantNotificationChannel.InApp,
|
||||
SentAt = DateTime.UtcNow,
|
||||
ReadAt = null,
|
||||
CreatedAt = DateTime.UtcNow
|
||||
};
|
||||
|
||||
await subscriptionRepository.AddNotificationAsync(notification, cancellationToken);
|
||||
successCount++;
|
||||
|
||||
logger.LogInformation(
|
||||
"发送续费提醒: SubscriptionId={SubscriptionId}, TenantId={TenantId}, TenantName={TenantName}",
|
||||
subscriptionId, item.Subscription.TenantId, item.Tenant.Name);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "发送续费提醒失败: SubscriptionId={SubscriptionId}", subscriptionId);
|
||||
failures.Add(new BatchFailureItem
|
||||
{
|
||||
SubscriptionId = subscriptionId,
|
||||
Reason = $"发送失败: {ex.Message}"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 记录操作日志
|
||||
var operationLog = new OperationLog
|
||||
{
|
||||
Id = idGenerator.NextId(),
|
||||
OperationType = "BatchRemind",
|
||||
TargetType = "Subscription",
|
||||
TargetIds = JsonSerializer.Serialize(request.SubscriptionIds),
|
||||
Parameters = JsonSerializer.Serialize(new { request.ReminderContent }),
|
||||
Result = JsonSerializer.Serialize(new { SuccessCount = successCount, FailureCount = failures.Count }),
|
||||
Success = failures.Count == 0,
|
||||
CreatedAt = DateTime.UtcNow
|
||||
};
|
||||
|
||||
await subscriptionRepository.AddOperationLogAsync(operationLog, cancellationToken);
|
||||
|
||||
// 保存所有更改
|
||||
await subscriptionRepository.SaveChangesAsync(cancellationToken);
|
||||
|
||||
return new BatchSendReminderResult
|
||||
{
|
||||
SuccessCount = successCount,
|
||||
FailureCount = failures.Count,
|
||||
Failures = failures
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Subscriptions.Commands;
|
||||
using TakeoutSaaS.Application.App.Subscriptions.Dto;
|
||||
using TakeoutSaaS.Application.App.Subscriptions.Queries;
|
||||
using TakeoutSaaS.Domain.Tenants.Entities;
|
||||
using TakeoutSaaS.Domain.Tenants.Enums;
|
||||
using TakeoutSaaS.Domain.Tenants.Repositories;
|
||||
using TakeoutSaaS.Shared.Abstractions.Ids;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Subscriptions.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 变更套餐命令处理器。
|
||||
/// </summary>
|
||||
public sealed class ChangeSubscriptionPlanCommandHandler(
|
||||
ISubscriptionRepository subscriptionRepository,
|
||||
IIdGenerator idGenerator,
|
||||
IMediator mediator)
|
||||
: IRequestHandler<ChangeSubscriptionPlanCommand, SubscriptionDetailDto?>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<SubscriptionDetailDto?> Handle(ChangeSubscriptionPlanCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 查询订阅
|
||||
var subscription = await subscriptionRepository.FindByIdAsync(request.SubscriptionId, cancellationToken);
|
||||
|
||||
if (subscription == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// 2. 记录原套餐ID
|
||||
var previousPackageId = subscription.TenantPackageId;
|
||||
|
||||
// 3. 根据是否立即生效更新订阅
|
||||
if (request.Immediate)
|
||||
{
|
||||
// 立即生效:直接更新当前套餐
|
||||
subscription.TenantPackageId = request.TargetPackageId;
|
||||
subscription.ScheduledPackageId = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 下周期生效:设置排期套餐
|
||||
subscription.ScheduledPackageId = request.TargetPackageId;
|
||||
}
|
||||
|
||||
// 4. 更新备注
|
||||
if (!string.IsNullOrWhiteSpace(request.Notes))
|
||||
{
|
||||
subscription.Notes = request.Notes;
|
||||
}
|
||||
|
||||
// 5. 判断变更类型(升级或降级)
|
||||
var fromPackage = await subscriptionRepository.FindPackageByIdAsync(previousPackageId, cancellationToken);
|
||||
var toPackage = await subscriptionRepository.FindPackageByIdAsync(request.TargetPackageId, cancellationToken);
|
||||
|
||||
var changeType = SubscriptionChangeType.Upgrade;
|
||||
if (fromPackage != null && toPackage != null)
|
||||
{
|
||||
// 简单根据价格判断升降级
|
||||
if (toPackage.MonthlyPrice < fromPackage.MonthlyPrice)
|
||||
{
|
||||
changeType = SubscriptionChangeType.Downgrade;
|
||||
}
|
||||
}
|
||||
|
||||
// 6. 记录变更历史
|
||||
var history = new TenantSubscriptionHistory
|
||||
{
|
||||
Id = idGenerator.NextId(),
|
||||
TenantId = subscription.TenantId,
|
||||
TenantSubscriptionId = subscription.Id,
|
||||
FromPackageId = previousPackageId,
|
||||
ToPackageId = request.TargetPackageId,
|
||||
ChangeType = changeType,
|
||||
EffectiveFrom = request.Immediate ? DateTime.UtcNow : subscription.EffectiveTo,
|
||||
EffectiveTo = subscription.EffectiveTo,
|
||||
Amount = null,
|
||||
Currency = null,
|
||||
Notes = request.Notes ?? (request.Immediate ? "套餐立即变更" : "套餐排期变更")
|
||||
};
|
||||
|
||||
await subscriptionRepository.AddHistoryAsync(history, cancellationToken);
|
||||
|
||||
// 7. 保存更改
|
||||
await subscriptionRepository.UpdateAsync(subscription, cancellationToken);
|
||||
await subscriptionRepository.SaveChangesAsync(cancellationToken);
|
||||
|
||||
// 8. 返回更新后的详情
|
||||
return await mediator.Send(new GetSubscriptionDetailQuery { SubscriptionId = subscription.Id }, cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Subscriptions.Commands;
|
||||
using TakeoutSaaS.Application.App.Subscriptions.Dto;
|
||||
using TakeoutSaaS.Application.App.Subscriptions.Queries;
|
||||
using TakeoutSaaS.Domain.Tenants.Entities;
|
||||
using TakeoutSaaS.Domain.Tenants.Enums;
|
||||
using TakeoutSaaS.Domain.Tenants.Repositories;
|
||||
using TakeoutSaaS.Shared.Abstractions.Ids;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Subscriptions.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 延期订阅命令处理器。
|
||||
/// </summary>
|
||||
public sealed class ExtendSubscriptionCommandHandler(
|
||||
ISubscriptionRepository subscriptionRepository,
|
||||
IIdGenerator idGenerator,
|
||||
IMediator mediator)
|
||||
: IRequestHandler<ExtendSubscriptionCommand, SubscriptionDetailDto?>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<SubscriptionDetailDto?> Handle(ExtendSubscriptionCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 查询订阅
|
||||
var subscription = await subscriptionRepository.FindByIdAsync(request.SubscriptionId, cancellationToken);
|
||||
|
||||
if (subscription == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// 2. 计算新的到期时间(从当前到期时间延长)
|
||||
var originalEffectiveTo = subscription.EffectiveTo;
|
||||
subscription.EffectiveTo = subscription.EffectiveTo.AddMonths(request.DurationMonths);
|
||||
|
||||
// 3. 更新备注
|
||||
if (!string.IsNullOrWhiteSpace(request.Notes))
|
||||
{
|
||||
subscription.Notes = request.Notes;
|
||||
}
|
||||
|
||||
// 4. 记录变更历史(使用 Renew 类型表示延期)
|
||||
var history = new TenantSubscriptionHistory
|
||||
{
|
||||
Id = idGenerator.NextId(),
|
||||
TenantId = subscription.TenantId,
|
||||
TenantSubscriptionId = subscription.Id,
|
||||
FromPackageId = subscription.TenantPackageId,
|
||||
ToPackageId = subscription.TenantPackageId,
|
||||
ChangeType = SubscriptionChangeType.Renew,
|
||||
EffectiveFrom = originalEffectiveTo,
|
||||
EffectiveTo = subscription.EffectiveTo,
|
||||
Amount = null,
|
||||
Currency = null,
|
||||
Notes = request.Notes ?? $"延期 {request.DurationMonths} 个月"
|
||||
};
|
||||
|
||||
await subscriptionRepository.AddHistoryAsync(history, cancellationToken);
|
||||
|
||||
// 5. 保存更改
|
||||
await subscriptionRepository.UpdateAsync(subscription, cancellationToken);
|
||||
await subscriptionRepository.SaveChangesAsync(cancellationToken);
|
||||
|
||||
// 6. 返回更新后的详情
|
||||
return await mediator.Send(new GetSubscriptionDetailQuery { SubscriptionId = subscription.Id }, cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Subscriptions.Dto;
|
||||
using TakeoutSaaS.Application.App.Subscriptions.Queries;
|
||||
using TakeoutSaaS.Application.App.Tenants;
|
||||
using TakeoutSaaS.Domain.Tenants.Repositories;
|
||||
|
||||
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);
|
||||
|
||||
if (detail == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// 2. 查询配额使用情况
|
||||
var quotaUsages = await subscriptionRepository.GetQuotaUsagesAsync(
|
||||
detail.Subscription.TenantId,
|
||||
cancellationToken);
|
||||
|
||||
var quotaUsageDtos = quotaUsages.Select(q => new QuotaUsageDto
|
||||
{
|
||||
Id = q.Id,
|
||||
QuotaType = q.QuotaType,
|
||||
LimitValue = q.LimitValue,
|
||||
UsedValue = q.UsedValue,
|
||||
ResetCycle = q.ResetCycle,
|
||||
LastResetAt = q.LastResetAt
|
||||
}).ToList();
|
||||
|
||||
// 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
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Subscriptions.Dto;
|
||||
using TakeoutSaaS.Application.App.Subscriptions.Queries;
|
||||
using TakeoutSaaS.Domain.Tenants.Repositories;
|
||||
using TakeoutSaaS.Shared.Abstractions.Results;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Subscriptions.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 订阅分页查询处理器。
|
||||
/// </summary>
|
||||
public sealed class GetSubscriptionListQueryHandler(ISubscriptionRepository subscriptionRepository)
|
||||
: IRequestHandler<GetSubscriptionListQuery, PagedResult<SubscriptionListDto>>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<PagedResult<SubscriptionListDto>> Handle(GetSubscriptionListQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 构建查询过滤条件
|
||||
var filter = new SubscriptionSearchFilter
|
||||
{
|
||||
Status = request.Status,
|
||||
TenantPackageId = request.TenantPackageId,
|
||||
TenantId = request.TenantId,
|
||||
TenantKeyword = request.TenantKeyword,
|
||||
ExpiringWithinDays = request.ExpiringWithinDays,
|
||||
AutoRenew = request.AutoRenew,
|
||||
Page = request.Page,
|
||||
PageSize = request.PageSize
|
||||
};
|
||||
|
||||
// 2. 执行分页查询
|
||||
var (items, total) = await subscriptionRepository.SearchPagedAsync(filter, cancellationToken);
|
||||
|
||||
// 3. 映射为 DTO
|
||||
var dtos = items.Select(x => new SubscriptionListDto
|
||||
{
|
||||
Id = x.Subscription.Id,
|
||||
TenantId = x.Subscription.TenantId,
|
||||
TenantName = x.TenantName,
|
||||
TenantCode = x.TenantCode,
|
||||
TenantPackageId = x.Subscription.TenantPackageId,
|
||||
PackageName = x.PackageName,
|
||||
ScheduledPackageId = x.Subscription.ScheduledPackageId,
|
||||
ScheduledPackageName = x.ScheduledPackageName,
|
||||
Status = x.Subscription.Status,
|
||||
EffectiveFrom = x.Subscription.EffectiveFrom,
|
||||
EffectiveTo = x.Subscription.EffectiveTo,
|
||||
NextBillingDate = x.Subscription.NextBillingDate,
|
||||
AutoRenew = x.Subscription.AutoRenew,
|
||||
Notes = x.Subscription.Notes,
|
||||
CreatedAt = x.Subscription.CreatedAt,
|
||||
UpdatedAt = x.Subscription.UpdatedAt
|
||||
}).ToList();
|
||||
|
||||
// 4. 返回分页结果
|
||||
return new PagedResult<SubscriptionListDto>(dtos, request.Page, request.PageSize, total);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Subscriptions.Commands;
|
||||
using TakeoutSaaS.Application.App.Subscriptions.Dto;
|
||||
using TakeoutSaaS.Application.App.Subscriptions.Queries;
|
||||
using TakeoutSaaS.Domain.Tenants.Repositories;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Subscriptions.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 更新订阅基础信息命令处理器。
|
||||
/// </summary>
|
||||
public sealed class UpdateSubscriptionCommandHandler(
|
||||
ISubscriptionRepository subscriptionRepository,
|
||||
IMediator mediator)
|
||||
: IRequestHandler<UpdateSubscriptionCommand, SubscriptionDetailDto?>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<SubscriptionDetailDto?> Handle(UpdateSubscriptionCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 查询订阅
|
||||
var subscription = await subscriptionRepository.FindByIdAsync(request.SubscriptionId, cancellationToken);
|
||||
|
||||
if (subscription == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// 2. 更新字段
|
||||
if (request.AutoRenew.HasValue)
|
||||
{
|
||||
subscription.AutoRenew = request.AutoRenew.Value;
|
||||
}
|
||||
|
||||
if (request.Notes != null)
|
||||
{
|
||||
subscription.Notes = request.Notes;
|
||||
}
|
||||
|
||||
// 3. 保存更改
|
||||
await subscriptionRepository.UpdateAsync(subscription, cancellationToken);
|
||||
await subscriptionRepository.SaveChangesAsync(cancellationToken);
|
||||
|
||||
// 4. 返回更新后的详情
|
||||
return await mediator.Send(new GetSubscriptionDetailQuery { SubscriptionId = subscription.Id }, cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Subscriptions.Commands;
|
||||
using TakeoutSaaS.Application.App.Subscriptions.Dto;
|
||||
using TakeoutSaaS.Application.App.Subscriptions.Queries;
|
||||
using TakeoutSaaS.Domain.Tenants.Repositories;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Subscriptions.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 更新订阅状态命令处理器。
|
||||
/// </summary>
|
||||
public sealed class UpdateSubscriptionStatusCommandHandler(
|
||||
ISubscriptionRepository subscriptionRepository,
|
||||
IMediator mediator)
|
||||
: IRequestHandler<UpdateSubscriptionStatusCommand, SubscriptionDetailDto?>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<SubscriptionDetailDto?> Handle(UpdateSubscriptionStatusCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 查询订阅
|
||||
var subscription = await subscriptionRepository.FindByIdAsync(request.SubscriptionId, cancellationToken);
|
||||
|
||||
if (subscription == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// 2. 更新状态
|
||||
subscription.Status = request.Status;
|
||||
|
||||
// 3. 更新备注
|
||||
if (!string.IsNullOrWhiteSpace(request.Notes))
|
||||
{
|
||||
subscription.Notes = request.Notes;
|
||||
}
|
||||
|
||||
// 4. 保存更改
|
||||
await subscriptionRepository.UpdateAsync(subscription, cancellationToken);
|
||||
await subscriptionRepository.SaveChangesAsync(cancellationToken);
|
||||
|
||||
// 5. 返回更新后的详情
|
||||
return await mediator.Send(new GetSubscriptionDetailQuery { SubscriptionId = subscription.Id }, cancellationToken);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user