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.Ids; using TakeoutSaaS.Shared.Abstractions.Tenancy; namespace TakeoutSaaS.Application.App.Tenants.Handlers; /// /// 实名资料提交流程。 /// public sealed class SubmitTenantVerificationCommandHandler( ITenantRepository tenantRepository, ITenantProvider tenantProvider, IIdGenerator idGenerator) : IRequestHandler { /// public async Task Handle(SubmitTenantVerificationCommand request, CancellationToken cancellationToken) { // 1. 校验租户上下文(租户端禁止跨租户) var currentTenantId = tenantProvider.GetCurrentTenantId(); if (currentTenantId <= 0) { throw new BusinessException(ErrorCodes.BadRequest, "缺少租户标识"); } // 2. (空行后) 兼容旧调用:若传入 TenantId,则必须与当前租户一致 if (request.TenantId > 0 && request.TenantId != currentTenantId) { throw new BusinessException(ErrorCodes.Forbidden, "无权提交其他租户实名认证资料"); } // 3. (空行后) 获取租户 var tenant = await tenantRepository.FindByIdAsync(currentTenantId, cancellationToken) ?? throw new BusinessException(ErrorCodes.NotFound, "租户不存在"); // 4. (空行后) 读取或初始化实名资料 var profile = await tenantRepository.GetVerificationProfileAsync(currentTenantId, cancellationToken) ?? new TenantVerificationProfile { Id = idGenerator.NextId(), TenantId = tenant.Id }; // 5. (空行后) 填充资料 profile.BusinessLicenseNumber = request.BusinessLicenseNumber; profile.BusinessLicenseUrl = request.BusinessLicenseUrl; profile.LegalPersonName = request.LegalPersonName; profile.LegalPersonIdNumber = request.LegalPersonIdNumber; profile.LegalPersonIdFrontUrl = request.LegalPersonIdFrontUrl; profile.LegalPersonIdBackUrl = request.LegalPersonIdBackUrl; profile.BankAccountName = request.BankAccountName; profile.BankAccountNumber = request.BankAccountNumber; profile.BankName = request.BankName; profile.AdditionalDataJson = request.AdditionalDataJson; profile.Status = TenantVerificationStatus.Pending; profile.SubmittedAt = DateTime.UtcNow; profile.ReviewedAt = null; profile.ReviewRemarks = null; profile.ReviewedBy = null; profile.ReviewedByName = null; // 6. (空行后) 保存资料并记录审计 await tenantRepository.UpsertVerificationProfileAsync(profile, cancellationToken); await tenantRepository.AddAuditLogAsync(new TenantAuditLog { TenantId = tenant.Id, Action = TenantAuditAction.VerificationSubmitted, Title = "提交实名认证资料", Description = request.BusinessLicenseNumber }, cancellationToken); await tenantRepository.SaveChangesAsync(cancellationToken); // 7. (空行后) 返回 DTO return profile.ToVerificationDto() ?? throw new BusinessException(ErrorCodes.InternalServerError, "实名资料保存失败"); } }