feat: 套餐管理与配额校验能力
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Tenants.Dto;
|
||||
using TakeoutSaaS.Domain.Tenants.Enums;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Tenants.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// 校验并消费租户配额命令。
|
||||
/// </summary>
|
||||
public sealed record CheckTenantQuotaCommand : IRequest<QuotaCheckResultDto>
|
||||
{
|
||||
/// <summary>
|
||||
/// 目标租户 ID。
|
||||
/// </summary>
|
||||
public long TenantId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 配额类型。
|
||||
/// </summary>
|
||||
public TenantQuotaType QuotaType { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 本次申请使用量。
|
||||
/// </summary>
|
||||
public decimal Delta { get; init; } = 1;
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Tenants.Dto;
|
||||
using TakeoutSaaS.Domain.Tenants.Enums;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Tenants.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// 创建租户套餐命令。
|
||||
/// </summary>
|
||||
public sealed record CreateTenantPackageCommand : IRequest<TenantPackageDto>
|
||||
{
|
||||
/// <summary>
|
||||
/// 套餐名称。
|
||||
/// </summary>
|
||||
public string Name { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 套餐描述。
|
||||
/// </summary>
|
||||
public string? Description { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 套餐类型。
|
||||
/// </summary>
|
||||
public TenantPackageType PackageType { get; init; } = TenantPackageType.Standard;
|
||||
|
||||
/// <summary>
|
||||
/// 月付价格。
|
||||
/// </summary>
|
||||
public decimal? MonthlyPrice { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 年付价格。
|
||||
/// </summary>
|
||||
public decimal? YearlyPrice { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 最大门店数。
|
||||
/// </summary>
|
||||
public int? MaxStoreCount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 最大账号数。
|
||||
/// </summary>
|
||||
public int? MaxAccountCount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 存储上限(GB)。
|
||||
/// </summary>
|
||||
public int? MaxStorageGb { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 短信额度。
|
||||
/// </summary>
|
||||
public int? MaxSmsCredits { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 配送单上限。
|
||||
/// </summary>
|
||||
public int? MaxDeliveryOrders { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 权益明细 JSON。
|
||||
/// </summary>
|
||||
public string? FeaturePoliciesJson { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否可售。
|
||||
/// </summary>
|
||||
public bool IsActive { get; init; } = true;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using MediatR;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Tenants.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// 删除租户套餐命令。
|
||||
/// </summary>
|
||||
public sealed record DeleteTenantPackageCommand : IRequest<bool>
|
||||
{
|
||||
/// <summary>
|
||||
/// 套餐 ID。
|
||||
/// </summary>
|
||||
public long TenantPackageId { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Tenants.Dto;
|
||||
using TakeoutSaaS.Domain.Tenants.Enums;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Tenants.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// 更新租户套餐命令。
|
||||
/// </summary>
|
||||
public sealed record UpdateTenantPackageCommand : IRequest<TenantPackageDto?>
|
||||
{
|
||||
/// <summary>
|
||||
/// 套餐 ID。
|
||||
/// </summary>
|
||||
public long TenantPackageId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 套餐名称。
|
||||
/// </summary>
|
||||
public string Name { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 套餐描述。
|
||||
/// </summary>
|
||||
public string? Description { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 套餐类型。
|
||||
/// </summary>
|
||||
public TenantPackageType PackageType { get; init; } = TenantPackageType.Standard;
|
||||
|
||||
/// <summary>
|
||||
/// 月付价格。
|
||||
/// </summary>
|
||||
public decimal? MonthlyPrice { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 年付价格。
|
||||
/// </summary>
|
||||
public decimal? YearlyPrice { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 最大门店数。
|
||||
/// </summary>
|
||||
public int? MaxStoreCount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 最大账号数。
|
||||
/// </summary>
|
||||
public int? MaxAccountCount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 存储上限(GB)。
|
||||
/// </summary>
|
||||
public int? MaxStorageGb { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 短信额度。
|
||||
/// </summary>
|
||||
public int? MaxSmsCredits { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 配送单上限。
|
||||
/// </summary>
|
||||
public int? MaxDeliveryOrders { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 权益明细 JSON。
|
||||
/// </summary>
|
||||
public string? FeaturePoliciesJson { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否可售。
|
||||
/// </summary>
|
||||
public bool IsActive { get; init; } = true;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using TakeoutSaaS.Domain.Tenants.Enums;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Tenants.Dto;
|
||||
|
||||
/// <summary>
|
||||
/// 配额校验结果。
|
||||
/// </summary>
|
||||
public sealed class QuotaCheckResultDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 配额类型。
|
||||
/// </summary>
|
||||
public TenantQuotaType QuotaType { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前配额上限,null 表示无限制。
|
||||
/// </summary>
|
||||
public decimal? Limit { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 已使用数量。
|
||||
/// </summary>
|
||||
public decimal Used { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 剩余额度,null 表示无限制。
|
||||
/// </summary>
|
||||
public decimal? Remaining { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using TakeoutSaaS.Domain.Tenants.Enums;
|
||||
using TakeoutSaaS.Shared.Abstractions.Serialization;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Tenants.Dto;
|
||||
|
||||
/// <summary>
|
||||
/// 租户套餐 DTO。
|
||||
/// </summary>
|
||||
public sealed class TenantPackageDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 套餐 ID。
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(SnowflakeIdJsonConverter))]
|
||||
public long Id { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 套餐名称。
|
||||
/// </summary>
|
||||
public string Name { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 描述。
|
||||
/// </summary>
|
||||
public string? Description { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 套餐类型。
|
||||
/// </summary>
|
||||
public TenantPackageType PackageType { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 月付价格。
|
||||
/// </summary>
|
||||
public decimal? MonthlyPrice { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 年付价格。
|
||||
/// </summary>
|
||||
public decimal? YearlyPrice { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 最大门店数。
|
||||
/// </summary>
|
||||
public int? MaxStoreCount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 最大账号数。
|
||||
/// </summary>
|
||||
public int? MaxAccountCount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 存储上限(GB)。
|
||||
/// </summary>
|
||||
public int? MaxStorageGb { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 短信额度。
|
||||
/// </summary>
|
||||
public int? MaxSmsCredits { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 配送单上限。
|
||||
/// </summary>
|
||||
public int? MaxDeliveryOrders { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 权益明细 JSON。
|
||||
/// </summary>
|
||||
public string? FeaturePoliciesJson { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否可售。
|
||||
/// </summary>
|
||||
public bool IsActive { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
using System;
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Tenants.Commands;
|
||||
using TakeoutSaaS.Application.App.Tenants.Dto;
|
||||
using TakeoutSaaS.Domain.Tenants.Entities;
|
||||
using TakeoutSaaS.Domain.Tenants.Enums;
|
||||
using TakeoutSaaS.Domain.Tenants.Repositories;
|
||||
using TakeoutSaaS.Shared.Abstractions.Constants;
|
||||
using TakeoutSaaS.Shared.Abstractions.Exceptions;
|
||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Tenants.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 配额校验处理器。
|
||||
/// </summary>
|
||||
public sealed class CheckTenantQuotaCommandHandler(
|
||||
ITenantRepository tenantRepository,
|
||||
ITenantPackageRepository packageRepository,
|
||||
ITenantQuotaUsageRepository quotaUsageRepository,
|
||||
ITenantProvider tenantProvider)
|
||||
: IRequestHandler<CheckTenantQuotaCommand, QuotaCheckResultDto>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<QuotaCheckResultDto> Handle(CheckTenantQuotaCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
if (request.Delta <= 0)
|
||||
{
|
||||
throw new BusinessException(ErrorCodes.BadRequest, "配额消耗量必须大于 0");
|
||||
}
|
||||
|
||||
var currentTenantId = tenantProvider.GetCurrentTenantId();
|
||||
if (currentTenantId == 0 || currentTenantId != request.TenantId)
|
||||
{
|
||||
throw new BusinessException(ErrorCodes.Forbidden, "租户上下文不匹配,请在请求头 X-Tenant-Id 指定目标租户");
|
||||
}
|
||||
|
||||
// 1. 获取租户与当前订阅。
|
||||
_ = await tenantRepository.FindByIdAsync(request.TenantId, cancellationToken)
|
||||
?? throw new BusinessException(ErrorCodes.NotFound, "租户不存在");
|
||||
|
||||
var subscription = await tenantRepository.GetActiveSubscriptionAsync(request.TenantId, cancellationToken);
|
||||
if (subscription == null || subscription.EffectiveTo <= DateTime.UtcNow)
|
||||
{
|
||||
throw new BusinessException(ErrorCodes.Conflict, "订阅不存在或已到期");
|
||||
}
|
||||
|
||||
var package = await packageRepository.FindByIdAsync(subscription.TenantPackageId, cancellationToken)
|
||||
?? throw new BusinessException(ErrorCodes.NotFound, "套餐不存在");
|
||||
|
||||
var limit = ResolveLimit(package, request.QuotaType);
|
||||
|
||||
// 2. 加载配额使用记录并计算。
|
||||
var usage = await quotaUsageRepository.FindAsync(request.TenantId, request.QuotaType, cancellationToken)
|
||||
?? new TenantQuotaUsage
|
||||
{
|
||||
TenantId = request.TenantId,
|
||||
QuotaType = request.QuotaType,
|
||||
LimitValue = limit ?? 0,
|
||||
UsedValue = 0,
|
||||
ResetCycle = ResolveResetCycle(request.QuotaType)
|
||||
};
|
||||
|
||||
var usedAfter = usage.UsedValue + request.Delta;
|
||||
if (limit.HasValue && usedAfter > (decimal)limit.Value)
|
||||
{
|
||||
usage.LimitValue = limit.Value;
|
||||
await PersistUsageAsync(usage, quotaUsageRepository, cancellationToken);
|
||||
throw new BusinessException(ErrorCodes.Conflict, $"{request.QuotaType} 配额不足");
|
||||
}
|
||||
|
||||
usage.LimitValue = limit ?? usage.LimitValue;
|
||||
usage.UsedValue = usedAfter;
|
||||
usage.ResetCycle ??= ResolveResetCycle(request.QuotaType);
|
||||
|
||||
await PersistUsageAsync(usage, quotaUsageRepository, cancellationToken);
|
||||
|
||||
return new QuotaCheckResultDto
|
||||
{
|
||||
QuotaType = request.QuotaType,
|
||||
Limit = limit,
|
||||
Used = usage.UsedValue,
|
||||
Remaining = limit.HasValue ? limit.Value - usage.UsedValue : null
|
||||
};
|
||||
}
|
||||
|
||||
private static decimal? ResolveLimit(TenantPackage package, TenantQuotaType quotaType)
|
||||
{
|
||||
return quotaType switch
|
||||
{
|
||||
TenantQuotaType.StoreCount => package.MaxStoreCount,
|
||||
TenantQuotaType.AccountCount => package.MaxAccountCount,
|
||||
TenantQuotaType.Storage => package.MaxStorageGb,
|
||||
TenantQuotaType.SmsCredits => package.MaxSmsCredits,
|
||||
TenantQuotaType.DeliveryOrders => package.MaxDeliveryOrders,
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
private static string ResolveResetCycle(TenantQuotaType quotaType)
|
||||
{
|
||||
return quotaType switch
|
||||
{
|
||||
TenantQuotaType.SmsCredits => "monthly",
|
||||
TenantQuotaType.DeliveryOrders => "monthly",
|
||||
_ => "lifetime"
|
||||
};
|
||||
}
|
||||
|
||||
private static async Task PersistUsageAsync(
|
||||
TenantQuotaUsage usage,
|
||||
ITenantQuotaUsageRepository quotaUsageRepository,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// 判断是否为新增。
|
||||
if (usage.Id == 0)
|
||||
{
|
||||
await quotaUsageRepository.AddAsync(usage, cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
await quotaUsageRepository.UpdateAsync(usage, cancellationToken);
|
||||
}
|
||||
|
||||
await quotaUsageRepository.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Tenants.Commands;
|
||||
using TakeoutSaaS.Application.App.Tenants.Dto;
|
||||
using TakeoutSaaS.Domain.Tenants.Entities;
|
||||
using TakeoutSaaS.Domain.Tenants.Repositories;
|
||||
using TakeoutSaaS.Shared.Abstractions.Constants;
|
||||
using TakeoutSaaS.Shared.Abstractions.Exceptions;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Tenants.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 创建租户套餐处理器。
|
||||
/// </summary>
|
||||
public sealed class CreateTenantPackageCommandHandler(ITenantPackageRepository packageRepository)
|
||||
: IRequestHandler<CreateTenantPackageCommand, TenantPackageDto>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<TenantPackageDto> Handle(CreateTenantPackageCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(request.Name))
|
||||
{
|
||||
throw new BusinessException(ErrorCodes.BadRequest, "套餐名称不能为空");
|
||||
}
|
||||
|
||||
var package = new TenantPackage
|
||||
{
|
||||
Name = request.Name.Trim(),
|
||||
Description = request.Description,
|
||||
PackageType = request.PackageType,
|
||||
MonthlyPrice = request.MonthlyPrice,
|
||||
YearlyPrice = request.YearlyPrice,
|
||||
MaxStoreCount = request.MaxStoreCount,
|
||||
MaxAccountCount = request.MaxAccountCount,
|
||||
MaxStorageGb = request.MaxStorageGb,
|
||||
MaxSmsCredits = request.MaxSmsCredits,
|
||||
MaxDeliveryOrders = request.MaxDeliveryOrders,
|
||||
FeaturePoliciesJson = request.FeaturePoliciesJson,
|
||||
IsActive = request.IsActive
|
||||
};
|
||||
|
||||
await packageRepository.AddAsync(package, cancellationToken);
|
||||
await packageRepository.SaveChangesAsync(cancellationToken);
|
||||
|
||||
return package.ToDto();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Tenants.Commands;
|
||||
using TakeoutSaaS.Domain.Tenants.Repositories;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Tenants.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 删除租户套餐处理器。
|
||||
/// </summary>
|
||||
public sealed class DeleteTenantPackageCommandHandler(ITenantPackageRepository packageRepository)
|
||||
: IRequestHandler<DeleteTenantPackageCommand, bool>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<bool> Handle(DeleteTenantPackageCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
await packageRepository.DeleteAsync(request.TenantPackageId, cancellationToken);
|
||||
await packageRepository.SaveChangesAsync(cancellationToken);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Tenants.Dto;
|
||||
using TakeoutSaaS.Application.App.Tenants.Queries;
|
||||
using TakeoutSaaS.Domain.Tenants.Repositories;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Tenants.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 套餐详情查询处理器。
|
||||
/// </summary>
|
||||
public sealed class GetTenantPackageByIdQueryHandler(ITenantPackageRepository packageRepository)
|
||||
: IRequestHandler<GetTenantPackageByIdQuery, TenantPackageDto?>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<TenantPackageDto?> Handle(GetTenantPackageByIdQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var package = await packageRepository.FindByIdAsync(request.TenantPackageId, cancellationToken);
|
||||
return package?.ToDto();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using System.Linq;
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Tenants.Dto;
|
||||
using TakeoutSaaS.Application.App.Tenants.Queries;
|
||||
using TakeoutSaaS.Domain.Tenants.Repositories;
|
||||
using TakeoutSaaS.Shared.Abstractions.Results;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Tenants.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 套餐分页查询处理器。
|
||||
/// </summary>
|
||||
public sealed class SearchTenantPackagesQueryHandler(ITenantPackageRepository packageRepository)
|
||||
: IRequestHandler<SearchTenantPackagesQuery, PagedResult<TenantPackageDto>>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<PagedResult<TenantPackageDto>> Handle(SearchTenantPackagesQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var packages = await packageRepository.SearchAsync(request.Keyword, request.IsActive, cancellationToken);
|
||||
|
||||
var ordered = packages.OrderByDescending(x => x.CreatedAt).ToList();
|
||||
var pageIndex = request.Page <= 0 ? 1 : request.Page;
|
||||
var size = request.PageSize <= 0 ? 20 : request.PageSize;
|
||||
|
||||
var pagedItems = ordered
|
||||
.Skip((pageIndex - 1) * size)
|
||||
.Take(size)
|
||||
.Select(x => x.ToDto())
|
||||
.ToList();
|
||||
|
||||
return new PagedResult<TenantPackageDto>(pagedItems, pageIndex, size, ordered.Count);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Tenants.Commands;
|
||||
using TakeoutSaaS.Application.App.Tenants.Dto;
|
||||
using TakeoutSaaS.Domain.Tenants.Repositories;
|
||||
using TakeoutSaaS.Shared.Abstractions.Constants;
|
||||
using TakeoutSaaS.Shared.Abstractions.Exceptions;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Tenants.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 更新租户套餐处理器。
|
||||
/// </summary>
|
||||
public sealed class UpdateTenantPackageCommandHandler(ITenantPackageRepository packageRepository)
|
||||
: IRequestHandler<UpdateTenantPackageCommand, TenantPackageDto?>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<TenantPackageDto?> Handle(UpdateTenantPackageCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(request.Name))
|
||||
{
|
||||
throw new BusinessException(ErrorCodes.BadRequest, "套餐名称不能为空");
|
||||
}
|
||||
|
||||
var package = await packageRepository.FindByIdAsync(request.TenantPackageId, cancellationToken);
|
||||
if (package == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
package.Name = request.Name.Trim();
|
||||
package.Description = request.Description;
|
||||
package.PackageType = request.PackageType;
|
||||
package.MonthlyPrice = request.MonthlyPrice;
|
||||
package.YearlyPrice = request.YearlyPrice;
|
||||
package.MaxStoreCount = request.MaxStoreCount;
|
||||
package.MaxAccountCount = request.MaxAccountCount;
|
||||
package.MaxStorageGb = request.MaxStorageGb;
|
||||
package.MaxSmsCredits = request.MaxSmsCredits;
|
||||
package.MaxDeliveryOrders = request.MaxDeliveryOrders;
|
||||
package.FeaturePoliciesJson = request.FeaturePoliciesJson;
|
||||
package.IsActive = request.IsActive;
|
||||
|
||||
await packageRepository.UpdateAsync(package, cancellationToken);
|
||||
await packageRepository.SaveChangesAsync(cancellationToken);
|
||||
|
||||
return package.ToDto();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Tenants.Dto;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Tenants.Queries;
|
||||
|
||||
/// <summary>
|
||||
/// 获取套餐详情查询。
|
||||
/// </summary>
|
||||
public sealed record GetTenantPackageByIdQuery : IRequest<TenantPackageDto?>
|
||||
{
|
||||
/// <summary>
|
||||
/// 套餐 ID。
|
||||
/// </summary>
|
||||
public long TenantPackageId { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Tenants.Dto;
|
||||
using TakeoutSaaS.Shared.Abstractions.Results;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Tenants.Queries;
|
||||
|
||||
/// <summary>
|
||||
/// 分页查询租户套餐。
|
||||
/// </summary>
|
||||
public sealed record SearchTenantPackagesQuery : IRequest<PagedResult<TenantPackageDto>>
|
||||
{
|
||||
/// <summary>
|
||||
/// 搜索关键词(名称/描述)。
|
||||
/// </summary>
|
||||
public string? Keyword { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否筛选可售套餐。
|
||||
/// </summary>
|
||||
public bool? IsActive { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 页码。
|
||||
/// </summary>
|
||||
public int Page { get; init; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// 每页条数。
|
||||
/// </summary>
|
||||
public int PageSize { get; init; } = 20;
|
||||
}
|
||||
@@ -73,4 +73,22 @@ internal static class TenantMapping
|
||||
CurrentStatus = log.CurrentStatus,
|
||||
CreatedAt = log.CreatedAt
|
||||
};
|
||||
|
||||
public static TenantPackageDto ToDto(this TenantPackage package)
|
||||
=> new()
|
||||
{
|
||||
Id = package.Id,
|
||||
Name = package.Name,
|
||||
Description = package.Description,
|
||||
PackageType = package.PackageType,
|
||||
MonthlyPrice = package.MonthlyPrice,
|
||||
YearlyPrice = package.YearlyPrice,
|
||||
MaxStoreCount = package.MaxStoreCount,
|
||||
MaxAccountCount = package.MaxAccountCount,
|
||||
MaxStorageGb = package.MaxStorageGb,
|
||||
MaxSmsCredits = package.MaxSmsCredits,
|
||||
MaxDeliveryOrders = package.MaxDeliveryOrders,
|
||||
FeaturePoliciesJson = package.FeaturePoliciesJson,
|
||||
IsActive = package.IsActive
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user