using MediatR; using System.Data; using System.Data.Common; using TakeoutSaaS.Application.App.Billings.Dto; using TakeoutSaaS.Application.App.Billings.Queries; using TakeoutSaaS.Shared.Abstractions.Constants; using TakeoutSaaS.Shared.Abstractions.Data; using TakeoutSaaS.Shared.Abstractions.Exceptions; using TakeoutSaaS.Domain.Tenants.Enums; namespace TakeoutSaaS.Application.App.Billings.Handlers; /// /// 查询账单支付记录处理器。 /// public sealed class GetBillingPaymentsQueryHandler( IDapperExecutor dapperExecutor) : IRequestHandler> { /// /// 处理查询账单支付记录请求。 /// /// 查询命令。 /// 取消标记。 /// 支付记录列表 DTO。 public async Task> Handle(GetBillingPaymentsQuery request, CancellationToken cancellationToken) { // 1. 校验账单是否存在 return await dapperExecutor.QueryAsync( DatabaseConstants.AppDataSource, DatabaseConnectionRole.Read, async (connection, token) => { // 1.1 校验账单存在 var exists = await ExecuteScalarIntAsync( connection, """ select 1 from public.tenant_billing_statements b where b."DeletedAt" is null and b."Id" = @billingId limit 1; """, [ ("billingId", request.BillingId) ], token); if (exists == 0) { throw new BusinessException(ErrorCodes.NotFound, "账单不存在"); } // 1.2 查询支付记录 await using var command = CreateCommand( connection, """ select p."Id", p."TenantId", p."BillingStatementId", p."Amount", p."Method", p."Status", p."TransactionNo", p."ProofUrl", p."Notes", p."VerifiedBy", p."VerifiedAt", p."RefundReason", p."RefundedAt", p."PaidAt", p."CreatedAt" from public.tenant_payments p where p."DeletedAt" is null and p."BillingStatementId" = @billingId order by p."CreatedAt" desc; """, [ ("billingId", request.BillingId) ]); await using var reader = await command.ExecuteReaderAsync(token); var results = new List(); while (await reader.ReadAsync(token)) { results.Add(new PaymentRecordDto { Id = reader.GetInt64(0), TenantId = reader.GetInt64(1), BillingId = reader.GetInt64(2), Amount = reader.GetDecimal(3), Method = (TenantPaymentMethod)reader.GetInt32(4), Status = (TenantPaymentStatus)reader.GetInt32(5), TransactionNo = reader.IsDBNull(6) ? null : reader.GetString(6), ProofUrl = reader.IsDBNull(7) ? null : reader.GetString(7), Notes = reader.IsDBNull(8) ? null : reader.GetString(8), VerifiedBy = reader.IsDBNull(9) ? null : reader.GetInt64(9), VerifiedAt = reader.IsDBNull(10) ? null : reader.GetDateTime(10), RefundReason = reader.IsDBNull(11) ? null : reader.GetString(11), RefundedAt = reader.IsDBNull(12) ? null : reader.GetDateTime(12), PaidAt = reader.IsDBNull(13) ? null : reader.GetDateTime(13), IsVerified = !reader.IsDBNull(10), CreatedAt = reader.GetDateTime(14) }); } return results; }, cancellationToken); } 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; } }