using MediatR; using System.Data; using System.Data.Common; using TakeoutSaaS.Application.App.Tenants.Dto; using TakeoutSaaS.Application.App.Tenants.Queries; using TakeoutSaaS.Domain.Tenants.Enums; using TakeoutSaaS.Shared.Abstractions.Constants; using TakeoutSaaS.Shared.Abstractions.Data; using TakeoutSaaS.Shared.Abstractions.Results; namespace TakeoutSaaS.Application.App.Tenants.Handlers; /// /// 查询套餐当前使用租户列表处理器。 /// public sealed class GetTenantPackageTenantsQueryHandler(IDapperExecutor dapperExecutor) : IRequestHandler> { /// public async Task> Handle(GetTenantPackageTenantsQuery request, CancellationToken cancellationToken) { // 1. 参数规范化 var page = request.Page <= 0 ? 1 : request.Page; var pageSize = request.PageSize <= 0 ? 20 : request.PageSize; var keyword = string.IsNullOrWhiteSpace(request.Keyword) ? null : request.Keyword.Trim(); // 2. (空行后) 以当前时间为准筛选“有效订阅” var now = DateTime.UtcNow; var offset = (page - 1) * pageSize; // 3. (空行后) 查询总数 + 列表 return await dapperExecutor.QueryAsync( DatabaseConstants.AppDataSource, DatabaseConnectionRole.Read, async (connection, token) => { // 3.1 统计总数 var total = await ExecuteScalarIntAsync( connection, BuildCountSql(), [ ("packageId", request.TenantPackageId), ("now", now), ("keyword", keyword) ], token); // 3.2 (空行后) 查询列表 var listSql = BuildListSql(); await using var listCommand = CreateCommand( connection, listSql, [ ("packageId", request.TenantPackageId), ("now", now), ("keyword", keyword), ("offset", offset), ("limit", pageSize) ]); await using var reader = await listCommand.ExecuteReaderAsync(token); var items = new List(); while (await reader.ReadAsync(token)) { items.Add(new TenantPackageTenantDto { TenantId = reader.GetInt64(0), Code = reader.GetString(1), Name = reader.GetString(2), Status = (TenantStatus)reader.GetInt32(3), ContactName = reader.IsDBNull(4) ? null : reader.GetString(4), ContactPhone = reader.IsDBNull(5) ? null : reader.GetString(5), SubscriptionEffectiveFrom = reader.GetDateTime(6), SubscriptionEffectiveTo = reader.GetDateTime(7) }); } // 3.3 (空行后) 返回分页 return new PagedResult(items, page, pageSize, total); }, cancellationToken); } private static string BuildCountSql() { return """ select count(*) from public.tenants t where t."DeletedAt" is null and ( @keyword::text is null or t."Name" ilike ('%' || @keyword::text || '%') or t."Code" ilike ('%' || @keyword::text || '%') or coalesce(t."ContactName", '') ilike ('%' || @keyword::text || '%') or coalesce(t."ContactPhone", '') ilike ('%' || @keyword::text || '%') ) and exists ( select 1 from public.tenant_subscriptions s where s."DeletedAt" is null and s."TenantId" = t."Id" and s."TenantPackageId" = @packageId and s."Status" = 1 and s."EffectiveFrom" <= @now and s."EffectiveTo" >= @now ); """; } private static string BuildListSql() { return """ select t."Id" as "TenantId", t."Code", t."Name", t."Status", t."ContactName", t."ContactPhone", s."EffectiveFrom", s."EffectiveTo" from public.tenants t join lateral ( select s."EffectiveFrom", s."EffectiveTo" from public.tenant_subscriptions s where s."DeletedAt" is null and s."TenantId" = t."Id" and s."TenantPackageId" = @packageId and s."Status" = 1 and s."EffectiveFrom" <= @now and s."EffectiveTo" >= @now order by s."EffectiveTo" desc limit 1 ) s on true where t."DeletedAt" is null and ( @keyword::text is null or t."Name" ilike ('%' || @keyword::text || '%') or t."Code" ilike ('%' || @keyword::text || '%') or coalesce(t."ContactName", '') ilike ('%' || @keyword::text || '%') or coalesce(t."ContactPhone", '') ilike ('%' || @keyword::text || '%') ) order by t."CreatedAt" desc offset @offset limit @limit; """; } private static async Task ExecuteScalarIntAsync( IDbConnection connection, string sql, (string Name, object? Value)[] parameters, CancellationToken cancellationToken) { await using var command = CreateCommand(connection, sql, parameters); var result = await command.ExecuteScalarAsync(cancellationToken); return result is null or DBNull ? 0 : Convert.ToInt32(result); } private static DbCommand CreateCommand(IDbConnection connection, string sql, (string Name, object? Value)[] parameters) { var command = connection.CreateCommand(); command.CommandText = sql; foreach (var (name, value) in parameters) { var p = command.CreateParameter(); p.ParameterName = name; p.Value = value ?? DBNull.Value; command.Parameters.Add(p); } return (DbCommand)command; } }