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,54 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.QuotaPackages.Commands;
|
||||
using TakeoutSaaS.Application.App.QuotaPackages.Dto;
|
||||
using TakeoutSaaS.Domain.Tenants.Entities;
|
||||
using TakeoutSaaS.Domain.Tenants.Repositories;
|
||||
using TakeoutSaaS.Shared.Abstractions.Ids;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.QuotaPackages.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 创建配额包命令处理器。
|
||||
/// </summary>
|
||||
public sealed class CreateQuotaPackageCommandHandler(
|
||||
IQuotaPackageRepository quotaPackageRepository,
|
||||
IIdGenerator idGenerator)
|
||||
: IRequestHandler<CreateQuotaPackageCommand, QuotaPackageDto>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<QuotaPackageDto> Handle(CreateQuotaPackageCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 创建配额包实体
|
||||
var quotaPackage = new QuotaPackage
|
||||
{
|
||||
Id = idGenerator.NextId(),
|
||||
Name = request.Name,
|
||||
QuotaType = request.QuotaType,
|
||||
QuotaValue = request.QuotaValue,
|
||||
Price = request.Price,
|
||||
IsActive = request.IsActive,
|
||||
SortOrder = request.SortOrder,
|
||||
Description = request.Description,
|
||||
CreatedAt = DateTime.UtcNow
|
||||
};
|
||||
|
||||
// 2. 保存到数据库
|
||||
await quotaPackageRepository.AddAsync(quotaPackage, cancellationToken);
|
||||
await quotaPackageRepository.SaveChangesAsync(cancellationToken);
|
||||
|
||||
// 3. 返回 DTO
|
||||
return new QuotaPackageDto
|
||||
{
|
||||
Id = quotaPackage.Id,
|
||||
Name = quotaPackage.Name,
|
||||
QuotaType = quotaPackage.QuotaType,
|
||||
QuotaValue = quotaPackage.QuotaValue,
|
||||
Price = quotaPackage.Price,
|
||||
IsActive = quotaPackage.IsActive,
|
||||
SortOrder = quotaPackage.SortOrder,
|
||||
Description = quotaPackage.Description,
|
||||
CreatedAt = quotaPackage.CreatedAt,
|
||||
UpdatedAt = quotaPackage.UpdatedAt
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.QuotaPackages.Commands;
|
||||
using TakeoutSaaS.Domain.Tenants.Repositories;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.QuotaPackages.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 删除配额包命令处理器。
|
||||
/// </summary>
|
||||
public sealed class DeleteQuotaPackageCommandHandler(IQuotaPackageRepository quotaPackageRepository)
|
||||
: IRequestHandler<DeleteQuotaPackageCommand, bool>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<bool> Handle(DeleteQuotaPackageCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 软删除配额包
|
||||
var deleted = await quotaPackageRepository.SoftDeleteAsync(request.QuotaPackageId, cancellationToken);
|
||||
|
||||
if (!deleted)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 保存变更
|
||||
await quotaPackageRepository.SaveChangesAsync(cancellationToken);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.QuotaPackages.Dto;
|
||||
using TakeoutSaaS.Application.App.QuotaPackages.Queries;
|
||||
using TakeoutSaaS.Domain.Tenants.Repositories;
|
||||
using TakeoutSaaS.Shared.Abstractions.Results;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.QuotaPackages.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 获取配额包列表查询处理器。
|
||||
/// </summary>
|
||||
public sealed class GetQuotaPackageListQueryHandler(IQuotaPackageRepository quotaPackageRepository)
|
||||
: IRequestHandler<GetQuotaPackageListQuery, PagedResult<QuotaPackageListDto>>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<PagedResult<QuotaPackageListDto>> Handle(GetQuotaPackageListQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 分页查询
|
||||
var (items, total) = await quotaPackageRepository.SearchPagedAsync(
|
||||
request.QuotaType,
|
||||
request.IsActive,
|
||||
request.Page,
|
||||
request.PageSize,
|
||||
cancellationToken);
|
||||
|
||||
// 2. 映射为 DTO
|
||||
var dtos = items.Select(x => new QuotaPackageListDto
|
||||
{
|
||||
Id = x.Id,
|
||||
Name = x.Name,
|
||||
QuotaType = x.QuotaType,
|
||||
QuotaValue = x.QuotaValue,
|
||||
Price = x.Price,
|
||||
IsActive = x.IsActive,
|
||||
SortOrder = x.SortOrder
|
||||
}).ToList();
|
||||
|
||||
// 3. 返回分页结果
|
||||
return new PagedResult<QuotaPackageListDto>(dtos, request.Page, request.PageSize, total);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.QuotaPackages.Dto;
|
||||
using TakeoutSaaS.Application.App.QuotaPackages.Queries;
|
||||
using TakeoutSaaS.Domain.Tenants.Repositories;
|
||||
using TakeoutSaaS.Shared.Abstractions.Results;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.QuotaPackages.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 获取租户配额购买记录查询处理器。
|
||||
/// </summary>
|
||||
public sealed class GetTenantQuotaPurchasesQueryHandler(IQuotaPackageRepository quotaPackageRepository)
|
||||
: IRequestHandler<GetTenantQuotaPurchasesQuery, PagedResult<TenantQuotaPurchaseDto>>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<PagedResult<TenantQuotaPurchaseDto>> Handle(GetTenantQuotaPurchasesQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 分页查询购买记录
|
||||
var (items, total) = await quotaPackageRepository.GetPurchasesPagedAsync(
|
||||
request.TenantId,
|
||||
request.Page,
|
||||
request.PageSize,
|
||||
cancellationToken);
|
||||
|
||||
// 2. 映射为 DTO
|
||||
var dtos = items.Select(x => new TenantQuotaPurchaseDto
|
||||
{
|
||||
Id = x.Purchase.Id,
|
||||
TenantId = x.Purchase.TenantId,
|
||||
QuotaPackageId = x.Purchase.QuotaPackageId,
|
||||
QuotaPackageName = x.Package.Name,
|
||||
QuotaType = x.Package.QuotaType,
|
||||
QuotaValue = x.Purchase.QuotaValue,
|
||||
Price = x.Purchase.Price,
|
||||
PurchasedAt = x.Purchase.PurchasedAt,
|
||||
ExpiredAt = x.Purchase.ExpiredAt,
|
||||
Notes = x.Purchase.Notes
|
||||
}).ToList();
|
||||
|
||||
// 3. 返回分页结果
|
||||
return new PagedResult<TenantQuotaPurchaseDto>(dtos, request.Page, request.PageSize, total);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.QuotaPackages.Dto;
|
||||
using TakeoutSaaS.Application.App.QuotaPackages.Queries;
|
||||
using TakeoutSaaS.Domain.Tenants.Repositories;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.QuotaPackages.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 获取租户配额使用情况查询处理器。
|
||||
/// </summary>
|
||||
public sealed class GetTenantQuotaUsageQueryHandler(IQuotaPackageRepository quotaPackageRepository)
|
||||
: IRequestHandler<GetTenantQuotaUsageQuery, IReadOnlyList<TenantQuotaUsageDto>>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<IReadOnlyList<TenantQuotaUsageDto>> Handle(GetTenantQuotaUsageQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 查询配额使用情况
|
||||
var items = await quotaPackageRepository.GetUsageByTenantAsync(
|
||||
request.TenantId,
|
||||
request.QuotaType,
|
||||
cancellationToken);
|
||||
|
||||
// 2. 映射为 DTO
|
||||
return items.Select(x => new TenantQuotaUsageDto
|
||||
{
|
||||
TenantId = x.TenantId,
|
||||
QuotaType = x.QuotaType,
|
||||
LimitValue = x.LimitValue,
|
||||
UsedValue = x.UsedValue,
|
||||
RemainingValue = x.LimitValue - x.UsedValue,
|
||||
ResetCycle = x.ResetCycle,
|
||||
LastResetAt = x.LastResetAt
|
||||
}).ToList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.QuotaPackages.Commands;
|
||||
using TakeoutSaaS.Application.App.QuotaPackages.Dto;
|
||||
using TakeoutSaaS.Domain.Tenants.Entities;
|
||||
using TakeoutSaaS.Domain.Tenants.Repositories;
|
||||
using TakeoutSaaS.Shared.Abstractions.Ids;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.QuotaPackages.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 购买配额包命令处理器。
|
||||
/// </summary>
|
||||
public sealed class PurchaseQuotaPackageCommandHandler(
|
||||
IQuotaPackageRepository quotaPackageRepository,
|
||||
IIdGenerator idGenerator)
|
||||
: IRequestHandler<PurchaseQuotaPackageCommand, TenantQuotaPurchaseDto>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<TenantQuotaPurchaseDto> Handle(PurchaseQuotaPackageCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 查找配额包
|
||||
var quotaPackage = await quotaPackageRepository.FindByIdAsync(request.QuotaPackageId, cancellationToken);
|
||||
|
||||
if (quotaPackage == null)
|
||||
{
|
||||
throw new InvalidOperationException("配额包不存在");
|
||||
}
|
||||
|
||||
// 2. 创建购买记录
|
||||
var purchase = new TenantQuotaPackagePurchase
|
||||
{
|
||||
Id = idGenerator.NextId(),
|
||||
TenantId = request.TenantId,
|
||||
QuotaPackageId = request.QuotaPackageId,
|
||||
QuotaValue = quotaPackage.QuotaValue,
|
||||
Price = quotaPackage.Price,
|
||||
PurchasedAt = DateTime.UtcNow,
|
||||
ExpiredAt = request.ExpiredAt,
|
||||
Notes = request.Notes,
|
||||
CreatedAt = DateTime.UtcNow
|
||||
};
|
||||
|
||||
// 3. 保存购买记录
|
||||
await quotaPackageRepository.AddPurchaseAsync(purchase, cancellationToken);
|
||||
|
||||
// 4. 更新租户配额(根据配额类型更新对应配额)
|
||||
var quotaUsage = await quotaPackageRepository.FindUsageAsync(request.TenantId, quotaPackage.QuotaType, cancellationToken);
|
||||
|
||||
if (quotaUsage != null)
|
||||
{
|
||||
quotaUsage.LimitValue += quotaPackage.QuotaValue;
|
||||
await quotaPackageRepository.UpdateUsageAsync(quotaUsage, cancellationToken);
|
||||
}
|
||||
|
||||
await quotaPackageRepository.SaveChangesAsync(cancellationToken);
|
||||
|
||||
// 5. 返回 DTO
|
||||
return new TenantQuotaPurchaseDto
|
||||
{
|
||||
Id = purchase.Id,
|
||||
TenantId = purchase.TenantId,
|
||||
QuotaPackageId = purchase.QuotaPackageId,
|
||||
QuotaPackageName = quotaPackage.Name,
|
||||
QuotaType = quotaPackage.QuotaType,
|
||||
QuotaValue = purchase.QuotaValue,
|
||||
Price = purchase.Price,
|
||||
PurchasedAt = purchase.PurchasedAt,
|
||||
ExpiredAt = purchase.ExpiredAt,
|
||||
Notes = purchase.Notes
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.QuotaPackages.Commands;
|
||||
using TakeoutSaaS.Application.App.QuotaPackages.Dto;
|
||||
using TakeoutSaaS.Domain.Tenants.Repositories;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.QuotaPackages.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 更新配额包命令处理器。
|
||||
/// </summary>
|
||||
public sealed class UpdateQuotaPackageCommandHandler(IQuotaPackageRepository quotaPackageRepository)
|
||||
: IRequestHandler<UpdateQuotaPackageCommand, QuotaPackageDto?>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<QuotaPackageDto?> Handle(UpdateQuotaPackageCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 查找配额包
|
||||
var quotaPackage = await quotaPackageRepository.FindByIdAsync(request.QuotaPackageId, cancellationToken);
|
||||
|
||||
if (quotaPackage == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// 2. 更新配额包
|
||||
quotaPackage.Name = request.Name;
|
||||
quotaPackage.QuotaType = request.QuotaType;
|
||||
quotaPackage.QuotaValue = request.QuotaValue;
|
||||
quotaPackage.Price = request.Price;
|
||||
quotaPackage.IsActive = request.IsActive;
|
||||
quotaPackage.SortOrder = request.SortOrder;
|
||||
quotaPackage.Description = request.Description;
|
||||
quotaPackage.UpdatedAt = DateTime.UtcNow;
|
||||
|
||||
// 3. 保存到数据库
|
||||
await quotaPackageRepository.UpdateAsync(quotaPackage, cancellationToken);
|
||||
await quotaPackageRepository.SaveChangesAsync(cancellationToken);
|
||||
|
||||
// 4. 返回 DTO
|
||||
return new QuotaPackageDto
|
||||
{
|
||||
Id = quotaPackage.Id,
|
||||
Name = quotaPackage.Name,
|
||||
QuotaType = quotaPackage.QuotaType,
|
||||
QuotaValue = quotaPackage.QuotaValue,
|
||||
Price = quotaPackage.Price,
|
||||
IsActive = quotaPackage.IsActive,
|
||||
SortOrder = quotaPackage.SortOrder,
|
||||
Description = quotaPackage.Description,
|
||||
CreatedAt = quotaPackage.CreatedAt,
|
||||
UpdatedAt = quotaPackage.UpdatedAt
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.QuotaPackages.Commands;
|
||||
using TakeoutSaaS.Domain.Tenants.Repositories;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.QuotaPackages.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 更新配额包状态命令处理器。
|
||||
/// </summary>
|
||||
public sealed class UpdateQuotaPackageStatusCommandHandler(IQuotaPackageRepository quotaPackageRepository)
|
||||
: IRequestHandler<UpdateQuotaPackageStatusCommand, bool>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<bool> Handle(UpdateQuotaPackageStatusCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 查找配额包
|
||||
var quotaPackage = await quotaPackageRepository.FindByIdAsync(request.QuotaPackageId, cancellationToken);
|
||||
|
||||
if (quotaPackage == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 更新状态
|
||||
quotaPackage.IsActive = request.IsActive;
|
||||
quotaPackage.UpdatedAt = DateTime.UtcNow;
|
||||
|
||||
// 3. 保存到数据库
|
||||
await quotaPackageRepository.UpdateAsync(quotaPackage, cancellationToken);
|
||||
await quotaPackageRepository.SaveChangesAsync(cancellationToken);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user