using MediatR; using System.Data; using System.Data.Common; using TakeoutSaaS.Application.App.Billings.Dto; using TakeoutSaaS.Application.App.Billings.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.Billings.Handlers; /// /// 查询逾期账单列表处理器。 /// public sealed class GetOverdueBillingsQueryHandler( IDapperExecutor dapperExecutor) : IRequestHandler> { /// /// 处理查询逾期账单列表请求。 /// /// 查询命令。 /// 取消标记。 /// 分页逾期账单列表 DTO。 public async Task> Handle(GetOverdueBillingsQuery request, CancellationToken cancellationToken) { // 1. 参数规范化 var page = request.PageNumber <= 0 ? 1 : request.PageNumber; var pageSize = request.PageSize is <= 0 or > 200 ? 20 : request.PageSize; var offset = (page - 1) * pageSize; var now = DateTime.UtcNow; // 2. (空行后) 查询总数 + 列表 return await dapperExecutor.QueryAsync( DatabaseConstants.AppDataSource, DatabaseConnectionRole.Read, async (connection, token) => { // 2.1 统计总数 var total = await ExecuteScalarIntAsync( connection, BuildCountSql(), [ ("now", now) ], token); // 2.2 (空行后) 查询列表 await using var listCommand = CreateCommand( connection, BuildListSql(), [ ("now", now), ("offset", offset), ("limit", pageSize) ]); await using var reader = await listCommand.ExecuteReaderAsync(token); var items = new List(); while (await reader.ReadAsync(token)) { var dueDate = reader.GetDateTime(13); var status = (TenantBillingStatus)reader.GetInt32(12); var amountDue = reader.GetDecimal(8); var discountAmount = reader.GetDecimal(9); var taxAmount = reader.GetDecimal(10); var totalAmount = amountDue - discountAmount + taxAmount; var overdueDays = dueDate < now ? (int)(now - dueDate).TotalDays : 0; items.Add(new BillingListDto { Id = reader.GetInt64(0), TenantId = reader.GetInt64(1), TenantName = reader.IsDBNull(2) ? string.Empty : reader.GetString(2), SubscriptionId = reader.IsDBNull(3) ? null : reader.GetInt64(3), StatementNo = reader.GetString(4), BillingType = (BillingType)reader.GetInt32(5), PeriodStart = reader.GetDateTime(6), PeriodEnd = reader.GetDateTime(7), AmountDue = amountDue, DiscountAmount = discountAmount, TaxAmount = taxAmount, TotalAmount = totalAmount, AmountPaid = reader.GetDecimal(11), Status = status, DueDate = dueDate, Currency = reader.IsDBNull(14) ? "CNY" : reader.GetString(14), IsOverdue = true, OverdueDays = overdueDays, CreatedAt = reader.GetDateTime(15), UpdatedAt = reader.IsDBNull(16) ? null : reader.GetDateTime(16) }); } // 2.3 (空行后) 返回分页 return new PagedResult(items, page, pageSize, total); }, cancellationToken); } private static string BuildCountSql() { return """ select count(*) from public.tenant_billing_statements b join public.tenants t on t."Id" = b."TenantId" and t."DeletedAt" is null where b."DeletedAt" is null and b."DueDate" < @now and b."Status" in (0, 2); """; } private static string BuildListSql() { return """ select b."Id", b."TenantId", t."Name" as "TenantName", b."SubscriptionId", b."StatementNo", b."BillingType", b."PeriodStart", b."PeriodEnd", b."AmountDue", b."DiscountAmount", b."TaxAmount", b."AmountPaid", b."Status", b."DueDate", b."Currency", b."CreatedAt", b."UpdatedAt" from public.tenant_billing_statements b join public.tenants t on t."Id" = b."TenantId" and t."DeletedAt" is null where b."DeletedAt" is null and b."DueDate" < @now and b."Status" in (0, 2) order by b."DueDate" asc 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; } }