fix:修复注释错误

This commit is contained in:
2026-01-04 21:22:26 +08:00
parent a427b0f22a
commit 398c716734
69 changed files with 353 additions and 318 deletions

View File

@@ -23,10 +23,10 @@ public sealed class CancelBillingCommandHandler(
throw new BusinessException(ErrorCodes.NotFound, "账单不存在");
}
// 2. (空行后) 取消账单(领域规则校验在实体方法内)
// 2. 取消账单(领域规则校验在实体方法内)
billing.Cancel(request.Reason);
// 3. (空行后) 持久化
// 3. 持久化
await billingRepository.UpdateAsync(billing, cancellationToken);
await billingRepository.SaveChangesAsync(cancellationToken);

View File

@@ -30,14 +30,14 @@ public sealed class ConfirmPaymentCommandHandler(
throw new BusinessException(ErrorCodes.Unauthorized, "未登录或无效的操作者身份");
}
// 2. (空行后) 查询账单
// 2. 查询账单
var billing = await billingRepository.FindByIdAsync(request.BillingId, cancellationToken);
if (billing is null)
{
throw new BusinessException(ErrorCodes.NotFound, "账单不存在");
}
// 3. (空行后) 业务规则检查
// 3. 业务规则检查
if (billing.Status == TenantBillingStatus.Paid)
{
throw new BusinessException(ErrorCodes.BusinessError, "已支付账单不允许重复收款");
@@ -48,7 +48,7 @@ public sealed class ConfirmPaymentCommandHandler(
throw new BusinessException(ErrorCodes.BusinessError, "已取消账单不允许收款");
}
// 4. (空行后) 金额边界:不允许超过剩余应收(与前端校验保持一致)
// 4. 金额边界:不允许超过剩余应收(与前端校验保持一致)
var totalAmount = billing.CalculateTotalAmount();
var remainingAmount = totalAmount - billing.AmountPaid;
if (request.Amount > remainingAmount)
@@ -56,7 +56,7 @@ public sealed class ConfirmPaymentCommandHandler(
throw new BusinessException(ErrorCodes.BadRequest, "支付金额不能超过剩余应收");
}
// 5. (空行后) 幂等校验:交易号唯一
// 5. 幂等校验:交易号唯一
if (!string.IsNullOrWhiteSpace(request.TransactionNo))
{
var exists = await paymentRepository.GetByTransactionNoAsync(request.TransactionNo.Trim(), cancellationToken);
@@ -66,7 +66,7 @@ public sealed class ConfirmPaymentCommandHandler(
}
}
// 6. (空行后) 构建支付记录并立即审核通过
// 6. 构建支付记录并立即审核通过
var now = DateTime.UtcNow;
var payment = new TenantPayment
{
@@ -84,15 +84,15 @@ public sealed class ConfirmPaymentCommandHandler(
payment.Verify(currentUserAccessor.UserId);
// 7. (空行后) 同步更新账单已收金额/状态(支持分次收款)
// 7. 同步更新账单已收金额/状态(支持分次收款)
billing.MarkAsPaid(payment.Amount, payment.TransactionNo ?? string.Empty);
// 8. (空行后) 持久化变更(同一 DbContext 下单次 SaveChanges 可提交两张表)
// 8. 持久化变更(同一 DbContext 下单次 SaveChanges 可提交两张表)
await paymentRepository.AddAsync(payment, cancellationToken);
await billingRepository.UpdateAsync(billing, cancellationToken);
await paymentRepository.SaveChangesAsync(cancellationToken);
// 9. (空行后) 返回 DTO
// 9. 返回 DTO
return payment.ToPaymentRecordDto();
}
}

View File

@@ -30,7 +30,7 @@ public sealed class CreateBillingCommandHandler(
throw new BusinessException(ErrorCodes.NotFound, "租户不存在");
}
// 2. (空行后) 构建账单实体
// 2. 构建账单实体
var now = DateTime.UtcNow;
var statementNo = $"BIL-{now:yyyyMMdd}-{idGenerator.NextId()}";
var lineItemsJson = JsonSerializer.Serialize(request.LineItems);
@@ -54,11 +54,11 @@ public sealed class CreateBillingCommandHandler(
Notes = request.Notes
};
// 3. (空行后) 持久化账单
// 3. 持久化账单
await billingRepository.AddAsync(billing, cancellationToken);
await billingRepository.SaveChangesAsync(cancellationToken);
// 4. (空行后) 返回详情 DTO
// 4. 返回详情 DTO
return billing.ToBillingDetailDto([], tenant.Name);
}
}

View File

@@ -24,14 +24,14 @@ public sealed class ExportBillingsQueryHandler(
throw new BusinessException(ErrorCodes.BadRequest, "账单 ID 列表不能为空");
}
// 2. (空行后) 查询账单数据
// 2. 查询账单数据
var billings = await billingRepository.GetByIdsAsync(request.BillingIds, cancellationToken);
if (billings.Count == 0)
{
throw new BusinessException(ErrorCodes.NotFound, "未找到任何匹配的账单");
}
// 3. (空行后) 根据格式导出
// 3. 根据格式导出
var format = (request.Format ?? string.Empty).Trim().ToLowerInvariant();
return format switch
{

View File

@@ -30,7 +30,7 @@ public sealed class GenerateSubscriptionBillingCommandHandler(
throw new BusinessException(ErrorCodes.NotFound, "订阅不存在");
}
// 2. (空行后) 校验套餐价格信息
// 2. 校验套餐价格信息
var subscription = detail.Subscription;
var package = detail.Package;
if (package is null)
@@ -38,7 +38,7 @@ public sealed class GenerateSubscriptionBillingCommandHandler(
throw new BusinessException(ErrorCodes.BusinessError, "订阅未关联有效套餐,无法生成账单");
}
// 3. (空行后) 按订阅周期选择价格(简化规则:优先按年/按月)
// 3. 按订阅周期选择价格(简化规则:优先按年/按月)
var billingPeriodDays = (subscription.EffectiveTo - subscription.EffectiveFrom).TotalDays;
var amountDue = billingPeriodDays >= 300
? package.YearlyPrice
@@ -49,14 +49,14 @@ public sealed class GenerateSubscriptionBillingCommandHandler(
throw new BusinessException(ErrorCodes.BusinessError, "套餐价格未配置,无法生成账单");
}
// 4. (空行后) 幂等校验:同一周期开始时间仅允许存在一张未取消账单
// 4. 幂等校验:同一周期开始时间仅允许存在一张未取消账单
var exists = await billingRepository.ExistsNotCancelledByPeriodStartAsync(subscription.TenantId, subscription.EffectiveFrom, cancellationToken);
if (exists)
{
throw new BusinessException(ErrorCodes.Conflict, "该订阅周期的账单已存在");
}
// 5. (空行后) 构建账单实体
// 5. 构建账单实体
var now = DateTime.UtcNow;
var statementNo = $"BIL-{now:yyyyMMdd}-{idGenerator.NextId()}";
var lineItems = new List<BillingLineItemDto>
@@ -91,11 +91,11 @@ public sealed class GenerateSubscriptionBillingCommandHandler(
Notes = subscription.Notes
};
// 6. (空行后) 持久化账单
// 6. 持久化账单
await billingRepository.AddAsync(billing, cancellationToken);
await billingRepository.SaveChangesAsync(cancellationToken);
// 7. (空行后) 返回详情 DTO
// 7. 返回详情 DTO
return billing.ToBillingDetailDto([], detail.TenantName);
}
}

View File

@@ -46,7 +46,7 @@ public sealed class GetBillingDetailQueryHandler(
throw new BusinessException(ErrorCodes.NotFound, "账单不存在");
}
// 1.2 (空行后) 读取账单行数据到内存(释放 Reader避免同连接并发执行命令
// 1.2 读取账单行数据到内存(释放 Reader避免同连接并发执行命令
var billingId = billReader.GetInt64(0);
var tenantId = billReader.GetInt64(1);
var tenantName = billReader.IsDBNull(2) ? string.Empty : billReader.GetString(2);
@@ -71,10 +71,10 @@ public sealed class GetBillingDetailQueryHandler(
DateTime? updatedAt = billReader.IsDBNull(21) ? null : billReader.GetDateTime(21);
long? updatedBy = billReader.IsDBNull(22) ? null : billReader.GetInt64(22);
// 1.3 (空行后) 主动释放账单 Reader确保后续查询不会触发 Npgsql 并发命令异常
// 1.3 主动释放账单 Reader确保后续查询不会触发 Npgsql 并发命令异常
await billReader.DisposeAsync();
// 1.4 (空行后) 反序列化账单明细
// 1.4 反序列化账单明细
var lineItems = new List<BillingLineItemDto>();
if (!string.IsNullOrWhiteSpace(lineItemsJson))
{
@@ -88,7 +88,7 @@ public sealed class GetBillingDetailQueryHandler(
}
}
// 1.5 (空行后) 查询支付记录
// 1.5 查询支付记录
var payments = new List<PaymentRecordDto>();
await using var paymentCommand = CreateCommand(
connection,
@@ -121,7 +121,7 @@ public sealed class GetBillingDetailQueryHandler(
});
}
// 1.6 (空行后) 组装详情 DTO
// 1.6 组装详情 DTO
var totalAmount = amountDue - discountAmount + taxAmount;
return new BillingDetailDto

View File

@@ -33,13 +33,13 @@ public sealed class GetBillingListQueryHandler(
var maxAmount = request.MaxAmount;
var offset = (page - 1) * pageSize;
// 1.1 (空行后) 金额区间规范化(避免 min > max 导致结果为空)
// 1.1 金额区间规范化(避免 min > max 导致结果为空)
if (minAmount.HasValue && maxAmount.HasValue && minAmount.Value > maxAmount.Value)
{
(minAmount, maxAmount) = (maxAmount, minAmount);
}
// 2. (空行后) 排序白名单(防 SQL 注入)
// 2. 排序白名单(防 SQL 注入)
var orderBy = request.SortBy?.Trim() switch
{
"DueDate" => "b.\"DueDate\"",
@@ -50,7 +50,7 @@ public sealed class GetBillingListQueryHandler(
_ => "b.\"CreatedAt\""
};
// 3. (空行后) 查询总数 + 列表
// 3. 查询总数 + 列表
return await dapperExecutor.QueryAsync(
DatabaseConstants.AppDataSource,
DatabaseConnectionRole.Read,
@@ -72,7 +72,7 @@ public sealed class GetBillingListQueryHandler(
],
token);
// 3.2 (空行后) 查询列表
// 3.2 查询列表
var listSql = BuildListSql(orderBy, request.SortDesc);
await using var listCommand = CreateCommand(
connection,
@@ -102,7 +102,7 @@ public sealed class GetBillingListQueryHandler(
var taxAmount = reader.GetDecimal(10);
var totalAmount = amountDue - discountAmount + taxAmount;
// 3.2.1 (空行后) 逾期辅助字段
// 3.2.1 逾期辅助字段
var isOverdue = status is TenantBillingStatus.Overdue
|| (status is TenantBillingStatus.Pending && dueDate < now);
var overdueDays = dueDate < now ? (int)(now - dueDate).TotalDays : 0;
@@ -132,7 +132,7 @@ public sealed class GetBillingListQueryHandler(
});
}
// 3.3 (空行后) 返回分页
// 3.3 返回分页
return new PagedResult<BillingListDto>(items, page, pageSize, total);
},
cancellationToken);

View File

@@ -51,7 +51,7 @@ public sealed class GetBillingPaymentsQueryHandler(
throw new BusinessException(ErrorCodes.NotFound, "账单不存在");
}
// 1.2 (空行后) 查询支付记录
// 1.2 查询支付记录
await using var command = CreateCommand(
connection,
"""

View File

@@ -29,7 +29,7 @@ public sealed class GetBillingStatisticsQueryHandler(
var endDate = request.EndDate ?? DateTime.UtcNow;
var groupBy = NormalizeGroupBy(request.GroupBy);
// 2. (空行后) 查询统计数据(总览 + 趋势)
// 2. 查询统计数据(总览 + 趋势)
return await dapperExecutor.QueryAsync(
DatabaseConstants.AppDataSource,
DatabaseConnectionRole.Read,
@@ -59,7 +59,7 @@ public sealed class GetBillingStatisticsQueryHandler(
var totalAmountUnpaid = summaryReader.IsDBNull(7) ? 0m : summaryReader.GetDecimal(7);
var totalOverdueAmount = summaryReader.IsDBNull(8) ? 0m : summaryReader.GetDecimal(8);
// 2.2 (空行后) 趋势数据
// 2.2 趋势数据
await using var trendCommand = CreateCommand(
connection,
BuildTrendSql(groupBy),
@@ -83,7 +83,7 @@ public sealed class GetBillingStatisticsQueryHandler(
countTrend[key] = trendReader.IsDBNull(3) ? 0 : trendReader.GetInt32(3);
}
// 2.3 (空行后) 组装 DTO
// 2.3 组装 DTO
return new BillingStatisticsDto
{
TenantId = request.TenantId,

View File

@@ -31,7 +31,7 @@ public sealed class GetOverdueBillingsQueryHandler(
var offset = (page - 1) * pageSize;
var now = DateTime.UtcNow;
// 2. (空行后) 查询总数 + 列表
// 2. 查询总数 + 列表
return await dapperExecutor.QueryAsync(
DatabaseConstants.AppDataSource,
DatabaseConnectionRole.Read,
@@ -46,7 +46,7 @@ public sealed class GetOverdueBillingsQueryHandler(
],
token);
// 2.2 (空行后) 查询列表
// 2.2 查询列表
await using var listCommand = CreateCommand(
connection,
BuildListSql(),
@@ -93,7 +93,7 @@ public sealed class GetOverdueBillingsQueryHandler(
});
}
// 2.3 (空行后) 返回分页
// 2.3 返回分页
return new PagedResult<BillingListDto>(items, page, pageSize, total);
},
cancellationToken);

View File

@@ -34,7 +34,7 @@ public sealed class RecordPaymentCommandHandler(
throw new BusinessException(ErrorCodes.NotFound, "账单不存在");
}
// 2. (空行后) 业务规则检查
// 2. 业务规则检查
if (billing.Status == TenantBillingStatus.Paid)
{
throw new BusinessException(ErrorCodes.BusinessError, "已支付账单不允许重复收款");
@@ -45,7 +45,7 @@ public sealed class RecordPaymentCommandHandler(
throw new BusinessException(ErrorCodes.BusinessError, "已取消账单不允许收款");
}
// 3. (空行后) 幂等校验:交易号唯一
// 3. 幂等校验:交易号唯一
if (!string.IsNullOrWhiteSpace(request.TransactionNo))
{
var exists = await paymentRepository.GetByTransactionNoAsync(request.TransactionNo.Trim(), cancellationToken);
@@ -55,7 +55,7 @@ public sealed class RecordPaymentCommandHandler(
}
}
// 4. (空行后) 构建支付记录(默认待审核)
// 4. 构建支付记录(默认待审核)
var now = DateTime.UtcNow;
var payment = new TenantPayment
{
@@ -71,11 +71,11 @@ public sealed class RecordPaymentCommandHandler(
Notes = request.Notes
};
// 5. (空行后) 持久化变更
// 5. 持久化变更
await paymentRepository.AddAsync(payment, cancellationToken);
await paymentRepository.SaveChangesAsync(cancellationToken);
// 6. (空行后) 返回 DTO
// 6. 返回 DTO
return payment.ToPaymentRecordDto();
}
}

View File

@@ -24,7 +24,7 @@ public sealed class UpdateBillingStatusCommandHandler(
throw new BusinessException(ErrorCodes.NotFound, "账单不存在");
}
// 2. (空行后) 状态转换规则校验
// 2. 状态转换规则校验
if (billing.Status == TenantBillingStatus.Paid && request.NewStatus != TenantBillingStatus.Paid)
{
throw new BusinessException(ErrorCodes.BusinessError, "已支付账单不允许改为其他状态");
@@ -35,7 +35,7 @@ public sealed class UpdateBillingStatusCommandHandler(
throw new BusinessException(ErrorCodes.BusinessError, "已取消账单不允许变更状态");
}
// 3. (空行后) 更新状态与备注
// 3. 更新状态与备注
billing.Status = request.NewStatus;
if (!string.IsNullOrWhiteSpace(request.Notes))
{
@@ -44,7 +44,7 @@ public sealed class UpdateBillingStatusCommandHandler(
: $"{billing.Notes}\n[状态变更] {request.Notes}";
}
// 4. (空行后) 持久化
// 4. 持久化
await billingRepository.UpdateAsync(billing, cancellationToken);
await billingRepository.SaveChangesAsync(cancellationToken);

View File

@@ -26,24 +26,24 @@ public sealed class VerifyPaymentCommandHandler(
throw new BusinessException(ErrorCodes.Unauthorized, "未登录或无效的操作者身份");
}
// 2. (空行后) 查询支付记录
// 2. 查询支付记录
var payment = await paymentRepository.FindByIdAsync(request.PaymentId, cancellationToken);
if (payment is null)
{
throw new BusinessException(ErrorCodes.NotFound, "支付记录不存在");
}
// 3. (空行后) 查询关联账单
// 3. 查询关联账单
var billing = await billingRepository.FindByIdAsync(payment.BillingStatementId, cancellationToken);
if (billing is null)
{
throw new BusinessException(ErrorCodes.NotFound, "关联账单不存在");
}
// 4. (空行后) 归一化审核备注
// 4. 归一化审核备注
var normalizedNotes = string.IsNullOrWhiteSpace(request.Notes) ? null : request.Notes.Trim();
// 5. (空行后) 根据审核结果更新支付与账单状态
// 5. 根据审核结果更新支付与账单状态
if (request.Approved)
{
payment.Verify(currentUserAccessor.UserId);
@@ -57,17 +57,17 @@ public sealed class VerifyPaymentCommandHandler(
payment.Notes = normalizedNotes;
}
// 6. (空行后) 持久化更新状态
// 6. 持久化更新状态
await paymentRepository.UpdateAsync(payment, cancellationToken);
if (request.Approved)
{
await billingRepository.UpdateAsync(billing, cancellationToken);
}
// 7. (空行后) 保存数据库更改
// 7. 保存数据库更改
await paymentRepository.SaveChangesAsync(cancellationToken);
// 8. (空行后) 返回 DTO
// 8. 返回 DTO
return payment.ToPaymentRecordDto();
}
}

View File

@@ -15,32 +15,32 @@ public sealed class ConfirmPaymentCommandValidator : AbstractValidator<ConfirmPa
.GreaterThan(0)
.WithMessage("账单 ID 必须大于 0");
// 2. (空行后) 支付金额必须大于 0
// 2. 支付金额必须大于 0
RuleFor(x => x.Amount)
.GreaterThan(0)
.WithMessage("支付金额必须大于 0")
.LessThanOrEqualTo(1_000_000_000)
.WithMessage("支付金额不能超过 10 亿");
// 3. (空行后) 支付方式必填
// 3. 支付方式必填
RuleFor(x => x.Method)
.IsInEnum()
.WithMessage("支付方式无效");
// 4. (空行后) 交易号必填
// 4. 交易号必填
RuleFor(x => x.TransactionNo)
.NotEmpty()
.WithMessage("交易号不能为空")
.MaximumLength(64)
.WithMessage("交易号不能超过 64 个字符");
// 5. (空行后) 支付凭证 URL可选
// 5. 支付凭证 URL可选
RuleFor(x => x.ProofUrl)
.MaximumLength(500)
.WithMessage("支付凭证 URL 不能超过 500 个字符")
.When(x => !string.IsNullOrWhiteSpace(x.ProofUrl));
// 6. (空行后) 备注(可选)
// 6. 备注(可选)
RuleFor(x => x.Notes)
.MaximumLength(500)
.WithMessage("备注不能超过 500 个字符")

View File

@@ -15,27 +15,27 @@ public sealed class CreateBillingCommandValidator : AbstractValidator<CreateBill
.GreaterThan(0)
.WithMessage("租户 ID 必须大于 0");
// 2. (空行后) 账单类型必填
// 2. 账单类型必填
RuleFor(x => x.BillingType)
.IsInEnum()
.WithMessage("账单类型无效");
// 3. (空行后) 应付金额必须大于 0
// 3. 应付金额必须大于 0
RuleFor(x => x.AmountDue)
.GreaterThan(0)
.WithMessage("应付金额必须大于 0");
// 4. (空行后) 到期日必须是未来时间
// 4. 到期日必须是未来时间
RuleFor(x => x.DueDate)
.GreaterThan(DateTime.UtcNow)
.WithMessage("到期日必须是未来时间");
// 5. (空行后) 账单明细至少包含一项
// 5. 账单明细至少包含一项
RuleFor(x => x.LineItems)
.NotEmpty()
.WithMessage("账单明细不能为空");
// 6. (空行后) 账单明细项验证
// 6. 账单明细项验证
RuleForEach(x => x.LineItems)
.ChildRules(lineItem =>
{
@@ -64,7 +64,7 @@ public sealed class CreateBillingCommandValidator : AbstractValidator<CreateBill
.WithMessage("账单明细金额不能为负数");
});
// 7. (空行后) 备注长度限制(可选)
// 7. 备注长度限制(可选)
RuleFor(x => x.Notes)
.MaximumLength(500)
.WithMessage("备注不能超过 500 个字符")

View File

@@ -15,32 +15,32 @@ public sealed class RecordPaymentCommandValidator : AbstractValidator<RecordPaym
.GreaterThan(0)
.WithMessage("账单 ID 必须大于 0");
// 2. (空行后) 支付金额必须大于 0
// 2. 支付金额必须大于 0
RuleFor(x => x.Amount)
.GreaterThan(0)
.WithMessage("支付金额必须大于 0")
.LessThanOrEqualTo(1_000_000_000)
.WithMessage("支付金额不能超过 10 亿");
// 3. (空行后) 支付方式必填
// 3. 支付方式必填
RuleFor(x => x.Method)
.IsInEnum()
.WithMessage("支付方式无效");
// 4. (空行后) 交易号必填
// 4. 交易号必填
RuleFor(x => x.TransactionNo)
.NotEmpty()
.WithMessage("交易号不能为空")
.MaximumLength(64)
.WithMessage("交易号不能超过 64 个字符");
// 5. (空行后) 支付凭证 URL可选
// 5. 支付凭证 URL可选
RuleFor(x => x.ProofUrl)
.MaximumLength(500)
.WithMessage("支付凭证 URL 不能超过 500 个字符")
.When(x => !string.IsNullOrWhiteSpace(x.ProofUrl));
// 6. (空行后) 备注(可选)
// 6. 备注(可选)
RuleFor(x => x.Notes)
.MaximumLength(500)
.WithMessage("备注不能超过 500 个字符")

View File

@@ -15,12 +15,12 @@ public sealed class UpdateBillingStatusCommandValidator : AbstractValidator<Upda
.GreaterThan(0)
.WithMessage("账单 ID 必须大于 0");
// 2. (空行后) 状态枚举校验
// 2. 状态枚举校验
RuleFor(x => x.NewStatus)
.IsInEnum()
.WithMessage("新状态无效");
// 3. (空行后) 备注长度限制(可选)
// 3. 备注长度限制(可选)
RuleFor(x => x.Notes)
.MaximumLength(500)
.WithMessage("备注不能超过 500 个字符")